diff --git a/app/client/cypress/e2e/Regression/ClientSide/Autocomplete/Autocomplete_setters_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Autocomplete/Autocomplete_setters_spec.ts new file mode 100644 index 0000000000..4ac4c96740 --- /dev/null +++ b/app/client/cypress/e2e/Regression/ClientSide/Autocomplete/Autocomplete_setters_spec.ts @@ -0,0 +1,56 @@ +import { + entityExplorer, + jsEditor, + agHelper, + draggableWidgets, + locators, + propPane, +} from "../../../../support/Objects/ObjectsCore"; + +const jsObjectBody = `export default { + myVar1: [], + myVar2: {}, + myFun1(){ + + }, + myFun2: async () => { + //use async-await or promises + } +}`; + +describe("Autocomplete tests for setters", () => { + it("1. Check if setters are present in autocomplete for widgets in JsObject", () => { + entityExplorer.DragDropWidgetNVerify(draggableWidgets.BUTTON, 200, 200); + jsEditor.CreateJSObject(jsObjectBody, { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + prettify: false, + }); + + agHelper.GetNClick(jsEditor._lineinJsEditor(5)); + agHelper.TypeText(locators._codeMirrorTextArea, "Button1"); + + agHelper.GetElementsNAssertTextPresence( + locators._hints, + "Button1.setColor()", + ); + + //For table widget + entityExplorer.DragDropWidgetNVerify(draggableWidgets.TABLE, 500, 300); + entityExplorer.SelectEntityByName("JSObject1"); + agHelper.GetNClick(jsEditor._lineinJsEditor(5)); + agHelper.RemoveCharsNType(locators._codeMirrorTextArea, 7, "Table1.set"); + + agHelper.GetElementsNAssertTextPresence(locators._hints, "setData()"); + }); + + it("2. Check if setters are present in autocomplete for widgets in property Pane", () => { + entityExplorer.DragDropWidgetNVerify(draggableWidgets.INPUT_V2, 200, 600); + + entityExplorer.SelectEntityByName("Button1"); + propPane.EnterJSContext("onClick", "{{Input1.set", true, false); + agHelper.GetElementsNAssertTextPresence(locators._hints, "setDisabled()"); + }); +}); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/Autocomplete_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/Autocomplete_spec.js index 42b96175db..159958ac92 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/Autocomplete_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/Autocomplete_spec.js @@ -61,13 +61,13 @@ describe("Autocomplete using slash command and mustache tests", function () { cy.get(dynamicInputLocators.hints).should("exist"); // validates all autocomplete functions on entering {{}} in onClick field cy.get(`${dynamicInputLocators.hints} li`) - .eq(1) + .eq(7) .should("have.text", "storeValue()"); cy.get(`${dynamicInputLocators.hints} li`) - .eq(2) + .eq(8) .should("have.text", "showAlert()"); cy.get(`${dynamicInputLocators.hints} li`) - .eq(3) + .eq(9) .should("have.text", "navigateTo()"); }); }); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/WidgetPropertySetters_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/WidgetPropertySetters_spec.ts new file mode 100644 index 0000000000..d5908aefc3 --- /dev/null +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/WidgetPropertySetters_spec.ts @@ -0,0 +1,185 @@ +import { + PROPERTY_SELECTOR, + WIDGET, + getWidgetSelector, +} from "../../../../locators/WidgetLocators"; + +import { + entityExplorer, + jsEditor, + agHelper, + locators, + propPane, +} from "../../../../support/Objects/ObjectsCore"; + +const setterMethodsToTest = [ + { + name: "setVisibility", + property: "isVisible", + widget: WIDGET.INPUT_V2, + actionBinding: "{{Input1.setVisibility(false)}}", + valueBinding: "{{Input1.isVisible}}", + expectedValue: "false", + }, + { + name: "setDisabled", + property: "isDisabled", + widget: WIDGET.INPUT_V2, + actionBinding: "{{Input1.setDisabled(false)}}", + valueBinding: "{{Input1.isDisabled}}", + expectedValue: "false", + }, + { + name: "setRequired", + property: "isRequired", + widget: WIDGET.INPUT_V2, + actionBinding: "{{Input1.setRequired(false)}}", + valueBinding: "{{Input1.isRequired}}", + expectedValue: "false", + }, + { + name: "setURL", + property: "url", + widget: WIDGET.AUDIO, + actionBinding: + "{{Audio1.setURL('https://www.youtube.com/watch?v=JGwWNGJdvx8')}}", + valueBinding: "{{Audio1.url}}", + expectedValue: "https://www.youtube.com/watch?v=JGwWNGJdvx8", + }, + { + name: "setPlaying", + property: "playing", + widget: WIDGET.AUDIO, + actionBinding: "{{Audio1.setPlaying(true)}}", + valueBinding: "{{Audio1.playing}}", + expectedValue: "true", + }, + { + name: "setSelectedRowIndex", + property: "selectedRowIndex", + widget: WIDGET.TABLE, + actionBinding: "{{Table1.setSelectedRowIndex(1)}}", + valueBinding: "{{Table1.selectedRowIndex}}", + expectedValue: "1", + }, + { + name: "setSelectedOption", + property: "selectedOptionLabel", + widget: WIDGET.SELECT, + actionBinding: "{{Select1.setSelectedOption('BLUE')}}", + valueBinding: "{{Select1.selectedOptionLabel}}", + expectedValue: "Blue", + }, + { + name: "setProgress", + property: "progress", + widget: WIDGET.PROGRESS, + actionBinding: "{{Progress1.setProgress(50)}}", + valueBinding: "{{Progress1.progress}}", + expectedValue: "50", + }, + { + name: "setText", + property: "text", + widget: WIDGET.TEXT, + actionBinding: "{{Text1.setText('Hello World')}}", + valueBinding: "{{Text1.text}}", + expectedValue: "Hello World", + }, + { + name: "setValue", + property: "text", + widget: WIDGET.INPUT_V2, + actionBinding: "{{Input1.setValue('Hello World')}}", + valueBinding: "{{Input1.text}}", + expectedValue: "Hello World", + }, + { + name: "setData", + property: "tableData", + widget: WIDGET.TABLE, + actionBinding: "{{Table1.setData([{name: 'test'}])}}", + valueBinding: "{{JSON.stringify(Table1.tableData)}}", + expectedValue: '[{"name":"test"}]', + }, +]; + +Object.values(setterMethodsToTest).forEach( + ( + { actionBinding, expectedValue, name, property, valueBinding, widget }, + index, + ) => { + describe(`${index + 1}. ${name} method test`, () => { + it(`1. DragDrop widget & Label/Text widgets and Verify the updated value`, () => { + entityExplorer.DragDropWidgetNVerify(widget, 300, 200); + entityExplorer.DragDropWidgetNVerify(WIDGET.BUTTON, 700, 200); + + propPane.EnterJSContext( + PROPERTY_SELECTOR.onClickFieldName, + actionBinding, + ); + + entityExplorer.DragDropWidgetNVerify(WIDGET.TEXT, 700, 400); + + propPane.UpdatePropertyFieldValue( + PROPERTY_SELECTOR.TextFieldName, + valueBinding, + ); + + agHelper.GetNClick(getWidgetSelector(WIDGET.BUTTON)); + + agHelper.GetText(getWidgetSelector(WIDGET.TEXT)).then(($label) => { + expect($label).to.eq(expectedValue); + }); + }); + + afterEach("Delete all the widgets on canvas", () => { + agHelper.GetNClick(locators._widgetInCanvas(widget)); + agHelper.PressDelete(); + + agHelper.GetNClick(getWidgetSelector(WIDGET.BUTTON)); + agHelper.AssertContains("is not defined"); // Since widget is removed & Button is still holding its reference + agHelper.PressDelete(); + + agHelper.GetNClick(getWidgetSelector(WIDGET.TEXT)).click(); + agHelper.GetNClick(propPane._deleteWidget); + }); + }); + }, +); + +describe("Linting warning for setter methods", function () { + it("Lint error when setter is used in a data field", function () { + entityExplorer.DragDropWidgetNVerify(WIDGET.BUTTON, 200, 200); + agHelper.GetNClick(getWidgetSelector(WIDGET.BUTTON)); + propPane.TypeTextIntoField("Label", "{{Button1.setLabel('Hello')}}"); + + //Mouse hover to exact warning message + agHelper.HoverElement(locators._lintErrorElement); + agHelper.AssertContains("Data fields cannot execute async code"); + + //Create a JS object + jsEditor.CreateJSObject( + `export default { + myFun1: () => { + Button1.setLabel('Hello'); + }, + }`, + { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + prettify: false, + }, + ); + + //Add myFun1 to onClick + entityExplorer.SelectEntityByName("Button1"); + propPane.TypeTextIntoField("Label", "{{JSObject1.myFun1()}}"); + + agHelper.AssertContains( + "Found an action invocation during evaluation. Data fields cannot execute actions.", + ); + }); +}); diff --git a/app/client/cypress/locators/WidgetLocators.ts b/app/client/cypress/locators/WidgetLocators.ts index 1139deaa68..7b39962bbb 100644 --- a/app/client/cypress/locators/WidgetLocators.ts +++ b/app/client/cypress/locators/WidgetLocators.ts @@ -35,6 +35,7 @@ export const WIDGET = { RANGE_SLIDER: "rangesliderwidget", IFRAME: "iframewidget", DIVIDER: "dividerwidget", + PROGRESS: "progresswidget", MODAL: "modalwidget", FORM: "formwidget", ICONBUTTON: "iconbuttonwidget", diff --git a/app/client/src/ce/workers/Evaluation/Actions.ts b/app/client/src/ce/workers/Evaluation/Actions.ts index f2a93c77dd..8fbf26d415 100644 --- a/app/client/src/ce/workers/Evaluation/Actions.ts +++ b/app/client/src/ce/workers/Evaluation/Actions.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import set from "lodash/set"; -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import type { EvalContext } from "workers/Evaluation/evaluate"; import type { EvaluationVersion } from "@appsmith/api/ApplicationApi"; import { addFn } from "workers/Evaluation/fns/utils/fnGuard"; @@ -11,6 +11,8 @@ import { } from "@appsmith/workers/Evaluation/fns"; import { getEntityForEvalContext } from "workers/Evaluation/getEntityForContext"; import { klona } from "klona/full"; +import { isEmpty } from "lodash"; +import setters from "workers/Evaluation/setters"; declare global { /** All identifiers added to the worker global scope should also * be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in @@ -38,8 +40,10 @@ export const addDataTreeToContext = (args: { dataTree: Readonly; removeEntityFunctions?: boolean; isTriggerBased: boolean; + configTree: ConfigTree; }) => { const { + configTree, dataTree, EVAL_CONTEXT, isTriggerBased, @@ -48,15 +52,39 @@ export const addDataTreeToContext = (args: { const dataTreeEntries = Object.entries(dataTree); const entityFunctionCollection: Record> = {}; + if (isTriggerBased && !removeEntityFunctions) setters.clear(); + for (const [entityName, entity] of dataTreeEntries) { EVAL_CONTEXT[entityName] = getEntityForEvalContext(entity, entityName); - if (!removeEntityFunctions && !isTriggerBased) continue; + + // when we evaluate data field and removeEntityFunctions is true then we skip adding entity function to evalContext + const skipEntityFunctions = !removeEntityFunctions && !isTriggerBased; + + if (skipEntityFunctions) continue; + for (const entityFn of entityFns) { if (!entityFn.qualifier(entity)) continue; const func = entityFn.fn(entity, entityName); const fullPath = `${entityFn.path || `${entityName}.${entityFn.name}`}`; set(entityFunctionCollection, fullPath, func); } + + // Don't add entity function ( setter method ) to evalContext if removeEntityFunctions is true + if (removeEntityFunctions) continue; + + const entityConfig = configTree[entityName]; + const entityMethodMap = setters.getEntitySettersFromConfig( + entityConfig, + entityName, + entity, + ); + + if (isEmpty(entityMethodMap)) continue; + EVAL_CONTEXT[entityName] = Object.assign( + {}, + dataTree[entityName], + entityMethodMap, + ); } if (removeEntityFunctions) @@ -71,7 +99,11 @@ export const addDataTreeToContext = (args: { for (const [entityName, funcObj] of Object.entries( entityFunctionCollection, )) { - EVAL_CONTEXT[entityName] = Object.assign({}, dataTree[entityName], funcObj); + EVAL_CONTEXT[entityName] = Object.assign( + {}, + EVAL_CONTEXT[entityName], + funcObj, + ); } }; @@ -81,7 +113,10 @@ export const addPlatformFunctionsToEvalContext = (context: any) => { } }; -export const getAllAsyncFunctions = (dataTree: DataTree) => { +export const getAllAsyncFunctions = ( + dataTree: DataTree, + configTree: ConfigTree, +) => { const asyncFunctionNameMap: Record = {}; const dataTreeEntries = Object.entries(dataTree); for (const [entityName, entity] of dataTreeEntries) { @@ -90,6 +125,19 @@ export const getAllAsyncFunctions = (dataTree: DataTree) => { const fullPath = `${entityFn.path || `${entityName}.${entityFn.name}`}`; asyncFunctionNameMap[fullPath] = true; } + + const entityConfig = configTree[entityName]; + const entityMethodMap = setters.getEntitySettersFromConfig( + entityConfig, + entityName, + entity, + ); + + if (isEmpty(entityMethodMap)) continue; + + for (const methodName of Object.keys(entityMethodMap)) { + asyncFunctionNameMap[`${entityName}.${methodName}`] = true; + } } for (const platformFn of getPlatformFunctions(self.$cloudHosting)) { asyncFunctionNameMap[platformFn.name] = true; diff --git a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts index 490168201d..7486c95a07 100644 --- a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts +++ b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts @@ -783,6 +783,8 @@ export const overrideWidgetProperties = (params: { evalMetaUpdates: EvalMetaUpdates; fullPropertyPath: string; isNewWidget: boolean; + shouldUpdateGlobalContext?: boolean; + overriddenProperties?: string[]; }) => { const { configTree, @@ -791,7 +793,9 @@ export const overrideWidgetProperties = (params: { evalMetaUpdates, fullPropertyPath, isNewWidget, + overriddenProperties, propertyPath, + shouldUpdateGlobalContext, value, } = params; const clonedValue = klona(value); @@ -813,9 +817,14 @@ export const overrideWidgetProperties = (params: { if (pathsNotToOverride.includes(overriddenPropertyPath)) return; _.set( currentTree, - [entity.widgetName, ...overriddenPropertyPathArray], + [entityName, ...overriddenPropertyPathArray], clonedValue, ); + + if (shouldUpdateGlobalContext) { + _.set(self, [entityName, ...overriddenPropertyPathArray], clonedValue); + } + overriddenProperties?.push(overriddenPropertyPath); // evalMetaUpdates has all updates from property which overrides meta values. if ( propertyPath.split(".")[0] !== "meta" && @@ -844,10 +853,14 @@ export const overrideWidgetProperties = (params: { const propertyPathArray = propertyPath.split("."); _.set( currentTree, - [entity.widgetName, ...propertyPathArray], + [entityName, ...propertyPathArray], clonedDefaultValue, ); + if (shouldUpdateGlobalContext) { + _.set(self, [entityName, ...propertyPathArray], clonedDefaultValue); + } + return { overwriteParsedValue: true, newValue: clonedDefaultValue, diff --git a/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx b/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx index d3e042afd2..bfe66d389d 100644 --- a/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/PeekOverlayPopup/PeekOverlayPopup.tsx @@ -9,7 +9,7 @@ import { zIndexLayers } from "constants/CanvasEditorConstants"; import { objectCollapseAnalytics, textSelectAnalytics } from "./Analytics"; import { Divider } from "design-system"; import { useSelector } from "react-redux"; -import { getDataTree } from "selectors/dataTreeSelectors"; +import { getConfigTree, getDataTree } from "selectors/dataTreeSelectors"; import { filterInternalProperties } from "utils/FilterInternalProperties"; import { getJSCollections } from "selectors/entitiesSelector"; @@ -54,6 +54,7 @@ export function PeekOverlayPopUpContent( const CONTAINER_MAX_HEIGHT_PX = 252; const dataWrapperRef: MutableRefObject = useRef(null); const dataTree = useSelector(getDataTree); + const configTree = useSelector(getConfigTree); const jsActions = useSelector(getJSCollections); const filteredData = filterInternalProperties( @@ -61,6 +62,7 @@ export function PeekOverlayPopUpContent( dataTree[props.objectName], jsActions, dataTree, + configTree, ); // Because getPropertyData can return a function diff --git a/app/client/src/entities/AppTheming/index.ts b/app/client/src/entities/AppTheming/index.ts index 31727a53bd..edb45b45e4 100644 --- a/app/client/src/entities/AppTheming/index.ts +++ b/app/client/src/entities/AppTheming/index.ts @@ -75,3 +75,13 @@ export type AppTheme = { }; }; }; + +export type SetterConfig = { + __setters: { + [key: string]: { + path: string; + type: string; + disabled?: string; + }; + }; +}; diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index cf8353cce6..26f308f355 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -57,6 +57,7 @@ export interface WidgetEntityConfig WidgetConfig { defaultMetaProps: Array; type: string; + __setters?: Record; } export interface AppsmithEntity extends Omit { diff --git a/app/client/src/entities/DataTree/dataTreeWidget.test.ts b/app/client/src/entities/DataTree/dataTreeWidget.test.ts index a026cd570e..f4e644c662 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.test.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.test.ts @@ -1,5 +1,8 @@ import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; -import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; +import { + generateDataTreeWidget, + getSetterConfig, +} from "entities/DataTree/dataTreeWidget"; import { ENTITY_TYPE, EvaluationSubstitutionType, @@ -305,4 +308,310 @@ describe("generateDataTreeWidget", () => { expect(result.unEvalEntity).toStrictEqual(expectedData); expect(result.configEntity).toStrictEqual(expectedConfig); }); + + it("generates setterConfig with the dynamic data", () => { + // Input widget + const inputWidget: FlattenedWidgetProps = { + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: "INPUT_WIDGET_V2", + version: 0, + widgetId: "123", + widgetName: "Input1", + defaultText: "", + deepObj: { + level1: { + value: 10, + }, + }, + }; + + const inputSetterConfig: Record = { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultText", + type: "string", + }, + }, + }; + + const expectedInputData = { + __setters: { + setVisibility: { + path: "Input1.isVisible", + type: "boolean", + }, + setDisabled: { + path: "Input1.isDisabled", + type: "boolean", + }, + setRequired: { + path: "Input1.isRequired", + type: "boolean", + }, + setValue: { + path: "Input1.defaultText", + type: "string", + }, + }, + }; + + const inputResult = getSetterConfig(inputSetterConfig, inputWidget); + + expect(inputResult).toStrictEqual(expectedInputData); + + //Json form widget + + const jsonFormWidget: FlattenedWidgetProps = { + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: "FORM_WIDGET", + version: 0, + widgetId: "123", + widgetName: "Form1", + defaultText: "", + deepObj: { + level1: { + value: 10, + }, + }, + }; + + const jsonFormSetterConfig: Record = { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setData: { + path: "sourceData", + type: "object", + }, + }, + }; + + const expectedJsonFormData = { + __setters: { + setVisibility: { + path: "Form1.isVisible", + type: "boolean", + }, + setData: { + path: "Form1.sourceData", + type: "object", + }, + }, + }; + + const jsonFormResult = getSetterConfig( + jsonFormSetterConfig, + jsonFormWidget, + ); + + expect(jsonFormResult).toStrictEqual(expectedJsonFormData); + + // Table widget + const tableWidget: FlattenedWidgetProps = { + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: "TABLE_WIDGET", + version: 0, + widgetId: "123", + widgetName: "Table1", + defaultText: "", + deepObj: { + level1: { + value: 10, + }, + }, + primaryColumns: { + step: { + index: 0, + width: 150, + id: "step", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "step", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.step))}}", + }, + task: { + index: 1, + width: 150, + id: "task", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "task", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.task))}}", + }, + status: { + index: 2, + width: 150, + id: "status", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "text", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDerived: false, + label: "status", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.status))}}", + }, + action: { + index: 3, + width: 150, + id: "action", + horizontalAlignment: "LEFT", + verticalAlignment: "CENTER", + columnType: "button", + textSize: "PARAGRAPH", + enableFilter: true, + enableSort: true, + isVisible: true, + isCellVisible: true, + isDisabled: false, + isDerived: false, + label: "action", + onClick: + "{{currentRow.step === '#1' ? showAlert('Done', 'success') : currentRow.step === '#2' ? navigateTo('https://docs.appsmith.com/core-concepts/connecting-to-data-sources/querying-a-database',undefined,'NEW_WINDOW') : navigateTo('https://docs.appsmith.com/core-concepts/displaying-data-read/display-data-tables',undefined,'NEW_WINDOW')}}", + computedValue: + "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.action))}}", + }, + }, + }; + + const tableSetterConfig: Record = { + __setters: { + setVisibility: { + path: "isVisible", + type: "string", + }, + setSelectedRowIndex: { + path: "defaultSelectedRowIndex", + type: "number", + disabled: "return options.entity.multiRowSelection", + }, + setSelectedRowIndices: { + path: "defaultSelectedRowIndices", + type: "array", + disabled: "return !options.entity.multiRowSelection", + }, + setData: { + path: "tableData", + type: "object", + }, + }, + text: { + __setters: { + setIsRequired: { + path: "primaryColumns.$columnId.isRequired", + type: "boolean", + }, + }, + }, + button: { + __setters: { + setIsRequired: { + path: "primaryColumns.$columnId.isRequired", + type: "boolean", + }, + }, + }, + pathToSetters: [ + { path: "primaryColumns.$columnId", property: "columnType" }, + ], + }; + + const expectedTableData = { + __setters: { + setVisibility: { + path: "Table1.isVisible", + type: "string", + }, + setSelectedRowIndex: { + path: "Table1.defaultSelectedRowIndex", + type: "number", + disabled: "return options.entity.multiRowSelection", + }, + setSelectedRowIndices: { + path: "Table1.defaultSelectedRowIndices", + type: "array", + disabled: "return !options.entity.multiRowSelection", + }, + setData: { + path: "Table1.tableData", + type: "object", + }, + "primaryColumns.action.setIsRequired": { + path: "Table1.primaryColumns.action.isRequired", + type: "boolean", + }, + "primaryColumns.status.setIsRequired": { + path: "Table1.primaryColumns.status.isRequired", + type: "boolean", + }, + "primaryColumns.step.setIsRequired": { + path: "Table1.primaryColumns.step.isRequired", + type: "boolean", + }, + "primaryColumns.task.setIsRequired": { + path: "Table1.primaryColumns.task.isRequired", + type: "boolean", + }, + }, + }; + + const tableResult = getSetterConfig(tableSetterConfig, tableWidget); + + expect(tableResult).toStrictEqual(expectedTableData); + }); }); diff --git a/app/client/src/entities/DataTree/dataTreeWidget.ts b/app/client/src/entities/DataTree/dataTreeWidget.ts index ef0aca0ae2..24f8babf72 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.ts @@ -1,5 +1,5 @@ import { getAllPathsFromPropertyConfig } from "entities/Widget/utils"; -import _, { isEmpty } from "lodash"; +import _, { get, isEmpty } from "lodash"; import memoize from "micro-memoize"; import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; import type { DynamicPath } from "utils/DynamicBindingUtils"; @@ -14,6 +14,140 @@ import type { import { OverridingPropertyType } from "./types"; import { setOverridingProperty } from "./utils"; +import { error } from "loglevel"; + +/** + * + * Example of setterConfig + * + * { + WIDGET: { + TABLE_WIDGET_V2: { + __setters: { + setIsRequired: { + path: "isRequired" + }, + }, + "text": { + __setters:{ + setIsRequired: { + path: "primaryColumns.$columnId.isRequired" + } + } + } + pathToSetters: [{ path: "primaryColumns.$columnId", property: "columnType" }] + } + } + } + + columnId = action + + Expected output + + { + Table2: { + isRequired: true, + __setters: { + setIsRequired: { + path: "Table2.isRequired" + }, + "primaryColumns.action.setIsRequired": { + path: "Table2.primaryColumns.action.isRequired" + } + }, + } + } + */ + +export function getSetterConfig( + setterConfig: Record, + widget: FlattenedWidgetProps, +) { + const modifiedSetterConfig: Record = {}; + + try { + if (setterConfig.__setters) { + modifiedSetterConfig.__setters = {}; + for (const setterMethodName of Object.keys(setterConfig.__setters)) { + const staticConfigSetter = setterConfig.__setters[setterMethodName]; + + modifiedSetterConfig.__setters[setterMethodName] = { + path: `${widget.widgetName}.${staticConfigSetter.path}`, + type: staticConfigSetter.type, + }; + + if (staticConfigSetter.disabled) { + modifiedSetterConfig.__setters[setterMethodName].disabled = + staticConfigSetter.disabled; + } + } + } + + if (!setterConfig.pathToSetters || !setterConfig.pathToSetters.length) + return modifiedSetterConfig; + + const pathToSetters = setterConfig.pathToSetters; + + //pathToSetters = [{ path: "primaryColumns.$columnId", property: "columnType" }] + for (const { path, property } of pathToSetters) { + const pathArray = path.split("."); + const placeHolder = pathArray[pathArray.length - 1]; + + if (placeHolder[0] !== "$") continue; + + //pathToParentObj = primaryColumns + const pathToParentObj = pathArray.slice(0, -1).join("."); + const accessors = Object.keys(get(widget, pathToParentObj)); + + //accessors = action, step, status, task + for (const accesskey of accessors) { + const fullPath = pathToParentObj + "." + accesskey; + const accessorObject = get(widget, fullPath); + + //propertyType = text, button etc + const propertyType = accessorObject[property]; + if (!propertyType) continue; + + // "text": { + // __setters:{ + // setIsRequired: { + // path: "primaryColumns.$columnId.isRequired" + // } + // } + // } + const accessorSetterConfig = setterConfig[propertyType]; + if (!accessorSetterConfig) continue; + + const accessorSettersMap = accessorSetterConfig.__setters; + if (!accessorSettersMap) continue; + + const entries = Object.entries(accessorSettersMap) as [ + string, + Record, + ][]; + + for (const [setterName, setterBody] of entries) { + //path = primaryColumns.action.isRequired + const path = (setterBody as any).path.replace(placeHolder, accesskey); + const setterPathArray = path.split("."); + setterPathArray.pop(); + setterPathArray.push(setterName); + + //setterPath = primaryColumns.action.setIsRequired + const setterPath = setterPathArray.join("."); + modifiedSetterConfig.__setters[setterPath] = { + path: `${widget.widgetName}.${path}`, //Table2.primaryColumns.action.isRequired + type: setterBody.type, + }; + } + } + } + } catch (e) { + error("Error while generating setter config", e); + } + + return modifiedSetterConfig; +} // We are splitting generateDataTreeWidget into two parts to memoize better as the widget doesn't change very often. // Widget changes only when dynamicBindingPathList changes. @@ -139,6 +273,11 @@ const generateDataTreeWidgetWithoutMeta = ( "type", ]; + const setterConfig = getSetterConfig( + WidgetFactory.getWidgetSetterConfig(widget.type), + widget, + ); + const dataTreeWidgetWithoutMetaProps = _.merge( { ENTITY_TYPE: ENTITY_TYPE.WIDGET, @@ -181,6 +320,7 @@ const generateDataTreeWidgetWithoutMeta = ( overridingPropertyPaths, type: widget.type, ...dynamicPathsList, + ...setterConfig, }, }; }; @@ -208,6 +348,7 @@ export const generateDataTreeWidget = ( // overridingMetaProps maps properties that can be overriden by either default values or meta changes to initial values. // initial value is set to metaProps value or defaultMetaProps value. + Object.entries(defaultMetaProps).forEach(([key, value]) => { if (overridingMetaPropsMap[key]) { overridingMetaProps[key] = diff --git a/app/client/src/entities/DataTree/types.ts b/app/client/src/entities/DataTree/types.ts index 0fd64a8006..bcbc0041b6 100644 --- a/app/client/src/entities/DataTree/types.ts +++ b/app/client/src/entities/DataTree/types.ts @@ -39,7 +39,7 @@ export interface ActionEntity { datasourceUrl: string; } -export interface ActionEntityConfig { +export interface ActionEntityConfig extends EntityConfig { dynamicBindingPathList: DynamicPath[]; bindingPaths: Record; reactivePaths: Record; @@ -60,7 +60,7 @@ export interface MetaArgs { confirmBeforeExecute: boolean; } -export interface JSActionEntityConfig { +export interface JSActionEntityConfig extends EntityConfig { meta: Record; dynamicBindingPathList: DynamicPath[]; bindingPaths: Record; @@ -109,7 +109,7 @@ export type PropertyOverrideDependency = Record< Partial >; -export type WidgetConfig = { +export interface WidgetConfig extends EntityConfig { bindingPaths: Record; reactivePaths: Record; triggerPaths: Record; @@ -119,4 +119,12 @@ export type WidgetConfig = { propertyOverrideDependency: PropertyOverrideDependency; overridingPropertyPaths: OverridingPropertyPaths; privateWidgets: PrivateWidgets; -}; +} + +export interface EntityConfig { + __setters?: Record; + bindingPaths?: Record; + reactivePaths?: Record; + validationPaths?: Record; + dynamicBindingPathList?: DynamicPath[]; +} diff --git a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx index 0122fb2580..5fc7abd227 100644 --- a/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx +++ b/app/client/src/pages/Editor/Explorer/Entity/EntityProperties.tsx @@ -154,6 +154,7 @@ export function EntityProperties() { break; case ENTITY_TYPE.ACTION: config = (entityDefinitions.ACTION as any)(entity as any); + if (config) { entityProperties = Object.keys(config) .filter((k) => k.indexOf("!") === -1) @@ -200,9 +201,12 @@ export function EntityProperties() { } if (isFunction(config)) config = config(entity); + const settersConfig = + WidgetFactory.getWidgetSetterConfig(type)?.__setters; entityProperties = Object.keys(config) .filter((k) => k.indexOf("!") === -1) + .filter((k) => settersConfig && !settersConfig[k]) .map((widgetProperty) => { return { propertyName: widgetProperty, diff --git a/app/client/src/plugins/Linting/constants.ts b/app/client/src/plugins/Linting/constants.ts index 0f02be7cb9..25d75990fa 100644 --- a/app/client/src/plugins/Linting/constants.ts +++ b/app/client/src/plugins/Linting/constants.ts @@ -74,7 +74,7 @@ export const CUSTOM_LINT_ERRORS: Record< entity: unknown, isJsObject: boolean, ) => - isEntityFunction(entity, propertyName) + isEntityFunction(entity, propertyName, entityName) ? asyncActionInSyncFieldLintMessage(isJsObject) : `"${propertyName}" doesn't exist in ${entityName}`, diff --git a/app/client/src/plugins/Linting/globalData.ts b/app/client/src/plugins/Linting/globalData.ts index 496a935497..ab13387d7a 100644 --- a/app/client/src/plugins/Linting/globalData.ts +++ b/app/client/src/plugins/Linting/globalData.ts @@ -1,18 +1,24 @@ -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import { isEmpty } from "lodash"; import type { EvalContext } from "workers/Evaluation/evaluate"; -import getEvaluationContext from "./utils/getEvaluationContext"; +import { getEvaluationContext } from "./utils/getEvaluationContext"; class GlobalData { globalDataWithFunctions: EvalContext = {}; globalDataWithoutFunctions: EvalContext = {}; unevalTree: DataTree = {}; + configTree: ConfigTree = {}; cloudHosting = false; - initialize(unevalTree: DataTree, cloudHosting: boolean) { + initialize( + unevalTree: DataTree, + configTree: ConfigTree, + cloudHosting: boolean, + ) { this.globalDataWithFunctions = {}; this.globalDataWithoutFunctions = {}; this.unevalTree = unevalTree; + this.configTree = configTree; this.cloudHosting = cloudHosting; } @@ -22,6 +28,7 @@ class GlobalData { if (isEmpty(this.globalDataWithFunctions)) { this.globalDataWithFunctions = getEvaluationContext( this.unevalTree, + this.configTree, this.cloudHosting, { withFunctions: true, @@ -33,6 +40,7 @@ class GlobalData { if (isEmpty(this.globalDataWithoutFunctions)) { this.globalDataWithoutFunctions = getEvaluationContext( this.unevalTree, + this.configTree, this.cloudHosting, { withFunctions: false, diff --git a/app/client/src/plugins/Linting/lintTree.ts b/app/client/src/plugins/Linting/lintTree.ts index 3240ba8a24..3f8673e3cc 100644 --- a/app/client/src/plugins/Linting/lintTree.ts +++ b/app/client/src/plugins/Linting/lintTree.ts @@ -8,6 +8,7 @@ import lintTriggerPath from "./utils/lintTriggerPath"; import lintJSObjectBody from "./utils/lintJSObjectBody"; import sortLintingPathsByType from "./utils/sortLintingPathsByType"; import lintJSObjectProperty from "./utils/lintJSObjectProperty"; +import setters from "workers/Evaluation/setters"; import type { getLintErrorsFromTreeProps, getLintErrorsFromTreeResponse, @@ -23,7 +24,10 @@ export function getLintErrorsFromTree({ }: getLintErrorsFromTreeProps): getLintErrorsFromTreeResponse { const lintTreeErrors: LintErrorsStore = {}; const lintedJSPaths = new Set(); - globalData.initialize(unEvalTree, cloudHosting); + + setters.init(configTree, unEvalTree); + globalData.initialize(unEvalTree, configTree, cloudHosting); + const { bindingPaths, jsObjectPaths, triggerPaths } = sortLintingPathsByType( pathsToLint, unEvalTree, diff --git a/app/client/src/plugins/Linting/utils/getEvaluationContext.ts b/app/client/src/plugins/Linting/utils/getEvaluationContext.ts index d1eb0bacb3..96118e1033 100644 --- a/app/client/src/plugins/Linting/utils/getEvaluationContext.ts +++ b/app/client/src/plugins/Linting/utils/getEvaluationContext.ts @@ -1,22 +1,25 @@ -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import { createEvaluationContext } from "workers/Evaluation/evaluate"; import { getActionTriggerFunctionNames } from "@appsmith/workers/Evaluation/fns"; -export default function getEvaluationContext( +export function getEvaluationContext( unevalTree: DataTree, + configTree: ConfigTree, cloudHosting: boolean, options: { withFunctions: boolean }, ) { if (!options.withFunctions) return createEvaluationContext({ dataTree: unevalTree, + configTree, isTriggerBased: false, removeEntityFunctions: true, }); const evalContext = createEvaluationContext({ dataTree: unevalTree, - isTriggerBased: false, + configTree, + isTriggerBased: true, removeEntityFunctions: false, }); diff --git a/app/client/src/plugins/Linting/utils/isEntityFunction.ts b/app/client/src/plugins/Linting/utils/isEntityFunction.ts index 01a3dea124..c36edbbfa1 100644 --- a/app/client/src/plugins/Linting/utils/isEntityFunction.ts +++ b/app/client/src/plugins/Linting/utils/isEntityFunction.ts @@ -1,12 +1,17 @@ import type { DataTreeEntity } from "entities/DataTree/dataTreeFactory"; import { isDataTreeEntity } from "@appsmith/workers/Evaluation/evaluationUtils"; import { entityFns } from "@appsmith/workers/Evaluation/fns"; +import setters from "workers/Evaluation/setters"; export default function isEntityFunction( entity: unknown, propertyName: string, + entityName: string, ) { if (!isDataTreeEntity(entity)) return false; + + if (setters.has(entityName, propertyName)) return true; + return entityFns.find((entityFn) => { const entityFnpropertyName = entityFn.path ? entityFn.path.split(".")[1] diff --git a/app/client/src/sagas/EvalWorkerActionSagas.ts b/app/client/src/sagas/EvalWorkerActionSagas.ts index afd84422ec..e001e4337d 100644 --- a/app/client/src/sagas/EvalWorkerActionSagas.ts +++ b/app/client/src/sagas/EvalWorkerActionSagas.ts @@ -172,6 +172,7 @@ export function* handleEvalWorkerMessage(message: TMessage) { }); break; } + case MAIN_THREAD_ACTION.PROCESS_JS_VAR_MUTATION_EVENTS: { const jsVarMutatedEvents: JSVarMutatedEvents = data; yield call(logJSVarMutationEvent, jsVarMutatedEvents); diff --git a/app/client/src/utils/FilterInternalProperties/Widget.ts b/app/client/src/utils/FilterInternalProperties/Widget.ts index 4c67bd24fb..d469135dbb 100644 --- a/app/client/src/utils/FilterInternalProperties/Widget.ts +++ b/app/client/src/utils/FilterInternalProperties/Widget.ts @@ -1,15 +1,25 @@ +import type { + ConfigTree, + DataTree, + WidgetEntity, + WidgetEntityConfig, +} from "entities/DataTree/dataTreeFactory"; import type { EntityDefinitionsOptions } from "@appsmith/utils/autocomplete/EntityDefinitions"; -import type { DataTree, WidgetEntity } from "entities/DataTree/dataTreeFactory"; import { isFunction } from "lodash"; +import type { Def } from "tern"; import WidgetFactory from "utils/WidgetFactory"; +import { addSettersToDefinitions } from "utils/autocomplete/dataTreeTypeDefCreator"; export const getWidgetChildrenPeekData = ( widgetName: string, widgetType: string, dataTree: DataTree, + configTree: ConfigTree, ) => { const peekData: Record = {}; const dataTreeWidget: WidgetEntity = dataTree[widgetName] as WidgetEntity; + const widgetConfig = configTree[widgetName]; + if (widgetType !== "FORM_WIDGET" && dataTreeWidget) { const type: Exclude< EntityDefinitionsOptions, @@ -21,12 +31,32 @@ export const getWidgetChildrenPeekData = ( let config: any = WidgetFactory.getAutocompleteDefinitions(type); if (config) { if (isFunction(config)) config = config(dataTreeWidget); + + // Need to add this in order to add the setters to the definitions which will appear in the peekOverlay + addSettersToDefinitions( + config as Def, + dataTreeWidget, + configTree[widgetName] as WidgetEntityConfig, + ); + const widgetProps = Object.keys(config).filter( (k) => k.indexOf("!") === -1, ); + widgetProps.forEach((prop) => { const data = dataTreeWidget[prop]; - peekData[prop] = data; + + let setterNames: string[] = []; + + if (widgetConfig.__setters) { + setterNames = Object.keys(widgetConfig.__setters); + } + if (setterNames.includes(prop)) { + // eslint-disable-next-line @typescript-eslint/no-empty-function + peekData[prop] = function () {}; // tern inference required here + } else { + peekData[prop] = data; + } }); } } diff --git a/app/client/src/utils/FilterInternalProperties/index.ts b/app/client/src/utils/FilterInternalProperties/index.ts index b070131df4..667e923df9 100644 --- a/app/client/src/utils/FilterInternalProperties/index.ts +++ b/app/client/src/utils/FilterInternalProperties/index.ts @@ -1,5 +1,6 @@ import { getActionChildrenPeekData } from "./Action"; import type { + ConfigTree, DataTree, DataTreeEntity, } from "entities/DataTree/dataTreeFactory"; @@ -21,6 +22,7 @@ export const filterInternalProperties = ( dataTreeEntity: DataTreeEntity, jsActions: JSCollectionDataState, dataTree: DataTree, + configTree: ConfigTree, ) => { if (!dataTreeEntity) return; if (isActionEntity(dataTreeEntity)) { @@ -35,8 +37,12 @@ export const filterInternalProperties = ( ? getJsActionPeekData(jsAction, dataTree)?.peekData : dataTreeEntity; } else if (isWidgetEntity(dataTreeEntity)) { - return getWidgetChildrenPeekData(objectName, dataTreeEntity.type, dataTree) - ?.peekData; + return getWidgetChildrenPeekData( + objectName, + dataTreeEntity.type, + dataTree, + configTree, + )?.peekData; } return dataTreeEntity; }; diff --git a/app/client/src/utils/WidgetFactory.tsx b/app/client/src/utils/WidgetFactory.tsx index 576fa0edae..95deb3feea 100644 --- a/app/client/src/utils/WidgetFactory.tsx +++ b/app/client/src/utils/WidgetFactory.tsx @@ -70,6 +70,7 @@ class WidgetFactory { static stylesheetConfigMap: Map = new Map(); static autocompleteDefinitions: Map = new Map(); + static setterConfig: Map> = new Map(); static widgetConfigMap: Map< WidgetType, @@ -99,6 +100,7 @@ class WidgetFactory { stylesheetConfig?: Stylesheet, autocompleteDefinitions?: AutocompletionDefinitions, autoLayoutConfig?: AutoLayoutConfig, + setterConfig?: Record, ) { if (!this.widgetTypes[widgetType]) { this.widgetTypes[widgetType] = widgetType; @@ -115,6 +117,7 @@ class WidgetFactory { this.stylesheetConfigMap.set(widgetType, stylesheetConfig); autocompleteDefinitions && this.autocompleteDefinitions.set(widgetType, autocompleteDefinitions); + setterConfig && this.setterConfig.set(widgetType, setterConfig); if (Array.isArray(propertyPaneConfig) && propertyPaneConfig.length > 0) { const enhancedPropertyPaneConfig = enhancePropertyPaneConfig( @@ -375,12 +378,22 @@ class WidgetFactory { type: WidgetType, ): AutocompletionDefinitions | undefined { const autocompleteDefinition = this.autocompleteDefinitions.get(type); + if (!autocompleteDefinition) { log.error("Widget autocomplete properties not defined: ", type); } return autocompleteDefinition; } + static getWidgetSetterConfig(type: WidgetType): Record { + const map = this.setterConfig.get(type); + + if (!map) { + return {}; + } + return map; + } + static getLoadingProperties(type: WidgetType): Array | undefined { return this.loadingProperties.get(type); } diff --git a/app/client/src/utils/WidgetRegisterHelpers.tsx b/app/client/src/utils/WidgetRegisterHelpers.tsx index 33910c4a8a..3f7c3259f9 100644 --- a/app/client/src/utils/WidgetRegisterHelpers.tsx +++ b/app/client/src/utils/WidgetRegisterHelpers.tsx @@ -66,6 +66,7 @@ export const registerWidget = ( config.properties.stylesheetConfig, config.properties.autocompleteDefinitions, config.autoLayout, + config.properties.setterConfig, ); configureWidget(config); diff --git a/app/client/src/utils/WorkerUtil.ts b/app/client/src/utils/WorkerUtil.ts index 62b34a32a4..e5d6d0bc0a 100644 --- a/app/client/src/utils/WorkerUtil.ts +++ b/app/client/src/utils/WorkerUtil.ts @@ -121,6 +121,7 @@ export class GracefulWorkerService { yield this.ready(true); if (!this._Worker) return; const messageType = MessageType.RESPONSE; + sendMessage.call(this._Worker, { body: { data, diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts index 8c7c4d232a..ab9df80f0f 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts @@ -1,4 +1,9 @@ -import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; +import type { + ConfigTree, + DataTree, + DataTreeEntity, + WidgetEntityConfig, +} from "entities/DataTree/dataTreeFactory"; import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import { uniqueId, isFunction, isObject } from "lodash"; import { entityDefinitions } from "@appsmith/utils/autocomplete/EntityDefinitions"; @@ -18,6 +23,7 @@ export type ExtraDef = Record; import type { JSActionEntityConfig } from "entities/DataTree/types"; import type { Variable } from "entities/JSCollection"; import WidgetFactory from "utils/WidgetFactory"; +import { shouldAddSetter } from "workers/Evaluation/evaluate"; // Def names are encoded with information about the entity // This so that we have more info about them @@ -45,12 +51,22 @@ export const dataTreeTypeDefCreator = ( WidgetFactory.getAutocompleteDefinitions(widgetType); if (autocompleteDefinitions) { + const entityConfig = configTree[entityName] as WidgetEntityConfig; + if (isFunction(autocompleteDefinitions)) { - def[entityName] = autocompleteDefinitions(entity, extraDefsToDefine); + def[entityName] = autocompleteDefinitions( + entity, + extraDefsToDefine, + entityConfig, + ); } else { def[entityName] = autocompleteDefinitions; } + + addSettersToDefinitions(def[entityName] as Def, entity, entityConfig); + flattenDef(def, entityName); + entityMap.set(entityName, { type: ENTITY_TYPE.WIDGET, subType: widgetType, @@ -218,3 +234,24 @@ export function generateJSFunctionTypeDef( data: generateTypeDef(jsData[fullFunctionName], extraDefs), }; } + +export function addSettersToDefinitions( + definitions: Def, + entity: DataTreeEntity, + entityConfig?: WidgetEntityConfig, +) { + if (entityConfig && entityConfig.__setters) { + const setters = Object.keys(entityConfig.__setters); + + setters.forEach((setterName: string) => { + const setter = entityConfig.__setters?.[setterName]; + const setterType = entityConfig.__setters?.[setterName].type; + + if (shouldAddSetter(setter, entity)) { + definitions[ + setterName + ] = `fn(value:${setterType}) -> +Promise[:t=[!0..:t]]`; + } + }); + } +} diff --git a/app/client/src/widgets/AudioRecorderWidget/index.ts b/app/client/src/widgets/AudioRecorderWidget/index.ts index 95cda64421..51079a3d1f 100644 --- a/app/client/src/widgets/AudioRecorderWidget/index.ts +++ b/app/client/src/widgets/AudioRecorderWidget/index.ts @@ -31,6 +31,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx index dda3c4ad89..2fa6d8b58a 100644 --- a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx +++ b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx @@ -3,7 +3,7 @@ import React from "react"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { createBlobUrl } from "utils/AppsmithUtils"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; @@ -236,6 +236,21 @@ class AudioRecorderWidget extends BaseWidget< } }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const { blobURL, diff --git a/app/client/src/widgets/AudioWidget/index.tsx b/app/client/src/widgets/AudioWidget/index.tsx index 18a04af96e..b25fc97a61 100644 --- a/app/client/src/widgets/AudioWidget/index.tsx +++ b/app/client/src/widgets/AudioWidget/index.tsx @@ -29,6 +29,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/AudioWidget/widget/index.tsx b/app/client/src/widgets/AudioWidget/widget/index.tsx index 3e314b5924..bf12b24d4e 100644 --- a/app/client/src/widgets/AudioWidget/widget/index.tsx +++ b/app/client/src/widgets/AudioWidget/widget/index.tsx @@ -11,6 +11,7 @@ import BaseWidget from "../../BaseWidget"; import type { AutocompletionDefinitions } from "widgets/constants"; import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants"; import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; +import type { SetterConfig } from "entities/AppTheming"; const AudioComponent = lazy(() => retryPromise(() => import("../component"))); @@ -32,6 +33,25 @@ class AudioWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setURL: { + path: "url", + type: "string", + }, + setPlaying: { + path: "autoPlay", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/ButtonGroupWidget/index.ts b/app/client/src/widgets/ButtonGroupWidget/index.ts index 9c7764190d..777f81705f 100644 --- a/app/client/src/widgets/ButtonGroupWidget/index.ts +++ b/app/client/src/widgets/ButtonGroupWidget/index.ts @@ -177,6 +177,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, }; diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx index 2f91b8597a..5429a8e64f 100644 --- a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx @@ -4,7 +4,7 @@ import type { ButtonPlacement, ButtonVariant } from "components/constants"; import { ButtonPlacementTypes, ButtonVariantTypes } from "components/constants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { get } from "lodash"; import React from "react"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; @@ -577,6 +577,21 @@ class ButtonGroupWidget extends BaseWidget< } }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const { componentWidth } = this.getComponentDimensions(); const minPopoverWidth = MinimumPopupRows * this.props.parentColumnSpace; diff --git a/app/client/src/widgets/ButtonWidget/index.ts b/app/client/src/widgets/ButtonWidget/index.ts index 956fa982aa..45d487f558 100644 --- a/app/client/src/widgets/ButtonWidget/index.ts +++ b/app/client/src/widgets/ButtonWidget/index.ts @@ -42,6 +42,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/ButtonWidget/widget/index.tsx b/app/client/src/widgets/ButtonWidget/widget/index.tsx index e1e43de78b..7c0efaff96 100644 --- a/app/client/src/widgets/ButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/ButtonWidget/widget/index.tsx @@ -14,7 +14,7 @@ import type { ExecutionResult } from "constants/AppsmithActionConstants/ActionCo import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import React from "react"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; @@ -444,6 +444,29 @@ class ButtonWidget extends BaseWidget { } }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setLabel: { + path: "text", + type: "string", + }, + setColor: { + path: "buttonColor", + type: "string", + }, + }, + }; + } + getPageView() { const disabled = this.props.disabledWhenInvalid && diff --git a/app/client/src/widgets/CameraWidget/index.ts b/app/client/src/widgets/CameraWidget/index.ts index b96841ee7f..7623becfb8 100644 --- a/app/client/src/widgets/CameraWidget/index.ts +++ b/app/client/src/widgets/CameraWidget/index.ts @@ -30,6 +30,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/CameraWidget/widget/index.tsx b/app/client/src/widgets/CameraWidget/widget/index.tsx index 0c9ee779c6..8554713cb6 100644 --- a/app/client/src/widgets/CameraWidget/widget/index.tsx +++ b/app/client/src/widgets/CameraWidget/widget/index.tsx @@ -9,7 +9,7 @@ import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; import { FileDataTypes } from "widgets/constants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import CameraComponent from "../component"; import type { CameraMode } from "../constants"; import { CameraModeTypes, MediaCaptureStatusTypes } from "../constants"; @@ -224,6 +224,21 @@ class CameraWidget extends BaseWidget { return "CAMERA_WIDGET"; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const { bottomRow, diff --git a/app/client/src/widgets/CategorySliderWidget/index.ts b/app/client/src/widgets/CategorySliderWidget/index.ts index 145b688932..782ffb6f60 100644 --- a/app/client/src/widgets/CategorySliderWidget/index.ts +++ b/app/client/src/widgets/CategorySliderWidget/index.ts @@ -46,6 +46,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/CategorySliderWidget/widget/index.tsx b/app/client/src/widgets/CategorySliderWidget/widget/index.tsx index 3598cea347..acf97eacde 100644 --- a/app/client/src/widgets/CategorySliderWidget/widget/index.tsx +++ b/app/client/src/widgets/CategorySliderWidget/widget/index.tsx @@ -8,7 +8,7 @@ import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; import type { SliderComponentProps } from "../../NumberSliderWidget/component/Slider"; import SliderComponent from "../../NumberSliderWidget/component/Slider"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -61,6 +61,25 @@ class CategorySliderWidget extends BaseWidget< }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setValue: { + path: "defaultOptionValue", + type: "number", + }, + }, + }; + } + componentDidUpdate(prevProps: CategorySliderWidgetProps) { /** * If you change the defaultOptionValue from the propertyPane diff --git a/app/client/src/widgets/CheckboxGroupWidget/index.ts b/app/client/src/widgets/CheckboxGroupWidget/index.ts index d7b69828ba..69ed7038a4 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/index.ts +++ b/app/client/src/widgets/CheckboxGroupWidget/index.ts @@ -46,6 +46,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx index 961b316bc6..cf4b11eb92 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx @@ -7,7 +7,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { TextSize, WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { compact, xor } from "lodash"; import { default as React } from "react"; @@ -529,6 +529,29 @@ class CheckboxGroupWidget extends BaseWidget< } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setSelectedOptions: { + path: "defaultSelectedValues", + type: "array", + }, + }, + }; + } + getPageView() { return ( { } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultCheckedState", + type: "boolean", + }, + }, + }; + } + getPageView() { return ( { } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultDate", + type: "string", + }, + }, + }; + } + getPageView() { return ( { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultDate", + type: "string", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/DividerWidget/index.ts b/app/client/src/widgets/DividerWidget/index.ts index c2eec7e78a..fab597e67b 100644 --- a/app/client/src/widgets/DividerWidget/index.ts +++ b/app/client/src/widgets/DividerWidget/index.ts @@ -33,6 +33,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/DividerWidget/widget/index.tsx b/app/client/src/widgets/DividerWidget/widget/index.tsx index 31297d9511..1a312bdac7 100644 --- a/app/client/src/widgets/DividerWidget/widget/index.tsx +++ b/app/client/src/widgets/DividerWidget/widget/index.tsx @@ -8,6 +8,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; import { isAutoLayout } from "utils/autoLayout/flexWidgetUtils"; +import type { SetterConfig } from "entities/AppTheming"; class DividerWidget extends BaseWidget { static getAutocompleteDefinitions(): AutocompletionDefinitions { @@ -24,6 +25,17 @@ class DividerWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/DocumentViewerWidget/index.ts b/app/client/src/widgets/DocumentViewerWidget/index.ts index b10852c1ba..12453f71fe 100644 --- a/app/client/src/widgets/DocumentViewerWidget/index.ts +++ b/app/client/src/widgets/DocumentViewerWidget/index.ts @@ -29,6 +29,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx b/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx index c411c90634..a39aa6f3f6 100644 --- a/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx +++ b/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx @@ -7,6 +7,7 @@ import BaseWidget from "widgets/BaseWidget"; import DocumentViewerComponent from "../component"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; +import type { SetterConfig } from "entities/AppTheming"; export function documentUrlValidation(value: unknown): ValidationResponse { // applied validations if value exist @@ -134,6 +135,21 @@ class DocumentViewerWidget extends BaseWidget< ]; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setURL: { + path: "docUrl", + type: "string", + }, + }, + }; + } + static getAutocompleteDefinitions(): AutocompletionDefinitions { return { "!doc": "Document viewer widget is used to show documents on a page", diff --git a/app/client/src/widgets/FilePickerWidgetV2/index.ts b/app/client/src/widgets/FilePickerWidgetV2/index.ts index fb935ad5d9..b2c069bdf7 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/index.ts +++ b/app/client/src/widgets/FilePickerWidgetV2/index.ts @@ -40,6 +40,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx index c0a1a9893b..3e23d80778 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx @@ -12,7 +12,7 @@ import { Colors } from "constants/Colors"; import type { WidgetType } from "constants/WidgetConstants"; import { FILE_SIZE_LIMIT_FOR_BLOBS } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { klona } from "klona"; import _, { findIndex } from "lodash"; @@ -822,6 +822,21 @@ class FilePickerWidget extends BaseWidget< this.state.uppy.close(); } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { return ( <> diff --git a/app/client/src/widgets/FilepickerWidget/index.ts b/app/client/src/widgets/FilepickerWidget/index.ts index 3904906adb..7c7c0cea78 100644 --- a/app/client/src/widgets/FilepickerWidget/index.ts +++ b/app/client/src/widgets/FilepickerWidget/index.ts @@ -32,6 +32,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, }; diff --git a/app/client/src/widgets/FilepickerWidget/widget/index.tsx b/app/client/src/widgets/FilepickerWidget/widget/index.tsx index 45289889ca..562eba1166 100644 --- a/app/client/src/widgets/FilepickerWidget/widget/index.tsx +++ b/app/client/src/widgets/FilepickerWidget/widget/index.tsx @@ -19,6 +19,7 @@ import log from "loglevel"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; +import type { SetterConfig } from "entities/AppTheming"; class FilePickerWidget extends BaseWidget< FilePickerWidgetProps, @@ -471,6 +472,21 @@ class FilePickerWidget extends BaseWidget< this.state.uppy.close(); } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { return ( { @@ -143,6 +144,17 @@ class FormWidget extends ContainerWidget { }); } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "string", + }, + }, + }; + } + static getDerivedPropertiesMap(): DerivedPropertiesMap { return { positioning: Positioning.Fixed }; } diff --git a/app/client/src/widgets/IconButtonWidget/index.ts b/app/client/src/widgets/IconButtonWidget/index.ts index 7e3aa01dc4..7477c33660 100644 --- a/app/client/src/widgets/IconButtonWidget/index.ts +++ b/app/client/src/widgets/IconButtonWidget/index.ts @@ -32,6 +32,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/IconButtonWidget/widget/index.tsx b/app/client/src/widgets/IconButtonWidget/widget/index.tsx index a615c2e9a8..1a84d1e7b0 100644 --- a/app/client/src/widgets/IconButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/IconButtonWidget/widget/index.tsx @@ -10,7 +10,7 @@ import BaseWidget from "widgets/BaseWidget"; import { IconNames } from "@blueprintjs/icons"; import type { ButtonVariant } from "components/constants"; import { ButtonVariantTypes } from "components/constants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import IconButtonComponent from "../component"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -214,6 +214,21 @@ class IconButtonWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const { borderRadius, diff --git a/app/client/src/widgets/IframeWidget/index.ts b/app/client/src/widgets/IframeWidget/index.ts index a673dd9e7d..d952a90287 100644 --- a/app/client/src/widgets/IframeWidget/index.ts +++ b/app/client/src/widgets/IframeWidget/index.ts @@ -34,6 +34,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/IframeWidget/widget/index.tsx b/app/client/src/widgets/IframeWidget/widget/index.tsx index de56ca326a..2a57ebce49 100644 --- a/app/client/src/widgets/IframeWidget/widget/index.tsx +++ b/app/client/src/widgets/IframeWidget/widget/index.tsx @@ -1,6 +1,6 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import React from "react"; import type { WidgetState } from "widgets/BaseWidget"; import BaseWidget from "widgets/BaseWidget"; @@ -22,6 +22,22 @@ class IframeWidget extends BaseWidget { messageMetadata: generateTypeDef(widget.messageMetadata), }); } + + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setURL: { + path: "source", + type: "string", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/ImageWidget/component/index.tsx b/app/client/src/widgets/ImageWidget/component/index.tsx index 23f72194eb..29e5d088f9 100644 --- a/app/client/src/widgets/ImageWidget/component/index.tsx +++ b/app/client/src/widgets/ImageWidget/component/index.tsx @@ -168,6 +168,7 @@ class ImageComponent extends React.Component< render() { const { imageUrl, maxZoomLevel } = this.props; + const { imageError, imageRotation } = this.state; const zoomActive = maxZoomLevel !== undefined && maxZoomLevel > 1 && !this.isPanning; diff --git a/app/client/src/widgets/ImageWidget/index.ts b/app/client/src/widgets/ImageWidget/index.ts index b3f426caeb..6c9654c591 100644 --- a/app/client/src/widgets/ImageWidget/index.ts +++ b/app/client/src/widgets/ImageWidget/index.ts @@ -29,6 +29,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/ImageWidget/widget/index.tsx b/app/client/src/widgets/ImageWidget/widget/index.tsx index 04e893062c..795a1e63c7 100644 --- a/app/client/src/widgets/ImageWidget/widget/index.tsx +++ b/app/client/src/widgets/ImageWidget/widget/index.tsx @@ -7,7 +7,7 @@ import ImageComponent from "../component"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -28,6 +28,21 @@ class ImageWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setImage: { + path: "image", + type: "string", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/InputWidget/index.ts b/app/client/src/widgets/InputWidget/index.ts index 971c3450d7..1ca9b80e89 100644 --- a/app/client/src/widgets/InputWidget/index.ts +++ b/app/client/src/widgets/InputWidget/index.ts @@ -38,6 +38,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, }; diff --git a/app/client/src/widgets/InputWidget/widget/index.tsx b/app/client/src/widgets/InputWidget/widget/index.tsx index 883e9df72a..99187effc8 100644 --- a/app/client/src/widgets/InputWidget/widget/index.tsx +++ b/app/client/src/widgets/InputWidget/widget/index.tsx @@ -29,7 +29,7 @@ import { getLocale, } from "../component/utilities"; import { LabelPosition } from "components/constants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { checkInputTypeTextByProps } from "widgets/BaseInputWidget/utils"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -127,7 +127,7 @@ class InputWidget extends BaseWidget { } static getAutocompleteDefinitions(): AutocompletionDefinitions { - return { + const definitions = { "!doc": "An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.", "!url": "https://docs.appsmith.com/widget-reference/input", @@ -148,6 +148,8 @@ class InputWidget extends BaseWidget { "!doc": "Selected country code for Currency type input", }, }; + + return definitions; } static getPropertyPaneConfig() { @@ -763,6 +765,29 @@ class InputWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultText", + type: "string", + }, + }, + }; + } + onValueChange = (value: string) => { this.props.updateWidgetMetaProperty("text", value, { triggerPropertyName: "onTextChanged", diff --git a/app/client/src/widgets/InputWidgetV2/index.ts b/app/client/src/widgets/InputWidgetV2/index.ts index f899cdf2b8..d67f93f220 100644 --- a/app/client/src/widgets/InputWidgetV2/index.ts +++ b/app/client/src/widgets/InputWidgetV2/index.ts @@ -41,6 +41,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/InputWidgetV2/widget/index.tsx b/app/client/src/widgets/InputWidgetV2/widget/index.tsx index 0fe5c82ca6..50e6b17143 100644 --- a/app/client/src/widgets/InputWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/InputWidgetV2/widget/index.tsx @@ -26,8 +26,8 @@ import { InputTypes, NumberInputStepButtonPosition, } from "widgets/BaseInputWidget/constants"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { getParsedText, isInputTypeEmailOrPassword } from "./Utilities"; -import type { Stylesheet } from "entities/AppTheming"; import { isAutoHeightEnabledForWidget, DefaultAutocompleteDefinitions, @@ -59,14 +59,20 @@ export function defaultValueValidation( } const { inputType } = props; + + if (_.isBoolean(value) || _.isNil(value) || _.isUndefined(value)) { + return { + isValid: false, + parsed: value, + messages: [STRING_ERROR_MESSAGE], + }; + } + let parsed; switch (inputType) { case "NUMBER": - if (_.isNil(value)) { - parsed = null; - } else { - parsed = Number(value); - } + parsed = Number(value); + let isValid, messages; if (_.isString(value) && value.trim() === "") { @@ -262,7 +268,7 @@ function InputTypeUpdateHook( class InputWidget extends BaseInputWidget { static getAutocompleteDefinitions(): AutocompletionDefinitions { - return { + const definitions: AutocompletionDefinitions = { "!doc": "An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.", "!url": "https://docs.appsmith.com/widget-reference/input", @@ -275,6 +281,8 @@ class InputWidget extends BaseInputWidget { isVisible: DefaultAutocompleteDefinitions.isVisible, isDisabled: "bool", }; + + return definitions; } static getPropertyPaneContentConfig() { return mergeWidgetConfig( @@ -590,6 +598,29 @@ class InputWidget extends BaseInputWidget { } }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultText", + type: "string", + }, + }, + }; + } + resetWidgetText = () => { this.props.updateWidgetMetaProperty("inputText", ""); this.props.updateWidgetMetaProperty( diff --git a/app/client/src/widgets/JSONFormWidget/index.ts b/app/client/src/widgets/JSONFormWidget/index.ts index c19e7f9818..6a00d0a192 100644 --- a/app/client/src/widgets/JSONFormWidget/index.ts +++ b/app/client/src/widgets/JSONFormWidget/index.ts @@ -97,6 +97,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/JSONFormWidget/widget/index.tsx b/app/client/src/widgets/JSONFormWidget/widget/index.tsx index f964561dea..21fcddb866 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/index.tsx +++ b/app/client/src/widgets/JSONFormWidget/widget/index.tsx @@ -24,6 +24,7 @@ import { convertSchemaItemToFormData } from "../helper"; import type { ButtonStyles, ChildStylesheet, + SetterConfig, Stylesheet, } from "entities/AppTheming"; import type { BatchPropertyUpdatePayload } from "actions/controlActions"; @@ -223,16 +224,35 @@ class JSONFormWidget extends BaseWidget< } static getAutocompleteDefinitions(): AutocompletionDefinitions { - return (widget: JSONFormWidgetProps) => ({ - "!doc": - "JSON Form widget can be used to auto-generate forms by providing a JSON source data.", - // TODO: Update the url - "!url": "https://docs.appsmith.com/widget-reference", - formData: generateTypeDef(widget.formData), - sourceData: generateTypeDef(widget.sourceData), - fieldState: generateTypeDef(widget.fieldState), - isValid: "bool", - }); + return (widget: JSONFormWidgetProps) => { + const definitions: AutocompletionDefinitions = { + "!doc": + "JSON Form widget can be used to auto-generate forms by providing a JSON source data.", + // TODO: Update the url + "!url": "https://docs.appsmith.com/widget-reference", + formData: generateTypeDef(widget.formData), + sourceData: generateTypeDef(widget.sourceData), + fieldState: generateTypeDef(widget.fieldState), + isValid: "bool", + }; + + return definitions; + }; + } + + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setSourceData: { + path: "sourceData", + type: "object", + }, + }, + }; } static defaultProps = {}; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts index 01615354ef..e8e1cf2869 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts @@ -33,6 +33,19 @@ export const sourceDataValidationFn = ( }; } + if (_.isNumber(value) || _.isBoolean(value)) { + return { + isValid: false, + parsed: {}, + messages: [ + { + name: "ValidationError", + message: `Source data cannot be ${value}`, + }, + ], + }; + } + if (_.isNil(value)) { return { isValid: true, diff --git a/app/client/src/widgets/ListWidget/index.ts b/app/client/src/widgets/ListWidget/index.ts index 3f834c7ca0..6d39e0a7fb 100644 --- a/app/client/src/widgets/ListWidget/index.ts +++ b/app/client/src/widgets/ListWidget/index.ts @@ -418,6 +418,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/ListWidget/widget/index.tsx b/app/client/src/widgets/ListWidget/widget/index.tsx index 9810bf4c64..a2cfe9b760 100644 --- a/app/client/src/widgets/ListWidget/widget/index.tsx +++ b/app/client/src/widgets/ListWidget/widget/index.tsx @@ -3,7 +3,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { GridDefaults, RenderModes } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import type { PrivateWidgets } from "entities/DataTree/types"; import equal from "fast-deep-equal/es6"; import { klona } from "klona/lite"; @@ -79,6 +79,18 @@ class ListWidget extends BaseWidget, WidgetState> { pageSize: generateTypeDef(widget.pageSize), }); } + + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return PropertyPaneContentConfig; } diff --git a/app/client/src/widgets/ListWidgetV2/index.ts b/app/client/src/widgets/ListWidgetV2/index.ts index 95286a91e7..5cf58099f0 100644 --- a/app/client/src/widgets/ListWidgetV2/index.ts +++ b/app/client/src/widgets/ListWidgetV2/index.ts @@ -477,6 +477,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/ListWidgetV2/widget/index.tsx b/app/client/src/widgets/ListWidgetV2/widget/index.tsx index 3bbac08fce..2af6fd8a2f 100644 --- a/app/client/src/widgets/ListWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/ListWidgetV2/widget/index.tsx @@ -34,7 +34,7 @@ import { RenderModes, WIDGET_PADDING } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { ModifyMetaWidgetPayload } from "reducers/entityReducers/metaWidgetsReducer"; import type { WidgetState } from "../../BaseWidget"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import type { TabContainerWidgetProps, TabsWidgetProps, @@ -159,34 +159,50 @@ class ListWidget extends BaseWidget< } static getAutocompleteDefinitions(): AutocompletionDefinitions { - return (widget: ListWidgetProps, extraDefsToDefine?: ExtraDef) => ({ - "!doc": - "Containers are used to group widgets together to form logical higher order widgets. Containers let you organize your page better and move all the widgets inside them together.", - "!url": "https://docs.appsmith.com/widget-reference/list", - backgroundColor: { - "!type": "string", - "!url": "https://docs.appsmith.com/widget-reference/how-to-use-widgets", + return (widget: ListWidgetProps, extraDefsToDefine?: ExtraDef) => { + const obj = { + "!doc": + "Containers are used to group widgets together to form logical higher order widgets. Containers let you organize your page better and move all the widgets inside them together.", + "!url": "https://docs.appsmith.com/widget-reference/list", + backgroundColor: { + "!type": "string", + "!url": + "https://docs.appsmith.com/widget-reference/how-to-use-widgets", + }, + isVisible: DefaultAutocompleteDefinitions.isVisible, + itemSpacing: "number", + selectedItem: generateTypeDef(widget.selectedItem, extraDefsToDefine), + selectedItemView: generateTypeDef( + widget.selectedItemView, + extraDefsToDefine, + ), + triggeredItem: generateTypeDef(widget.triggeredItem, extraDefsToDefine), + triggeredItemView: generateTypeDef( + widget.triggeredItemView, + extraDefsToDefine, + ), + listData: generateTypeDef(widget.listData, extraDefsToDefine), + pageNo: generateTypeDef(widget.pageNo), + pageSize: generateTypeDef(widget.pageSize), + currentItemsView: generateTypeDef( + widget.currentItemsView, + extraDefsToDefine, + ), + }; + + return obj; + }; + } + + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, }, - isVisible: DefaultAutocompleteDefinitions.isVisible, - itemSpacing: "number", - selectedItem: generateTypeDef(widget.selectedItem, extraDefsToDefine), - selectedItemView: generateTypeDef( - widget.selectedItemView, - extraDefsToDefine, - ), - triggeredItem: generateTypeDef(widget.triggeredItem, extraDefsToDefine), - triggeredItemView: generateTypeDef( - widget.triggeredItemView, - extraDefsToDefine, - ), - listData: generateTypeDef(widget.listData, extraDefsToDefine), - pageNo: generateTypeDef(widget.pageNo), - pageSize: generateTypeDef(widget.pageSize), - currentItemsView: generateTypeDef( - widget.currentItemsView, - extraDefsToDefine, - ), - }); + }; } static getDerivedPropertiesMap() { diff --git a/app/client/src/widgets/MapChartWidget/index.ts b/app/client/src/widgets/MapChartWidget/index.ts index 9adddc2235..5ca523df7b 100644 --- a/app/client/src/widgets/MapChartWidget/index.ts +++ b/app/client/src/widgets/MapChartWidget/index.ts @@ -49,6 +49,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/MapChartWidget/widget/index.tsx b/app/client/src/widgets/MapChartWidget/widget/index.tsx index c2c13ae7a7..66e55d69dd 100644 --- a/app/client/src/widgets/MapChartWidget/widget/index.tsx +++ b/app/client/src/widgets/MapChartWidget/widget/index.tsx @@ -4,7 +4,7 @@ import Skeleton from "components/utils/Skeleton"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { retryPromise } from "utils/AppsmithUtils"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; @@ -78,6 +78,18 @@ class MapChartWidget extends BaseWidget { }, }; } + + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/MapWidget/index.ts b/app/client/src/widgets/MapWidget/index.ts index be6a226d76..059d323999 100644 --- a/app/client/src/widgets/MapWidget/index.ts +++ b/app/client/src/widgets/MapWidget/index.ts @@ -35,6 +35,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/MapWidget/widget/index.tsx b/app/client/src/widgets/MapWidget/widget/index.tsx index 615ccc2749..24ba6a6196 100644 --- a/app/client/src/widgets/MapWidget/widget/index.tsx +++ b/app/client/src/widgets/MapWidget/widget/index.tsx @@ -7,7 +7,7 @@ import MapComponent from "../component"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import styled from "styled-components"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; @@ -67,6 +67,17 @@ class MapWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneContentConfig() { return [ { diff --git a/app/client/src/widgets/MenuButtonWidget/index.ts b/app/client/src/widgets/MenuButtonWidget/index.ts index e4318e0dc7..90d7400b5d 100644 --- a/app/client/src/widgets/MenuButtonWidget/index.ts +++ b/app/client/src/widgets/MenuButtonWidget/index.ts @@ -56,6 +56,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx index 0ef4f9fa11..70f7219a65 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx @@ -1,6 +1,6 @@ import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { isArray, orderBy } from "lodash"; import { default as React } from "react"; import type { WidgetState } from "widgets/BaseWidget"; @@ -114,6 +114,21 @@ class MenuButtonWidget extends BaseWidget { return []; }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const { componentWidth } = this.getComponentDimensions(); const menuDropDownWidth = MinimumPopupRows * this.props.parentColumnSpace; diff --git a/app/client/src/widgets/MultiSelectTreeWidget/index.ts b/app/client/src/widgets/MultiSelectTreeWidget/index.ts index b345039b0a..b37a9e116d 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/index.ts +++ b/app/client/src/widgets/MultiSelectTreeWidget/index.ts @@ -70,6 +70,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx index d32edd2bdc..2d59f1fe34 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx @@ -5,7 +5,7 @@ import { Layers } from "constants/Layers"; import type { TextSize, WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { isArray, xor } from "lodash"; import type { DefaultValueType } from "rc-tree-select/lib/interface"; @@ -561,6 +561,25 @@ class MultiSelectTreeWidget extends BaseWidget< } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + }, + }; + } + getPageView() { const options = isArray(this.props.options) ? this.props.options : []; const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace; diff --git a/app/client/src/widgets/MultiSelectWidget/index.ts b/app/client/src/widgets/MultiSelectWidget/index.ts index 48811a6045..9ed1ba9dac 100644 --- a/app/client/src/widgets/MultiSelectWidget/index.ts +++ b/app/client/src/widgets/MultiSelectWidget/index.ts @@ -44,6 +44,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, }; diff --git a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx index d4aa0fcfa1..24f82a21cb 100644 --- a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx @@ -10,7 +10,7 @@ import BaseWidget from "widgets/BaseWidget"; import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; import { Layers } from "constants/Layers"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import type { DraftValueType } from "rc-select/lib/Select"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; @@ -447,6 +447,29 @@ class MultiSelectWidget extends BaseWidget< }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setSelectedOptions: { + path: "defaultOptionValue", + type: "array", + }, + }, + }; + } + getPageView() { const options = isArray(this.props.options) ? this.props.options : []; const values: string[] = isArray(this.props.selectedOptionValues) diff --git a/app/client/src/widgets/MultiSelectWidgetV2/index.ts b/app/client/src/widgets/MultiSelectWidgetV2/index.ts index 24c964abe1..a4ee15ef09 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/index.ts +++ b/app/client/src/widgets/MultiSelectWidgetV2/index.ts @@ -55,6 +55,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx index 3d6875bacf..7da2e5a2b4 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx @@ -5,7 +5,7 @@ import { Layers } from "constants/Layers"; import type { WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import equal from "fast-deep-equal/es6"; import type { LoDashStatic } from "lodash"; @@ -714,6 +714,29 @@ class MultiSelectWidget extends BaseWidget< } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setSelectedOptions: { + path: "defaultOptionValue", + type: "array", + }, + }, + }; + } + getPageView() { const options = isArray(this.props.options) ? this.props.options : []; const minDropDownWidth = MinimumPopupRows * this.props.parentColumnSpace; diff --git a/app/client/src/widgets/NumberSliderWidget/index.ts b/app/client/src/widgets/NumberSliderWidget/index.ts index 402c1a2b77..2ae47fc308 100644 --- a/app/client/src/widgets/NumberSliderWidget/index.ts +++ b/app/client/src/widgets/NumberSliderWidget/index.ts @@ -48,6 +48,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/NumberSliderWidget/widget/index.tsx b/app/client/src/widgets/NumberSliderWidget/widget/index.tsx index 7067f3acfe..64345af9d0 100644 --- a/app/client/src/widgets/NumberSliderWidget/widget/index.tsx +++ b/app/client/src/widgets/NumberSliderWidget/widget/index.tsx @@ -8,7 +8,7 @@ import type { SliderComponentProps } from "../component/Slider"; import SliderComponent from "../component/Slider"; import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -59,6 +59,25 @@ class NumberSliderWidget extends BaseWidget< }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setValue: { + path: "defaultValue", + type: "number", + }, + }, + }; + } + componentDidUpdate(prevProps: NumberSliderWidgetProps) { /** * If you change the defaultValue from the propertyPane diff --git a/app/client/src/widgets/PhoneInputWidget/index.ts b/app/client/src/widgets/PhoneInputWidget/index.ts index d4450b24ec..7217ab5ed1 100644 --- a/app/client/src/widgets/PhoneInputWidget/index.ts +++ b/app/client/src/widgets/PhoneInputWidget/index.ts @@ -42,6 +42,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx index 8b847014e0..4392c705c0 100644 --- a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx +++ b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx @@ -26,7 +26,7 @@ import { AsYouType, parseIncompletePhoneNumber } from "libphonenumber-js"; import * as Sentry from "@sentry/react"; import log from "loglevel"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { isAutoHeightEnabledForWidget, DefaultAutocompleteDefinitions, @@ -365,6 +365,21 @@ class PhoneInputWidget extends BaseInputWidget< this.props.updateWidgetMetaProperty("value", undefined); }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + getPageView() { const value = this.props.text ?? ""; const isInvalid = diff --git a/app/client/src/widgets/ProgressWidget/index.ts b/app/client/src/widgets/ProgressWidget/index.ts index b8aed04c15..67aa3c3fc3 100644 --- a/app/client/src/widgets/ProgressWidget/index.ts +++ b/app/client/src/widgets/ProgressWidget/index.ts @@ -35,6 +35,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/ProgressWidget/widget/index.tsx b/app/client/src/widgets/ProgressWidget/widget/index.tsx index 93dcd439aa..1850c32699 100644 --- a/app/client/src/widgets/ProgressWidget/widget/index.tsx +++ b/app/client/src/widgets/ProgressWidget/widget/index.tsx @@ -7,7 +7,7 @@ import BaseWidget from "widgets/BaseWidget"; import { Colors } from "constants/Colors"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import ProgressComponent from "../component"; import { ProgressType, ProgressVariant } from "../constants"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -188,6 +188,21 @@ class ProgressWidget extends BaseWidget { return {}; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setProgress: { + path: "progress", + type: "number", + }, + }, + }; + } + getPageView() { const { borderRadius, diff --git a/app/client/src/widgets/RadioGroupWidget/index.ts b/app/client/src/widgets/RadioGroupWidget/index.ts index e0017a6fdc..65c896d855 100644 --- a/app/client/src/widgets/RadioGroupWidget/index.ts +++ b/app/client/src/widgets/RadioGroupWidget/index.ts @@ -46,6 +46,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { defaults: { diff --git a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx index fd754dc50f..c4a34a0f1f 100644 --- a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx @@ -7,7 +7,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { TextSize, WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; @@ -572,6 +572,25 @@ class RadioGroupWidget extends BaseWidget { } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setData: { + path: "options", + type: "array", + }, + }, + }; + } + getPageView() { const { alignment, diff --git a/app/client/src/widgets/RangeSliderWidget/index.ts b/app/client/src/widgets/RangeSliderWidget/index.ts index a3f4b346f3..115d7f4ffe 100644 --- a/app/client/src/widgets/RangeSliderWidget/index.ts +++ b/app/client/src/widgets/RangeSliderWidget/index.ts @@ -48,6 +48,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/RangeSliderWidget/widget/index.tsx b/app/client/src/widgets/RangeSliderWidget/widget/index.tsx index a85c6256d5..cdca5f8993 100644 --- a/app/client/src/widgets/RangeSliderWidget/widget/index.tsx +++ b/app/client/src/widgets/RangeSliderWidget/widget/index.tsx @@ -9,7 +9,7 @@ import RangeSliderComponent from "../component/RangeSlider"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; import type { AutocompletionDefinitions } from "widgets/constants"; @@ -55,6 +55,21 @@ class RangeSliderWidget extends BaseWidget< return contentConfig; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisable: { + path: "isDisabled", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneStyleConfig() { return styleConfig; } diff --git a/app/client/src/widgets/RateWidget/index.ts b/app/client/src/widgets/RateWidget/index.ts index f917c79227..15007fd5f2 100644 --- a/app/client/src/widgets/RateWidget/index.ts +++ b/app/client/src/widgets/RateWidget/index.ts @@ -71,6 +71,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, }; diff --git a/app/client/src/widgets/RateWidget/widget/index.tsx b/app/client/src/widgets/RateWidget/widget/index.tsx index 173aa44c0c..8ed05545ad 100644 --- a/app/client/src/widgets/RateWidget/widget/index.tsx +++ b/app/client/src/widgets/RateWidget/widget/index.tsx @@ -7,7 +7,8 @@ import type { RateSize } from "../constants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; + +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils"; @@ -323,6 +324,25 @@ class RateWidget extends BaseWidget { }); }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setValue: { + path: "defaultRate", + type: "number", + }, + }, + }; + } + getPageView() { return ( (this.props.rate || this.props.rate === 0) && ( diff --git a/app/client/src/widgets/RichTextEditorWidget/index.ts b/app/client/src/widgets/RichTextEditorWidget/index.ts index a78df52ca2..29d77cacec 100644 --- a/app/client/src/widgets/RichTextEditorWidget/index.ts +++ b/app/client/src/widgets/RichTextEditorWidget/index.ts @@ -48,6 +48,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx index 22436f088e..dfcda1f990 100644 --- a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx @@ -16,7 +16,7 @@ import { import type { WidgetProps, WidgetState } from "../../BaseWidget"; import BaseWidget from "../../BaseWidget"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import type { AutocompletionDefinitions } from "widgets/constants"; export enum RTEFormats { @@ -400,6 +400,25 @@ class RichTextEditorWidget extends BaseWidget< }); }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + }, + }; + } + getPageView() { let value = this.props.text ?? ""; if (this.props.inputType === RTEFormats.MARKDOWN) { diff --git a/app/client/src/widgets/SelectWidget/index.ts b/app/client/src/widgets/SelectWidget/index.ts index d797b8e51f..1b2bc749df 100644 --- a/app/client/src/widgets/SelectWidget/index.ts +++ b/app/client/src/widgets/SelectWidget/index.ts @@ -54,6 +54,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/SelectWidget/widget/index.tsx b/app/client/src/widgets/SelectWidget/widget/index.tsx index 829112a063..e681dda7de 100644 --- a/app/client/src/widgets/SelectWidget/widget/index.tsx +++ b/app/client/src/widgets/SelectWidget/widget/index.tsx @@ -4,7 +4,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import equal from "fast-deep-equal/es6"; import type { LoDashStatic } from "lodash"; @@ -602,6 +602,33 @@ class SelectWidget extends BaseWidget { } } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setOptions: { + path: "options", + type: "array", + }, + setSelectedOption: { + path: "defaultOptionValue", + type: "string", + }, + }, + }; + } + isStringOrNumber = (value: any): value is string | number => isString(value) || isNumber(value); diff --git a/app/client/src/widgets/SingleSelectTreeWidget/index.ts b/app/client/src/widgets/SingleSelectTreeWidget/index.ts index 8b970a9790..140b0b28d0 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/index.ts +++ b/app/client/src/widgets/SingleSelectTreeWidget/index.ts @@ -68,6 +68,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx index d378b9e2a8..8118223341 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx @@ -5,7 +5,7 @@ import { Layers } from "constants/Layers"; import type { TextSize, WidgetType } from "constants/WidgetConstants"; import type { ValidationResponse } from "constants/WidgetValidation"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { isArray } from "lodash"; import type { DefaultValueType } from "rc-tree-select/lib/interface"; @@ -350,6 +350,21 @@ class SingleSelectTreeWidget extends BaseWidget< }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneStyleConfig() { return [ { diff --git a/app/client/src/widgets/StatboxWidget/index.ts b/app/client/src/widgets/StatboxWidget/index.ts index 397ee465bf..54f9edb62b 100644 --- a/app/client/src/widgets/StatboxWidget/index.ts +++ b/app/client/src/widgets/StatboxWidget/index.ts @@ -251,6 +251,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/StatboxWidget/widget/index.tsx b/app/client/src/widgets/StatboxWidget/widget/index.tsx index 4158f11699..a331edede0 100644 --- a/app/client/src/widgets/StatboxWidget/widget/index.tsx +++ b/app/client/src/widgets/StatboxWidget/widget/index.tsx @@ -2,7 +2,7 @@ import type { WidgetType } from "constants/WidgetConstants"; import { ContainerWidget } from "widgets/ContainerWidget/widget"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import { Positioning } from "utils/autoLayout/constants"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; @@ -49,6 +49,17 @@ class StatboxWidget extends ContainerWidget { ]; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneStyleConfig() { return [ { diff --git a/app/client/src/widgets/SwitchGroupWidget/index.ts b/app/client/src/widgets/SwitchGroupWidget/index.ts index 9ffe407431..503b38a3e5 100644 --- a/app/client/src/widgets/SwitchGroupWidget/index.ts +++ b/app/client/src/widgets/SwitchGroupWidget/index.ts @@ -47,6 +47,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx index 08db8c61e6..1daa289295 100644 --- a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -10,7 +10,7 @@ import BaseWidget from "widgets/BaseWidget"; import { LabelPosition } from "components/constants"; import type { TextSize } from "constants/WidgetConstants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import type { OptionProps } from "../component"; @@ -254,6 +254,25 @@ class SwitchGroupWidget extends BaseWidget< ]; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisable: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneStyleConfig() { return [ { diff --git a/app/client/src/widgets/SwitchWidget/index.ts b/app/client/src/widgets/SwitchWidget/index.ts index af7684dae8..25fcb9394b 100644 --- a/app/client/src/widgets/SwitchWidget/index.ts +++ b/app/client/src/widgets/SwitchWidget/index.ts @@ -39,6 +39,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { disabledPropsDefaults: { diff --git a/app/client/src/widgets/SwitchWidget/widget/index.tsx b/app/client/src/widgets/SwitchWidget/widget/index.tsx index 03f2fa4f0e..93dcf0cb4e 100644 --- a/app/client/src/widgets/SwitchWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchWidget/widget/index.tsx @@ -11,7 +11,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import { AlignWidgetTypes } from "widgets/constants"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { isAutoHeightEnabledForWidget, DefaultAutocompleteDefinitions, @@ -258,6 +258,33 @@ class SwitchWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setValue: { + path: "defaultSwitchState", + type: "boolean", + }, + setColor: { + path: "accentColor", + type: "string", + }, + }, + }; + } + getPageView() { return ( @@ -177,6 +177,31 @@ class TableWidget extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "string", + }, + setSelectedRowIndex: { + path: "defaultSelectedRowIndex", + type: "number", + disabled: "return options.entity.multiRowSelection", + }, + setSelectedRowIndices: { + path: "defaultSelectedRowIndices", + type: "array", + disabled: "return !options.entity.multiRowSelection", + }, + setData: { + path: "tableData", + type: "object", + }, + }, + }; + } + getTableColumns = () => { let columns: ReactTableColumnProps[] = []; const hiddenColumns: ReactTableColumnProps[] = []; diff --git a/app/client/src/widgets/TableWidgetV2/index.ts b/app/client/src/widgets/TableWidgetV2/index.ts index ac99c44166..61b9813f9b 100644 --- a/app/client/src/widgets/TableWidgetV2/index.ts +++ b/app/client/src/widgets/TableWidgetV2/index.ts @@ -62,6 +62,7 @@ export const CONFIG = { stylesheetConfig: Widget.getStylesheetConfig(), loadingProperties: Widget.getLoadingProperties(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, methods: { getQueryGenerationConfig: (widgetProps: WidgetProps) => { diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index c04575038a..dca87d171d 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -99,7 +99,7 @@ import { SelectCell } from "../component/cellComponents/SelectCell"; import { CellWrapper } from "../component/TableStyledWrappers"; import localStorage from "utils/localStorage"; import { generateNewColumnOrderFromStickyValue } from "./utilities"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { DateCell } from "../component/cellComponents/DateCell"; import type { MenuItem } from "widgets/MenuButtonWidget/constants"; import { MenuItemsSource } from "widgets/MenuButtonWidget/constants"; @@ -318,6 +318,7 @@ class TableWidgetV2 extends BaseWidget { previousPageVisited: generateTypeDef(widget.previousPageVisited), nextPageVisited: generateTypeDef(widget.nextPageButtonClicked), }; + return config; }; } @@ -385,6 +386,31 @@ class TableWidgetV2 extends BaseWidget { }; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "string", + }, + setSelectedRowIndex: { + path: "defaultSelectedRowIndex", + type: "number", + disabled: "return options.entity.multiRowSelection", + }, + setSelectedRowIndices: { + path: "defaultSelectedRowIndices", + type: "array", + disabled: "return !options.entity.multiRowSelection", + }, + setData: { + path: "tableData", + type: "object", + }, + }, + }; + } + /* * Function to get the table columns with appropriate render functions * based on columnType diff --git a/app/client/src/widgets/TabsWidget/index.ts b/app/client/src/widgets/TabsWidget/index.ts index 2ee0845e31..833b53a4bf 100644 --- a/app/client/src/widgets/TabsWidget/index.ts +++ b/app/client/src/widgets/TabsWidget/index.ts @@ -146,6 +146,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { widgetSize: [ diff --git a/app/client/src/widgets/TabsWidget/widget/index.tsx b/app/client/src/widgets/TabsWidget/widget/index.tsx index 437e51be04..bc5e97a5d1 100644 --- a/app/client/src/widgets/TabsWidget/widget/index.tsx +++ b/app/client/src/widgets/TabsWidget/widget/index.tsx @@ -15,7 +15,7 @@ import BaseWidget from "../../BaseWidget"; import TabsComponent from "../component"; import type { TabContainerWidgetProps, TabsWidgetProps } from "../constants"; import derivedProperties from "./parseDerivedProperties"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { isAutoHeightEnabledForWidget, isAutoHeightEnabledForWidgetWithLimits, @@ -281,6 +281,17 @@ class TabsWidget extends BaseWidget< ]; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + }, + }; + } + callDynamicHeightUpdates = () => { const { checkContainersForAutoHeight } = this.context; checkContainersForAutoHeight && checkContainersForAutoHeight(); diff --git a/app/client/src/widgets/TextWidget/index.ts b/app/client/src/widgets/TextWidget/index.ts index 4272e35706..fd4a7f79b0 100644 --- a/app/client/src/widgets/TextWidget/index.ts +++ b/app/client/src/widgets/TextWidget/index.ts @@ -77,6 +77,7 @@ export const CONFIG = { styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), + setterConfig: Widget.getSetterConfig(), }, autoLayout: { autoDimension: { diff --git a/app/client/src/widgets/TextWidget/widget/index.tsx b/app/client/src/widgets/TextWidget/widget/index.tsx index 76da3b042b..dc7f828706 100644 --- a/app/client/src/widgets/TextWidget/widget/index.tsx +++ b/app/client/src/widgets/TextWidget/widget/index.tsx @@ -9,7 +9,7 @@ import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import WidgetStyleContainer from "components/designSystems/appsmith/WidgetStyleContainer"; import type { Color } from "constants/Colors"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import { pick } from "lodash"; import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { WidgetProps, WidgetState } from "widgets/BaseWidget"; @@ -368,6 +368,33 @@ class TextWidget extends BaseWidget { ); }; + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setDisabled: { + path: "isDisabled", + type: "boolean", + }, + setRequired: { + path: "isRequired", + type: "boolean", + }, + setText: { + path: "text", + type: "text", + }, + setTextColor: { + path: "textColor", + type: "string", + }, + }, + }; + } + getPageView() { const disableLink: boolean = this.props.disableLink ? true diff --git a/app/client/src/widgets/VideoWidget/index.ts b/app/client/src/widgets/VideoWidget/index.ts index 70e8724d4a..fdb63f8172 100644 --- a/app/client/src/widgets/VideoWidget/index.ts +++ b/app/client/src/widgets/VideoWidget/index.ts @@ -29,6 +29,7 @@ export const CONFIG = { contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), stylesheetConfig: Widget.getStylesheetConfig(), + setterConfig: Widget.getSetterConfig(), autocompleteDefinitions: Widget.getAutocompleteDefinitions(), }, autoLayout: { diff --git a/app/client/src/widgets/VideoWidget/widget/index.tsx b/app/client/src/widgets/VideoWidget/widget/index.tsx index 94b4bbb002..52366e17fc 100644 --- a/app/client/src/widgets/VideoWidget/widget/index.tsx +++ b/app/client/src/widgets/VideoWidget/widget/index.tsx @@ -3,7 +3,7 @@ import Skeleton from "components/utils/Skeleton"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import type { WidgetType } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; -import type { Stylesheet } from "entities/AppTheming"; +import type { SetterConfig, Stylesheet } from "entities/AppTheming"; import React, { lazy, Suspense } from "react"; import type ReactPlayer from "react-player"; import { retryPromise } from "utils/AppsmithUtils"; @@ -125,6 +125,25 @@ class VideoWidget extends BaseWidget { ]; } + static getSetterConfig(): SetterConfig { + return { + __setters: { + setVisibility: { + path: "isVisible", + type: "boolean", + }, + setURL: { + path: "url", + type: "string", + }, + setPlaying: { + path: "autoPlay", + type: "boolean", + }, + }, + }; + } + static getPropertyPaneStyleConfig() { return [ { diff --git a/app/client/src/widgets/constants.ts b/app/client/src/widgets/constants.ts index b1426a6ebe..9c5e60c5ab 100644 --- a/app/client/src/widgets/constants.ts +++ b/app/client/src/widgets/constants.ts @@ -15,6 +15,7 @@ import type { DerivedPropertiesMap } from "utils/WidgetFactory"; import type { WidgetFeatures } from "utils/WidgetFeatures"; import type { WidgetProps } from "./BaseWidget"; import type { ExtraDef } from "utils/autocomplete/dataTreeTypeDefCreator"; +import type { WidgetEntityConfig } from "entities/DataTree/dataTreeFactory"; import type { WidgetQueryConfig, WidgetQueryGenerationConfig, @@ -72,6 +73,7 @@ export interface WidgetConfiguration { loadingProperties?: Array; stylesheetConfig?: Stylesheet; autocompleteDefinitions?: AutocompletionDefinitions; + setterConfig?: Record; }; methods?: Record; } @@ -121,6 +123,7 @@ interface LayoutProps { export type AutocompleteDefinitionFunction = ( widgetProps: WidgetProps, extraDefsToDefine?: ExtraDef, + configTree?: WidgetEntityConfig, ) => Record; export type AutocompletionDefinitions = diff --git a/app/client/src/workers/Evaluation/JSObject/index.ts b/app/client/src/workers/Evaluation/JSObject/index.ts index 8e1bce592b..6ce900ff6d 100644 --- a/app/client/src/workers/Evaluation/JSObject/index.ts +++ b/app/client/src/workers/Evaluation/JSObject/index.ts @@ -272,7 +272,7 @@ export function parseJSActions( }); } - functionDeterminer.setupEval(unEvalDataTree); + functionDeterminer.setupEval(unEvalDataTree, dataTreeEvalRef.getConfigTree()); jsPropertiesState.stopUpdate(); Object.keys(jsUpdates).forEach((entityName) => { diff --git a/app/client/src/workers/Evaluation/__tests__/Actions.test.ts b/app/client/src/workers/Evaluation/__tests__/Actions.test.ts index c1d969e333..5e7d24ff14 100644 --- a/app/client/src/workers/Evaluation/__tests__/Actions.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/Actions.test.ts @@ -42,6 +42,7 @@ describe("Add functions", () => { }; const evalContext = createEvaluationContext({ dataTree, + configTree: {}, isTriggerBased: true, context: {}, }); @@ -538,12 +539,15 @@ const dataTree = { }, }; +const configTree = {}; + describe("Test addDataTreeToContext method", () => { const evalContext: EvalContext = {}; beforeAll(() => { addDataTreeToContext({ EVAL_CONTEXT: evalContext, dataTree: dataTree as unknown as DataTree, + configTree, isTriggerBased: true, }); addPlatformFunctionsToEvalContext(evalContext); diff --git a/app/client/src/workers/Evaluation/__tests__/errorModifier.test.ts b/app/client/src/workers/Evaluation/__tests__/errorModifier.test.ts index ddba404fb9..6f29c66435 100644 --- a/app/client/src/workers/Evaluation/__tests__/errorModifier.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/errorModifier.test.ts @@ -121,7 +121,7 @@ describe("Test error modifier", () => { } as unknown as DataTree; beforeAll(() => { - errorModifier.updateAsyncFunctions(dataTree); + errorModifier.updateAsyncFunctions(dataTree, {}); }); it("TypeError for defined Api in data field ", () => { diff --git a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts index bab1c6f152..6392be80ac 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluate.test.ts @@ -265,7 +265,7 @@ describe("isFunctionAsync", () => { if (typeof testFunc === "string") { testFunc = eval(testFunc); } - functionDeterminer.setupEval({}); + functionDeterminer.setupEval({}, {}); const actual = functionDeterminer.isFunctionAsync(testFunc); expect(actual).toBe(testCase.expected); } diff --git a/app/client/src/workers/Evaluation/__tests__/setters.test.ts b/app/client/src/workers/Evaluation/__tests__/setters.test.ts new file mode 100644 index 0000000000..25f8d5702b --- /dev/null +++ b/app/client/src/workers/Evaluation/__tests__/setters.test.ts @@ -0,0 +1,93 @@ +import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; +import setters from "../setters"; +import TableWidget from "widgets/TableWidgetV2/widget"; +import { CONFIG } from "widgets/TableWidgetV2/index"; +import { RenderModes } from "constants/WidgetConstants"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; +import { registerWidget } from "utils/WidgetRegisterHelpers"; +import { createEvaluationContext } from "../evaluate"; + +registerWidget(TableWidget, CONFIG); + +const evalTree: DataTree = {}; +const configTree: ConfigTree = {}; + +const tableWidgetDataTree = generateDataTreeWidget({ + type: TableWidget.getWidgetType(), + widgetId: "random", + widgetName: "Table1", + children: [], + bottomRow: 0, + isLoading: false, + parentColumnSpace: 0, + parentRowSpace: 0, + version: 1, + leftColumn: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + primaryColumns: [], + tableData: [], +}); + +evalTree["Table1"] = tableWidgetDataTree.unEvalEntity; +configTree["Table1"] = tableWidgetDataTree.configEntity; + +const evalContext = createEvaluationContext({ + dataTree: evalTree, + isTriggerBased: true, + context: {}, + configTree, +}); + +jest.mock("workers/Evaluation/handlers/evalTree", () => ({ + get dataTreeEvaluator() { + return { + evalTree: evalContext, + getEvalTree: () => evalContext, + getConfigTree: () => configTree, + }; + }, +})); + +jest.mock("../evalTreeWithChanges", () => ({ + evalTreeWithChanges: () => { + // + }, +})); + +describe("Setter class test", () => { + it("Setters init method ", () => { + setters.init(configTree, evalTree); + + expect(setters.getMap()).toEqual({ + Table1: { + setData: true, + setSelectedRowIndex: true, + setVisibility: true, + }, + }); + }); + + it("getEntitySettersFromConfig method ", async () => { + const methodMap = setters.getEntitySettersFromConfig( + tableWidgetDataTree.configEntity, + "Table1", + evalTree["Table1"], + ); + + const globalContext = self as any; + + Object.assign(globalContext, evalContext); + + expect(Object.keys(methodMap)).toEqual([ + "setVisibility", + "setSelectedRowIndex", + "setData", + ]); + + globalContext.Table1.setVisibility(true); + + expect(globalContext.Table1.isVisible).toEqual(true); + }); +}); diff --git a/app/client/src/workers/Evaluation/errorModifier.ts b/app/client/src/workers/Evaluation/errorModifier.ts index 10d64716f9..261b00e05d 100644 --- a/app/client/src/workers/Evaluation/errorModifier.ts +++ b/app/client/src/workers/Evaluation/errorModifier.ts @@ -1,4 +1,4 @@ -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import { getAllAsyncFunctions } from "@appsmith/workers/Evaluation/Actions"; import type { EvaluationError } from "utils/DynamicBindingUtils"; import { PropertyEvaluationErrorCategory } from "utils/DynamicBindingUtils"; @@ -13,8 +13,8 @@ class ErrorModifier { private asyncFunctionsNameMap: Record = {}; - updateAsyncFunctions(dataTree: DataTree) { - this.asyncFunctionsNameMap = getAllAsyncFunctions(dataTree); + updateAsyncFunctions(dataTree: DataTree, configTree: ConfigTree) { + this.asyncFunctionsNameMap = getAllAsyncFunctions(dataTree, configTree); } run(error: Error): { diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts index 72fc018f70..5d45a7913d 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts @@ -15,7 +15,10 @@ import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; import type { JSUpdate } from "utils/JSPaneUtils"; import { setEvalContext } from "./evaluate"; -export function evalTreeWithChanges(updatedValuePaths: string[][]) { +export function evalTreeWithChanges( + updatedValuePaths: string[][], + metaUpdates: EvalMetaUpdates = [], +) { let evalOrder: string[] = []; let jsUpdates: Record = {}; let unEvalUpdates: DataTreeDiff[] = []; @@ -25,7 +28,7 @@ export function evalTreeWithChanges(updatedValuePaths: string[][]) { const errors: EvalError[] = []; const logs: any[] = []; const dependencies: DependencyMap = {}; - let evalMetaUpdates: EvalMetaUpdates = []; + let evalMetaUpdates: EvalMetaUpdates = metaUpdates; let staleMetaIds: string[] = []; const pathsToClearErrorsFor: any[] = []; let unevalTree: UnEvalTree = {}; @@ -49,7 +52,8 @@ export function evalTreeWithChanges(updatedValuePaths: string[][]) { ); setEvalContext({ - dataTree: dataTreeEvaluator.evalTree, + dataTree: dataTreeEvaluator.getEvalTree(), + configTree: dataTreeEvaluator.getConfigTree(), isDataField: false, isTriggerBased: true, }); @@ -57,6 +61,7 @@ export function evalTreeWithChanges(updatedValuePaths: string[][]) { dataTree = makeEntityConfigsAsObjProperties(dataTreeEvaluator.evalTree, { evalProps: dataTreeEvaluator.evalProps, }); + evalMetaUpdates = JSON.parse( JSON.stringify(updateResponse.evalMetaUpdates), ); diff --git a/app/client/src/workers/Evaluation/evaluate.ts b/app/client/src/workers/Evaluation/evaluate.ts index fdf08fa4ae..795f5b8a59 100644 --- a/app/client/src/workers/Evaluation/evaluate.ts +++ b/app/client/src/workers/Evaluation/evaluate.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import type { EvaluationError } from "utils/DynamicBindingUtils"; import { PropertyEvaluationErrorType } from "utils/DynamicBindingUtils"; import unescapeJS from "unescape-js"; @@ -13,6 +13,7 @@ import { errorModifier, FoundPromiseInSyncEvalError } from "./errorModifier"; import { addDataTreeToContext } from "@appsmith/workers/Evaluation/Actions"; import log from "loglevel"; import * as Sentry from "@sentry/react"; +import type { DataTreeEntity } from "entities/DataTree/dataTreeFactory"; export type EvalResult = { result: any; @@ -123,6 +124,7 @@ const beginsWithLineBreakRegex = /^\s+|\s+$/; export type EvalContext = Record; export interface createEvaluationContextArgs { dataTree: DataTree; + configTree?: ConfigTree; context?: EvaluateContext; isTriggerBased: boolean; evalArguments?: Array; @@ -140,6 +142,7 @@ export interface createEvaluationContextArgs { */ export const createEvaluationContext = (args: createEvaluationContextArgs) => { const { + configTree = {}, context, dataTree, evalArguments, @@ -160,6 +163,7 @@ export const createEvaluationContext = (args: createEvaluationContextArgs) => { addDataTreeToContext({ EVAL_CONTEXT, dataTree, + configTree, removeEntityFunctions: !!removeEntityFunctions, isTriggerBased, }); @@ -206,6 +210,7 @@ export const getUserScriptToEvaluate = ( }; export function setEvalContext({ + configTree, context, dataTree, evalArguments, @@ -214,6 +219,7 @@ export function setEvalContext({ }: { context?: EvaluateContext; dataTree: DataTree; + configTree?: ConfigTree; evalArguments?: Array; isDataField: boolean; isTriggerBased: boolean; @@ -222,6 +228,7 @@ export function setEvalContext({ const evalContext = createEvaluationContext({ dataTree, + configTree, context, evalArguments, isTriggerBased, @@ -236,6 +243,7 @@ export default function evaluateSync( isJSCollection: boolean, context?: EvaluateContext, evalArguments?: Array, + configTree?: ConfigTree, ): EvalResult { return (function () { resetWorkerGlobalScope(); @@ -259,6 +267,7 @@ export default function evaluateSync( setEvalContext({ dataTree, + configTree, isDataField: true, isTriggerBased: isJSCollection, context, @@ -297,6 +306,7 @@ export default function evaluateSync( export async function evaluateAsync( userScript: string, dataTree: DataTree, + configTree: ConfigTree, context?: EvaluateContext, evalArguments?: Array, ) { @@ -309,6 +319,7 @@ export async function evaluateAsync( setEvalContext({ dataTree, + configTree, isDataField: false, isTriggerBased: true, context, @@ -363,3 +374,13 @@ export function convertAllDataTypesToString(e: any) { } } } + +export function shouldAddSetter(setter: any, entity: DataTreeEntity) { + const isDisabledExpression = setter.disabled; + + if (!isDisabledExpression) return true; + + const isDisabledFn = new Function("options", isDisabledExpression); + + return !isDisabledFn({ entity }); +} diff --git a/app/client/src/workers/Evaluation/functionDeterminer.ts b/app/client/src/workers/Evaluation/functionDeterminer.ts index ebe8cd68a0..71058a99aa 100644 --- a/app/client/src/workers/Evaluation/functionDeterminer.ts +++ b/app/client/src/workers/Evaluation/functionDeterminer.ts @@ -1,7 +1,7 @@ import { addDataTreeToContext } from "@appsmith/workers/Evaluation/Actions"; import type { EvalContext } from "./evaluate"; import { setEvalContext } from "./evaluate"; -import type { DataTree } from "entities/DataTree/dataTreeFactory"; +import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeFactory"; import userLogs from "./fns/overrides/console"; import ExecutionMetaData from "./fns/utils/ExecutionMetaData"; import { dataTreeEvaluator } from "./handlers/evalTree"; @@ -9,7 +9,7 @@ import { dataTreeEvaluator } from "./handlers/evalTree"; class FunctionDeterminer { evalContext: EvalContext = {}; - setupEval(dataTree: DataTree) { + setupEval(dataTree: DataTree, configTree: ConfigTree) { /**** Setting the eval context ****/ const evalContext: EvalContext = { $isDataField: true, @@ -23,6 +23,7 @@ class FunctionDeterminer { addDataTreeToContext({ dataTree, + configTree, EVAL_CONTEXT: evalContext, isTriggerBased: true, }); @@ -43,9 +44,11 @@ class FunctionDeterminer { }); if (!dataTreeEvaluator) return; - const dataTree = dataTreeEvaluator?.getEvalTree(); + const dataTree = dataTreeEvaluator.getEvalTree(); + const configTree = dataTreeEvaluator.getConfigTree(); setEvalContext({ dataTree, + configTree, isTriggerBased: true, isDataField: false, }); diff --git a/app/client/src/workers/Evaluation/handlers/evalExpression.ts b/app/client/src/workers/Evaluation/handlers/evalExpression.ts index c6823f05b2..9f48afc9ec 100644 --- a/app/client/src/workers/Evaluation/handlers/evalExpression.ts +++ b/app/client/src/workers/Evaluation/handlers/evalExpression.ts @@ -6,6 +6,7 @@ export default function (request: EvalWorkerASyncRequest) { const { data } = request; const { expression } = data; const evalTree = dataTreeEvaluator?.evalTree; - if (!evalTree) return {}; - return evaluateAsync(expression, evalTree, {}); + const configTree = dataTreeEvaluator?.configTree; + if (!evalTree || !configTree) return {}; + return evaluateAsync(expression, evalTree, configTree, {}, undefined); } diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index 450689d8f5..bccb5c4223 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -59,6 +59,7 @@ export default function (request: EvalWorkerSyncRequest) { const unevalTree = __unevalTree__.unEvalTree; configTree = __unevalTree__.configTree as ConfigTree; + try { if (!dataTreeEvaluator) { isCreateFirstTree = true; @@ -113,6 +114,7 @@ export default function (request: EvalWorkerSyncRequest) { setEvalContext({ dataTree: dataTreeEvaluator.evalTree, + configTree, isDataField: false, isTriggerBased: true, }); @@ -155,6 +157,7 @@ export default function (request: EvalWorkerSyncRequest) { setEvalContext({ dataTree: dataTreeEvaluator.evalTree, + configTree, isDataField: false, isTriggerBased: true, }); @@ -168,7 +171,6 @@ export default function (request: EvalWorkerSyncRequest) { ); staleMetaIds = updateResponse.staleMetaIds; } - dataTreeEvaluator = dataTreeEvaluator as DataTreeEvaluator; dependencies = dataTreeEvaluator.inverseDependencyMap; errors = dataTreeEvaluator.errors; dataTreeEvaluator.clearErrors(); diff --git a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts index 502f6e27cb..33732a6e3a 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts @@ -35,6 +35,7 @@ export default async function (request: EvalWorkerASyncRequest) { return dataTreeEvaluator.evaluateTriggers( dynamicTrigger, evalTree, + unEvalTree.configTree, callbackData, { globalContext, diff --git a/app/client/src/workers/Evaluation/setters.ts b/app/client/src/workers/Evaluation/setters.ts new file mode 100644 index 0000000000..65e41eaa86 --- /dev/null +++ b/app/client/src/workers/Evaluation/setters.ts @@ -0,0 +1,175 @@ +import { + getEntityNameAndPropertyPath, + isWidget, + overrideWidgetProperties, +} from "@appsmith/workers/Evaluation/evaluationUtils"; +import type { EvalMetaUpdates } from "@appsmith/workers/common/DataTreeEvaluator/types"; +import { evalTreeWithChanges } from "./evalTreeWithChanges"; +import { dataTreeEvaluator } from "./handlers/evalTree"; +import { get, set } from "lodash"; +import { validate } from "./validations"; +import type { + ConfigTree, + DataTree, + DataTreeEntity, + DataTreeEntityConfig, +} from "entities/DataTree/dataTreeFactory"; +import { getFnWithGuards, isAsyncGuard } from "./fns/utils/fnGuard"; +import { shouldAddSetter } from "./evaluate"; + +class Setters { + /** stores the setter accessor as key and true as value + * + * example - ```{ "Table1.setVisibility": true, "Table1.setData": true }``` + */ + private setterMethodLookup: Record = {}; + + private applySetterMethod( + path: string, + value: unknown, + setterMethodName: string, + ) { + const { entityName, propertyPath } = getEntityNameAndPropertyPath(path); + + if (!dataTreeEvaluator) return; + + const evalTree = dataTreeEvaluator.getEvalTree(); + const configTree = dataTreeEvaluator.getConfigTree(); + + const entity = evalTree[entityName]; + const entityConfig = configTree[entityName]; + + const updatedProperties: string[][] = []; + const overriddenProperties: string[] = []; + const evalMetaUpdates: EvalMetaUpdates = []; + + let parsedValue = value; + + if (value === undefined) { + const error = new Error( + `The value passed to ${entityName}.${setterMethodName}() evaluates to undefined.`, + ); + error.name = entityName + "." + setterMethodName + " failed"; + throw error; + } + + const { validationPaths } = entityConfig; + + if (validationPaths) { + const validationConfig = validationPaths[propertyPath] || {}; + + const config = { + ...validationConfig, + params: { ...(validationConfig.params || {}) }, + }; + config.params.strict = true; + + const { isValid, messages, parsed } = validate( + config, + value, + entity as Record, + propertyPath, + ); + parsedValue = parsed; + + if (!isValid) { + const message = messages && messages[0] ? messages[0].message : ""; + const error = new Error( + `${entityName + "." + setterMethodName}: ${message}`, + ); + error.name = entityName + "." + setterMethodName + " failed"; + throw error; + } + } + + if (isWidget(entity)) { + overrideWidgetProperties({ + entity, + propertyPath, + value: parsedValue, + currentTree: evalTree, + configTree, + evalMetaUpdates, + fullPropertyPath: path, + isNewWidget: false, + shouldUpdateGlobalContext: true, + overriddenProperties, + }); + + overriddenProperties.forEach((propPath) => { + updatedProperties.push([entityName, propPath]); + }); + } + + set(evalTree, path, parsedValue); + set(self, path, parsedValue); + + return new Promise((resolve) => { + updatedProperties.push([entityName, propertyPath]); + evalTreeWithChanges(updatedProperties, evalMetaUpdates); + resolve(parsedValue); + }); + } + /** Generates a new setter method */ + private factory(path: string, setterMethodName: string, entityName: string) { + /** register the setter method in the lookup */ + set(this.setterMethodLookup, [entityName, setterMethodName], true); + + const fn = async (value: unknown) => { + if (!dataTreeEvaluator) return; + return this.applySetterMethod(path, value, setterMethodName); + }; + + return getFnWithGuards(fn, setterMethodName, [isAsyncGuard]); + } + + clear() { + this.setterMethodLookup = {}; + } + + has(entityName: string, propertyName: string) { + return get(this.setterMethodLookup, [entityName, propertyName], false); + } + + getMap() { + return this.setterMethodLookup; + } + + getEntitySettersFromConfig( + entityConfig: DataTreeEntityConfig, + entityName: string, + entity: DataTreeEntity, + ) { + const setterMethodMap: Record = {}; + if (!entityConfig) return setterMethodMap; + + if (entityConfig.__setters) { + for (const setterMethodName of Object.keys(entityConfig.__setters)) { + const path = entityConfig.__setters[setterMethodName].path; + + if (!shouldAddSetter(entityConfig.__setters[setterMethodName], entity)) + continue; + + setterMethodMap[setterMethodName] = this.factory( + path, + setterMethodName, + entityName, + ); + } + } + + return setterMethodMap; + } + + init(configTree: ConfigTree, dataTree: DataTree) { + const configTreeEntries = Object.entries(configTree); + for (const [entityName, entityConfig] of configTreeEntries) { + const entity = dataTree[entityName]; + + this.getEntitySettersFromConfig(entityConfig, entityName, entity); + } + } +} + +const setters = new Setters(); +export default setters; diff --git a/app/client/src/workers/Evaluation/validations.ts b/app/client/src/workers/Evaluation/validations.ts index 5b3595a57a..ab503ac47f 100644 --- a/app/client/src/workers/Evaluation/validations.ts +++ b/app/client/src/workers/Evaluation/validations.ts @@ -16,7 +16,6 @@ import _, { import moment from "moment"; import type { ValidationConfig } from "constants/PropertyControlConstants"; -import evaluate from "./evaluate"; import getIsSafeURL from "utils/validation/getIsSafeURL"; import * as log from "loglevel"; @@ -727,10 +726,12 @@ export const VALIDATORS: Record = { } const isABoolean = value === true || value === false; const isStringTrueFalse = value === "true" || value === "false"; - const isValid = isABoolean || isStringTrueFalse; + // if strictCheck is true then stringTrueFalse are considered invalid value. + const strictCheck = config.params && config.params.strict; + const isValid = strictCheck ? isABoolean : isABoolean || isStringTrueFalse; let parsed = value; - if (isStringTrueFalse) parsed = value !== "false"; + if (isStringTrueFalse && !strictCheck) parsed = value !== "false"; if (!isValid) { return { @@ -1089,13 +1090,19 @@ export const VALIDATORS: Record = { }; if (config.params?.fnString && isString(config.params?.fnString)) { try { - const { result } = evaluate( - config.params.fnString, - {}, - false, - undefined, - [value, props, globalThis._, globalThis.moment, propertyPath, config], - ); + const fnBody = `const fn = ${config.params?.fnString}; + return fn(value, props, _, moment, propertyPath, config);`; + + const result = new Function( + "value", + "props", + "_", + "moment", + "propertyPath", + "config", + fnBody, + )(value, props, globalThis._, globalThis.moment, propertyPath, config); + return result; } catch (e) { log.error("Validation function error: ", { e }); diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index c3dedd79ac..6f27df6ca2 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -135,6 +135,7 @@ export default class DataTreeEvaluator { inverseDependencyMap: DependencyMap = {}; widgetConfigMap: WidgetTypeConfigMap = {}; evalTree: DataTree = {}; + configTree: ConfigTree = {}; /** * This contains raw evaluated value without any validation or parsing. @@ -191,6 +192,14 @@ export default class DataTreeEvaluator { this.evalTree = evalTree; } + getConfigTree() { + return this.configTree; + } + + setConfigTree(configTree: ConfigTree) { + if (configTree) this.configTree = configTree; + } + getUnParsedEvalTree() { return this.unParsedEvalTree; } @@ -214,6 +223,8 @@ export default class DataTreeEvaluator { jsUpdates: Record; evalOrder: string[]; } { + this.setConfigTree(configTree); + const totalFirstTreeSetupStartTime = performance.now(); // cloneDeep will make sure not to omit key which has value as undefined. const firstCloneStartTime = performance.now(); @@ -239,6 +250,7 @@ export default class DataTreeEvaluator { const allKeysGenerationEndTime = performance.now(); const createDependencyMapStartTime = performance.now(); + // Create dependency map const { dependencyMap, invalidReferencesMap, validationDependencyMap } = createDependencyMap(this, localUnEvalTree, configTree); @@ -390,9 +402,11 @@ export default class DataTreeEvaluator { pathsToClearErrorsFor: any[]; isNewWidgetAdded: boolean; } { + this.setConfigTree(configTree); const totalUpdateTreeSetupStartTime = performance.now(); let localUnEvalTree = Object.assign({}, unEvalTree); + let jsUpdates: Record = {}; const diffCheckTimeStartTime = performance.now(); //update uneval tree from previously saved current state of collection @@ -410,6 +424,7 @@ export default class DataTreeEvaluator { translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree), ), ); + //save parsed functions in resolveJSFunctions, update current state of js collection const parsedCollections = parseJSActions( this, @@ -888,7 +903,7 @@ export default class DataTreeEvaluator { staleMetaIds: string[]; } { const tree = klona(oldUnevalTree); - errorModifier.updateAsyncFunctions(tree); + errorModifier.updateAsyncFunctions(tree, this.getConfigTree()); const evalMetaUpdates: EvalMetaUpdates = []; const { isFirstTree, metaWidgets, skipRevalidation, unevalUpdates } = options; @@ -1305,6 +1320,7 @@ export default class DataTreeEvaluator { async evaluateTriggers( userScript: string, dataTree: DataTree, + configTree: ConfigTree, callbackData: Array, context?: EvaluateContext, ) { @@ -1313,6 +1329,7 @@ export default class DataTreeEvaluator { return evaluateAsync( jsSnippets[0] || userScript, dataTree, + configTree, context, callbackData, );