diff --git a/.github/ISSUE_TEMPLATE/--documentation-improvement.yaml b/.github/ISSUE_TEMPLATE/--documentation-improvement.yaml deleted file mode 100644 index 4c38739196..0000000000 --- a/.github/ISSUE_TEMPLATE/--documentation-improvement.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: 📖 Documentation Improvement -description: Suggest improvements to our documentation -title: "[Docs]: " -labels: [Documentation] -assignees: -- Nikhil-Nandagopal -- danciaclara -body: -- type: markdown - attributes: - value: | - Thanks for taking the time to fill out this documentation improvement request! -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue realated to this already exists. - options: - - label: I have searched the existing issues - required: true -- type: input - attributes: - label: Documentation Link - description: Add a link to the page which needs improvement (if relevant) - validations: - required: false -- type: textarea - attributes: - label: Describe the problem - description: Is the documentation missing? Or is it confusing? Why is it confusing? - validations: - required: true -- type: textarea - attributes: - label: Describe the improvement - description: A clear and concise description of the improvement. - validations: - required: true diff --git a/.github/config.json b/.github/config.json index 9feed48b9a..40f92266bc 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1 +1 @@ -{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"Business Edition","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Fork App","type":"hasLabel","value":true},{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Templates","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"Function execution","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"AST","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"App Theming","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Dynamic Height","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ads migration","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true}],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"D2ACD2","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"5369db","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"community":{"color":"dded34","name":"community","description":"issues reported by community members"},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"d5794b","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Performance":{"color":"d30e53","name":"Performance","description":"Page Load and evaluations"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"37EA75","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"074ac6","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"f14274","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"a7768a","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"12b715","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"771e69","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"C4568E","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"2cc0d4","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"color":"9168f4","name":"In App Comms","description":"Issues around communication with appsmith instances"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"regression":{"color":"ffe5bc","name":"regression","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"6310da"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"12b715","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"Invite users":{"color":"1799b0","name":"Invite users","description":"Invite users flow and any associated actions"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"26ef4f"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"Function execution":{"name":"Function execution","description":"JS function execution","color":"a302b0"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"3897be"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"8bf430"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to Templates","color":"c3b541"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"A-Force":{"name":"A-Force","description":"Issues raised by A-Force team","color":"274ecc"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"55184d"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Design system related issues","color":"6d1c11"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"ads migration":{"name":"ads migration","description":"All issues related to Appsmith design system migration","color":"6d1c11"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"41dd97"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"AST":{"name":"AST","description":"Issues related to maintaining AST logic","color":"418fa4"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Dynamic Height":{"name":"Dynamic Height","description":"Issues related to dynamic height of widgets","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"}},"success":true} \ No newline at end of file +{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"Business Edition","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Fork App","type":"hasLabel","value":true},{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Templates","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"Function execution","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"AST","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"App Theming","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ads migration","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true}],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"D2ACD2","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"5369db","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"community":{"color":"dded34","name":"community","description":"issues reported by community members"},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"d5794b","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Performance":{"color":"d30e53","name":"Performance","description":"Page Load and evaluations"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"37EA75","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"074ac6","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"f14274","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"a7768a","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"12b715","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"771e69","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"C4568E","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"2cc0d4","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"color":"9168f4","name":"In App Comms","description":"Issues around communication with appsmith instances"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"regression":{"color":"ffe5bc","name":"regression","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"6310da"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"12b715","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"Invite users":{"color":"1799b0","name":"Invite users","description":"Invite users flow and any associated actions"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"26ef4f"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"Function execution":{"name":"Function execution","description":"JS function execution","color":"a302b0"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"3897be"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"8bf430"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to Templates","color":"c3b541"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"A-Force":{"name":"A-Force","description":"Issues raised by A-Force team","color":"274ecc"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"55184d"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Design system related issues","color":"6d1c11"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"ads migration":{"name":"ads migration","description":"All issues related to Appsmith design system migration","color":"6d1c11"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"41dd97"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"AST":{"name":"AST","description":"Issues related to maintaining AST logic","color":"418fa4"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"}},"success":true} \ No newline at end of file diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 561dab8962..fdce19f2ef 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -168,6 +168,18 @@ jobs: yarn global add serve echo "$(yarn global bin)" >> $GITHUB_PATH + - name: Load docker image + if: steps.run_result.outputs.run_result != 'success' + env: + APPSMITH_LICENSE_KEY: ${{ secrets.APPSMITH_LICENSE_KEY }} + working-directory: "." + run: | + mkdir -p ~/git-server/keys + mkdir -p ~/git-server/repos + docker run --name test-event-driver -d -p 2222:22 -p 5001:5001 -p 3306:3306 \ + -p 5432:5432 -p 28017:27017 -p 25:25 --privileged --pid=host --ipc=host --volume /:/host -v ~/git-server/keys:/git-server/keys \ + -v ~/git-server/repos:/git-server/repos appsmith/test-event-driver:latest + - name: Setting up the perf tests if: steps.run_result.outputs.run_result != 'success' shell: bash diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index fdf1e6fb3c..3aa77b8a3d 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -1476,7 +1476,7 @@ jobs: # Force save the failed spec list into a cache - name: Store the combined run result - if: needs.ui-test.result + if: needs.ui-test.result != 'success' uses: martijnhols/actions-cache/save@v3 with: path: | @@ -1487,7 +1487,7 @@ jobs: # Force save the fat failed spec list into a cache - name: Store the combined run result for fat - if: needs.ui-test.result + if: needs.fat-container-test.result != 'success' uses: martijnhols/actions-cache/save@v3 with: path: | @@ -1499,7 +1499,7 @@ jobs: # Upload combined failed spec list to a file # This is done for debugging. - name: upload combined failed spec - if: needs.ui-test.result + if: needs.ui-test.result != 'success' uses: actions/upload-artifact@v2 with: name: combined_failed_spec @@ -1508,7 +1508,7 @@ jobs: # Upload combined failed fat spec list to a file # This is done for debugging. - name: upload combined failed spec - if: needs.ui-test.result + if: needs.fat-container-test.result != 'success' uses: actions/upload-artifact@v2 with: name: combined_failed_spec_fat diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef78d7888e..8252ed59e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ We welcome all feature requests, whether it's to add new functionality to an exi File your feature request through GitHub Issues using the [Feature Request](https://github.com/appsmithorg/appsmith/issues/new?assignees=Nikhil-Nandagopal&labels=Enhancement&template=--feature-request.yaml&title=%5BFeature%5D%3A+) template. #### 📝 Improve the documentation -In the process of shipping features quickly, we may forget to keep our docs up to date. You can help by suggesting improvements to our documentation using the [Documentation Improvement](https://github.com/appsmithorg/appsmith/issues/new?assignees=Nikhil-Nandagopal&labels=Documentation&template=--documentation-improvement.yaml&title=%5BDocs%5D%3A+) template or dive right into our [Docs Contribution Guide](contributions/docs/CONTRIBUTING.md)! +In the process of shipping features quickly, we may forget to keep our docs up to date. You can help by suggesting improvements to our documentation using the [Documentation templates](https://github.com/appsmithorg/appsmith-docs/issues/new/choose) or dive right into our [Docs Contribution Guide](https://github.com/appsmithorg/appsmith-docs/blob/main/CONTRIBUTING.md)! #### ⚙️ Close a Bug / Feature issue We welcome contributions that help make appsmith bug free & improve the experience of our users. You can also find issues tagged [Good First Issues](https://github.com/appsmithorg/appsmith/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22+bug). Check out our [Code Contribution Guide](contributions/CodeContributionsGuidelines.md) to begin. diff --git a/README.md b/README.md index b7ff6db362..84c88f4670 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ Lets build great software together. [![sharat87](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/120119?v=4&w=50&h=50&mask=circle)](https://github.com/sharat87) [![riodeuno](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/103687?v=4&w=50&h=50&mask=circle)](https://github.com/riodeuno) [![vicky-primathon](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/67091118?v=4&w=50&h=50&mask=circle)](https://github.com/vicky-primathon) -[![satbir121](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/39981226?v=4&w=50&h=50&mask=circle)](https://github.com/satbir121) [![akash-codemonk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/67054171?v=4&w=50&h=50&mask=circle)](https://github.com/akash-codemonk) +[![satbir121](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/39981226?v=4&w=50&h=50&mask=circle)](https://github.com/satbir121) [![nidhi-nair](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5298848?v=4&w=50&h=50&mask=circle)](https://github.com/nidhi-nair) [![Tooluloope](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/31691737?v=4&w=50&h=50&mask=circle)](https://github.com/Tooluloope) [![sumitsum](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1757421?v=4&w=50&h=50&mask=circle)](https://github.com/sumitsum) @@ -198,16 +198,16 @@ Lets build great software together. [![ayushpahwa](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/8526215?v=4&w=50&h=50&mask=circle)](https://github.com/ayushpahwa) [![Parthvi12](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/80334441?v=4&w=50&h=50&mask=circle)](https://github.com/Parthvi12) [![ashit-rath](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/88306433?v=4&w=50&h=50&mask=circle)](https://github.com/ashit-rath) -[![areyabhishek](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/30255708?v=4&w=50&h=50&mask=circle)](https://github.com/areyabhishek) [![AmanAgarwal041](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/7565635?v=4&w=50&h=50&mask=circle)](https://github.com/AmanAgarwal041) +[![areyabhishek](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/30255708?v=4&w=50&h=50&mask=circle)](https://github.com/areyabhishek) [![rimildeyjsr](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/10229595?v=4&w=50&h=50&mask=circle)](https://github.com/rimildeyjsr) -[![cokoghenun](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17744578?v=4&w=50&h=50&mask=circle)](https://github.com/cokoghenun) [![ankurrsinghal](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17961105?v=4&w=50&h=50&mask=circle)](https://github.com/ankurrsinghal) +[![cokoghenun](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17744578?v=4&w=50&h=50&mask=circle)](https://github.com/cokoghenun) [![vishnu-gp](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/9128194?v=4&w=50&h=50&mask=circle)](https://github.com/vishnu-gp) [![keyurparalkar](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/14138515?v=4&w=50&h=50&mask=circle)](https://github.com/keyurparalkar) [![vihar](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/16307796?v=4&w=50&h=50&mask=circle)](https://github.com/vihar) -[![eco-monk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/66776129?v=4&w=50&h=50&mask=circle)](https://github.com/eco-monk) [![ChandanBalajiBP](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/104058110?v=4&w=50&h=50&mask=circle)](https://github.com/ChandanBalajiBP) +[![eco-monk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/66776129?v=4&w=50&h=50&mask=circle)](https://github.com/eco-monk) [![souma-ghosh](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/103924539?v=4&w=50&h=50&mask=circle)](https://github.com/souma-ghosh) [![subrata71](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/3524599?v=4&w=50&h=50&mask=circle)](https://github.com/subrata71) [![dhruvikn](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/22471214?v=4&w=50&h=50&mask=circle)](https://github.com/dhruvikn) @@ -215,23 +215,22 @@ Lets build great software together. [![tanvibhakta](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13763558?v=4&w=50&h=50&mask=circle)](https://github.com/tanvibhakta) [![berzerkeer](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/74818788?v=4&w=50&h=50&mask=circle)](https://github.com/berzerkeer) [![nsarupr](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20905988?v=4&w=50&h=50&mask=circle)](https://github.com/nsarupr) -[![sondermanish](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/107841575?v=4&w=50&h=50&mask=circle)](https://github.com/sondermanish) [![sneha122](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/30018882?v=4&w=50&h=50&mask=circle)](https://github.com/sneha122) [![pratapaprasanna](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/15846947?v=4&w=50&h=50&mask=circle)](https://github.com/pratapaprasanna) +[![sondermanish](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/107841575?v=4&w=50&h=50&mask=circle)](https://github.com/sondermanish) [![Pranay105](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48308728?v=4&w=50&h=50&mask=circle)](https://github.com/Pranay105) +[![iamrkcheers](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/16760643?v=4&w=50&h=50&mask=circle)](https://github.com/iamrkcheers) [![vaibh1297](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/40293928?v=4&w=50&h=50&mask=circle)](https://github.com/vaibh1297) [![ankitsrivas14](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/67647761?v=4&w=50&h=50&mask=circle)](https://github.com/ankitsrivas14) [![kocharrahul7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20532920?v=4&w=50&h=50&mask=circle)](https://github.com/kocharrahul7) [![rohitagarwal88](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/890915?v=4&w=50&h=50&mask=circle)](https://github.com/rohitagarwal88) [![ramsaptami](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/79509062?v=4&w=50&h=50&mask=circle)](https://github.com/ramsaptami) +[![ravikp7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13567359?v=4&w=50&h=50&mask=circle)](https://github.com/ravikp7) [![AS-Laguna](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/101155659?v=4&w=50&h=50&mask=circle)](https://github.com/AS-Laguna) [![NilanshBansal](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25542733?v=4&w=50&h=50&mask=circle)](https://github.com/NilanshBansal) [![RakshaKShetty](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/45958978?v=4&w=50&h=50&mask=circle)](https://github.com/RakshaKShetty) -[![ravikp7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13567359?v=4&w=50&h=50&mask=circle)](https://github.com/ravikp7) -[![iamrkcheers](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/16760643?v=4&w=50&h=50&mask=circle)](https://github.com/iamrkcheers) [![Rishabhkaul](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1650391?v=4&w=50&h=50&mask=circle)](https://github.com/Rishabhkaul) [![rohan-arthur](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/94514895?v=4&w=50&h=50&mask=circle)](https://github.com/rohan-arthur) -[![somnathdasadhikari](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17508328?v=4&w=50&h=50&mask=circle)](https://github.com/somnathdasadhikari) [![hiteshjoshi](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/217911?v=4&w=50&h=50&mask=circle)](https://github.com/hiteshjoshi) [![rlnorthcutt](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/673633?v=4&w=50&h=50&mask=circle)](https://github.com/rlnorthcutt) [![vuiets](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1571370?v=4&w=50&h=50&mask=circle)](https://github.com/vuiets) @@ -245,6 +244,7 @@ Lets build great software together. [![riteshkew](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20280935?v=4&w=50&h=50&mask=circle)](https://github.com/riteshkew) [![andrewdietekoki](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/23283551?v=4&w=50&h=50&mask=circle)](https://github.com/andrewdietekoki) [![GreenFlux](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/24459976?v=4&w=50&h=50&mask=circle)](https://github.com/GreenFlux) +[![vivonk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25587962?v=4&w=50&h=50&mask=circle)](https://github.com/vivonk) [![danciaclara](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/32227135?v=4&w=50&h=50&mask=circle)](https://github.com/danciaclara) [![Debsourabh](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/34486435?v=4&w=50&h=50&mask=circle)](https://github.com/Debsourabh) [![tejasahluwalia](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/39881648?v=4&w=50&h=50&mask=circle)](https://github.com/tejasahluwalia) @@ -390,6 +390,7 @@ Lets build great software together. [![Saket2](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/49346036?v=4&w=50&h=50&mask=circle)](https://github.com/Saket2) [![withshubh](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25361949?v=4&w=50&h=50&mask=circle)](https://github.com/withshubh) [![smrutiparida](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5945400?v=4&w=50&h=50&mask=circle)](https://github.com/smrutiparida) +[![somnathdasadhikari](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17508328?v=4&w=50&h=50&mask=circle)](https://github.com/somnathdasadhikari) [![srijanshetty](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1744347?v=4&w=50&h=50&mask=circle)](https://github.com/srijanshetty) [![Sufiyan1997](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/22118049?v=4&w=50&h=50&mask=circle)](https://github.com/Sufiyan1997) [![rayrny](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48341341?v=4&w=50&h=50&mask=circle)](https://github.com/rayrny) diff --git a/app/client/craco.build.config.js b/app/client/craco.build.config.js index 9d5f3b698e..608c5e731f 100644 --- a/app/client/craco.build.config.js +++ b/app/client/craco.build.config.js @@ -15,7 +15,7 @@ plugins.push( swSrc: "./src/serviceWorker.js", mode: "development", swDest: "./pageService.js", - maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, + maximumFileSizeToCacheInBytes: 11 * 1024 * 1024, }), ); diff --git a/app/client/cypress/fixtures/Bugs/CheckboxGroupInListWidgetDsl.json b/app/client/cypress/fixtures/Bugs/CheckboxGroupInListWidgetDsl.json new file mode 100644 index 0000000000..70a0677350 --- /dev/null +++ b/app/client/cypress/fixtures/Bugs/CheckboxGroupInListWidgetDsl.json @@ -0,0 +1,857 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 560.0, + "containerStyle": "none", + "snapRows": 125.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69.0, + "minHeight": 1292.0, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "template": { + "Image1": { + "isVisible": true, + "defaultImage": "https://assets.appsmith.com/widgets/default.png", + "imageShape": "RECTANGLE", + "maxZoomLevel": 1.0, + "enableRotation": false, + "enableDownload": false, + "objectFit": "cover", + "image": "{{List1.listData.map((currentItem) => currentItem.img)}}", + "widgetName": "Image1", + "version": 1.0, + "animateLoading": true, + "type": "IMAGE_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Image", + "key": "n1szm8g77z", + "iconSVG": "/static/media/icon.52d8fb963abcb95c79b10f1553389f22.svg", + "boxShadow": "none", + "dynamicBindingPathList": [ + { + "key": "image" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [], + "widgetId": "852qrq5vm1", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 0.0, + "rightColumn": 16.0, + "topRow": 0.0, + "bottomRow": 8.0, + "parentId": "7z0hh0zvos" + }, + "Text1": { + "isVisible": true, + "text": "{{List1.listData.map((currentItem) => currentItem.name)}}", + "fontSize": "1rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "shouldTruncate": false, + "overflow": "NONE", + "version": 1.0, + "animateLoading": true, + "minDynamicHeight": 4.0, + "maxDynamicHeight": 9000.0, + "dynamicHeight": "AUTO_HEIGHT", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "tf4er66bom", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "textStyle": "HEADING", + "boxShadow": "none", + "dynamicBindingPathList": [ + { + "key": "text" + }, + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [], + "widgetId": "1sfn18h25j", + "renderMode": "CANVAS", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 16.0, + "rightColumn": 28.0, + "topRow": 0.0, + "bottomRow": 4.0, + "parentId": "7z0hh0zvos" + }, + "Text2": { + "isVisible": true, + "text": "{{List1.listData.map((currentItem) => currentItem.id)}}", + "fontSize": "1rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text2", + "shouldTruncate": false, + "overflow": "NONE", + "version": 1.0, + "animateLoading": true, + "minDynamicHeight": 4.0, + "maxDynamicHeight": 9000.0, + "dynamicHeight": "AUTO_HEIGHT", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "tf4er66bom", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "textStyle": "BODY", + "boxShadow": "none", + "dynamicBindingPathList": [ + { + "key": "text" + }, + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [], + "widgetId": "ekx7bft1ux", + "renderMode": "CANVAS", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 16.0, + "rightColumn": 24.0, + "topRow": 4.0, + "bottomRow": 8.0, + "parentId": "7z0hh0zvos" + }, + "CheckboxGroup1": { + "isVisible": true, + "animateLoading": true, + "labelTextSize": "0.875rem", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "defaultSelectedValues": [ + "BLUE" + ], + "isDisabled": false, + "isInline": true, + "isRequired": false, + "labelText": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelWidth": 5.0, + "widgetName": "CheckboxGroup1", + "version": 2.0, + "minDynamicHeight": 4.0, + "maxDynamicHeight": 9000.0, + "dynamicHeight": "AUTO_HEIGHT", + "type": "CHECKBOX_GROUP_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Checkbox Group", + "key": "2ge4cq00z3", + "iconSVG": "/static/media/icon.ecb3847950c4515966ef642a32758afb.svg", + "widgetId": "30pveks53r", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 4.2734375, + "parentRowSpace": 10.0, + "leftColumn": 35.0, + "rightColumn": 58.0, + "topRow": 0.0, + "bottomRow": 6.0, + "parentId": "7z0hh0zvos", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "logBlackList": { + "isVisible": true, + "animateLoading": true, + "labelTextSize": true, + "options": true, + "defaultSelectedValues": true, + "isDisabled": true, + "isInline": true, + "isRequired": true, + "labelText": true, + "labelPosition": true, + "labelAlignment": true, + "labelWidth": true, + "widgetName": true, + "version": true, + "minDynamicHeight": true, + "maxDynamicHeight": true, + "dynamicHeight": true, + "searchTags": true, + "type": true, + "hideCard": true, + "isDeprecated": true, + "replacement": true, + "displayName": true, + "key": true, + "iconSVG": true, + "isCanvas": true, + "minHeight": true, + "widgetId": true, + "renderMode": true, + "accentColor": true, + "borderRadius": true, + "isLoading": true, + "parentColumnSpace": true, + "parentRowSpace": true, + "leftColumn": true, + "rightColumn": true, + "topRow": true, + "bottomRow": true, + "parentId": true, + "dynamicBindingPathList": true + } + } + }, + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "widgetName": "List1", + "listData": [ + { + "id": "001", + "name": "Blue", + "img": "https://assets.appsmith.com/widgets/default.png" + }, + { + "id": "002", + "name": "Green", + "img": "https://assets.appsmith.com/widgets/default.png" + }, + { + "id": "003", + "name": "Red", + "img": "https://assets.appsmith.com/widgets/default.png" + } + ], + "isCanvas": true, + "displayName": "List", + "iconSVG": "/static/media/icon.9925ee17dee37bf1ba7374412563a8a7.svg", + "topRow": 8.0, + "bottomRow": 48.0, + "parentRowSpace": 10.0, + "type": "LIST_WIDGET", + "hideCard": false, + "gridGap": 0.0, + "animateLoading": true, + "parentColumnSpace": 12.5625, + "dynamicTriggerPathList": [], + "leftColumn": 18.0, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + }, + { + "key": "template.Image1.image" + }, + { + "key": "template.Text1.text" + }, + { + "key": "template.Text2.text" + } + ], + "gridType": "vertical", + "enhancements": true, + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas1", + "displayName": "Canvas", + "topRow": 0.0, + "bottomRow": 400.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": false, + "hideCard": true, + "dropDisabled": true, + "openParentPropertyPane": true, + "minHeight": 400.0, + "noPad": true, + "parentColumnSpace": 1.0, + "leftColumn": 0.0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "widgetName": "Container1", + "borderColor": "#E0DEDE", + "disallowCopy": true, + "isCanvas": true, + "displayName": "Container", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "searchTags": [ + "div", + "parent", + "group" + ], + "topRow": 0.0, + "bottomRow": 12.0, + "dragDisabled": true, + "type": "CONTAINER_WIDGET", + "hideCard": false, + "openParentPropertyPane": true, + "shouldScrollContents": true, + "isDeletable": false, + "animateLoading": true, + "leftColumn": 0.0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas2", + "displayName": "Canvas", + "topRow": 0.0, + "bottomRow": 150.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": false, + "hideCard": true, + "minHeight": 150.0, + "parentColumnSpace": 1.0, + "leftColumn": 0.0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Image1", + "displayName": "Image", + "iconSVG": "/static/media/icon.52d8fb963abcb95c79b10f1553389f22.svg", + "topRow": 0.0, + "bottomRow": 8.0, + "type": "IMAGE_WIDGET", + "hideCard": false, + "animateLoading": true, + "dynamicTriggerPathList": [], + "imageShape": "RECTANGLE", + "dynamicBindingPathList": [ + { + "key": "image" + }, + { + "key": "borderRadius" + } + ], + "leftColumn": 0.0, + "defaultImage": "https://assets.appsmith.com/widgets/default.png", + "key": "n1szm8g77z", + "image": "{{currentItem.img}}", + "isDeprecated": false, + "rightColumn": 16.0, + "objectFit": "cover", + "widgetId": "852qrq5vm1", + "logBlackList": { + "isVisible": true, + "defaultImage": true, + "imageShape": true, + "maxZoomLevel": true, + "enableRotation": true, + "enableDownload": true, + "objectFit": true, + "image": true, + "widgetName": true, + "version": true, + "animateLoading": true, + "searchTags": true, + "type": true, + "hideCard": true, + "isDeprecated": true, + "replacement": true, + "displayName": true, + "key": true, + "iconSVG": true, + "isCanvas": true, + "boxShadow": true, + "dynamicBindingPathList": true, + "dynamicTriggerPathList": true, + "minHeight": true, + "widgetId": true, + "renderMode": true, + "borderRadius": true, + "isLoading": true, + "parentColumnSpace": true, + "parentRowSpace": true, + "leftColumn": true, + "rightColumn": true, + "topRow": true, + "bottomRow": true, + "parentId": true + }, + "isVisible": true, + "version": 1.0, + "parentId": "7z0hh0zvos", + "renderMode": "CANVAS", + "isLoading": false, + "maxZoomLevel": 1.0, + "enableDownload": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "enableRotation": false + }, + { + "boxShadow": "none", + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 0.0, + "bottomRow": 4.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "dynamicTriggerPathList": [], + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "dynamicBindingPathList": [ + { + "key": "text" + }, + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "leftColumn": 16.0, + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{currentItem.name}}", + "key": "tf4er66bom", + "isDeprecated": false, + "rightColumn": 28.0, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "1sfn18h25j", + "logBlackList": { + "isVisible": true, + "text": true, + "fontSize": true, + "fontStyle": true, + "textAlign": true, + "textColor": true, + "widgetName": true, + "shouldTruncate": true, + "overflow": true, + "version": true, + "animateLoading": true, + "minDynamicHeight": true, + "maxDynamicHeight": true, + "dynamicHeight": true, + "searchTags": true, + "type": true, + "hideCard": true, + "isDeprecated": true, + "replacement": true, + "displayName": true, + "key": true, + "iconSVG": true, + "isCanvas": true, + "textStyle": true, + "boxShadow": true, + "dynamicBindingPathList": true, + "dynamicTriggerPathList": true, + "minHeight": true, + "widgetId": true, + "renderMode": true, + "truncateButtonColor": true, + "fontFamily": true, + "borderRadius": true, + "isLoading": true, + "parentColumnSpace": true, + "parentRowSpace": true, + "leftColumn": true, + "rightColumn": true, + "topRow": true, + "bottomRow": true, + "parentId": true + }, + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "7z0hh0zvos", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000.0, + "fontSize": "1rem", + "textStyle": "HEADING", + "minDynamicHeight": 4.0 + }, + { + "boxShadow": "none", + "widgetName": "Text2", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 4.0, + "bottomRow": 9.0, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "dynamicTriggerPathList": [], + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "dynamicBindingPathList": [ + { + "key": "text" + }, + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "leftColumn": 16.0, + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{currentItem.id}}", + "key": "tf4er66bom", + "isDeprecated": false, + "rightColumn": 24.0, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "ekx7bft1ux", + "logBlackList": { + "isVisible": true, + "text": true, + "fontSize": true, + "fontStyle": true, + "textAlign": true, + "textColor": true, + "widgetName": true, + "shouldTruncate": true, + "overflow": true, + "version": true, + "animateLoading": true, + "minDynamicHeight": true, + "maxDynamicHeight": true, + "dynamicHeight": true, + "searchTags": true, + "type": true, + "hideCard": true, + "isDeprecated": true, + "replacement": true, + "displayName": true, + "key": true, + "iconSVG": true, + "isCanvas": true, + "textStyle": true, + "boxShadow": true, + "dynamicBindingPathList": true, + "dynamicTriggerPathList": true, + "minHeight": true, + "widgetId": true, + "renderMode": true, + "truncateButtonColor": true, + "fontFamily": true, + "borderRadius": true, + "isLoading": true, + "parentColumnSpace": true, + "parentRowSpace": true, + "leftColumn": true, + "rightColumn": true, + "topRow": true, + "bottomRow": true, + "parentId": true + }, + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1.0, + "parentId": "7z0hh0zvos", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 4.0, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000.0, + "originalBottomRow": 9.0, + "fontSize": "1rem", + "textStyle": "BODY", + "minDynamicHeight": 4.0 + }, + { + "widgetName": "CheckboxGroup1", + "displayName": "Checkbox Group", + "iconSVG": "/static/media/icon.ecb3847950c4515966ef642a32758afb.svg", + "labelText": "Label", + "topRow": 0.0, + "bottomRow": 13.0, + "parentRowSpace": 10.0, + "labelWidth": 5.0, + "type": "CHECKBOX_GROUP_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 4.2734375, + "leftColumn": 35.0, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "labelPosition": "Top", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "isDisabled": false, + "key": "2ge4cq00z3", + "labelTextSize": "0.875rem", + "isRequired": false, + "isDeprecated": false, + "rightColumn": 58.0, + "defaultSelectedValues": [ + "BLUE" + ], + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "30pveks53r", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "logBlackList": { + "isVisible": true, + "animateLoading": true, + "labelTextSize": true, + "options": true, + "defaultSelectedValues": true, + "isDisabled": true, + "isInline": true, + "isRequired": true, + "labelText": true, + "labelPosition": true, + "labelAlignment": true, + "labelWidth": true, + "widgetName": true, + "version": true, + "minDynamicHeight": true, + "maxDynamicHeight": true, + "dynamicHeight": true, + "searchTags": true, + "type": true, + "hideCard": true, + "isDeprecated": true, + "replacement": true, + "displayName": true, + "key": true, + "iconSVG": true, + "isCanvas": true, + "minHeight": true, + "widgetId": true, + "renderMode": true, + "accentColor": true, + "borderRadius": true, + "isLoading": true, + "parentColumnSpace": true, + "parentRowSpace": true, + "leftColumn": true, + "rightColumn": true, + "topRow": true, + "bottomRow": true, + "parentId": true, + "dynamicBindingPathList": true + }, + "isVisible": true, + "version": 2.0, + "parentId": "7z0hh0zvos", + "labelAlignment": "left", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 0.0, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000.0, + "originalBottomRow": 6.0, + "isInline": true, + "minDynamicHeight": 4.0 + } + ], + "key": "8xilm9v7l4", + "isDeprecated": false, + "detachFromLayout": true, + "widgetId": "7z0hh0zvos", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "containerStyle": "none", + "isVisible": true, + "version": 1.0, + "parentId": "s5iecr6n8i", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "borderWidth": "1", + "key": "h9n2njehd3", + "disablePropertyPane": true, + "backgroundColor": "white", + "isDeprecated": false, + "rightColumn": 64.0, + "dynamicHeight": "FIXED", + "widgetId": "s5iecr6n8i", + "containerStyle": "card", + "isVisible": true, + "version": 1.0, + "parentId": "9iszxoqodt", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWidgetFeatures": [ + "dynamicHeight" + ], + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000.0, + "minDynamicHeight": 10.0 + } + ], + "key": "8xilm9v7l4", + "isDeprecated": false, + "rightColumn": 301.5, + "detachFromLayout": true, + "widgetId": "9iszxoqodt", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "containerStyle": "none", + "isVisible": true, + "version": 1.0, + "parentId": "3c9elfu0p5", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "privateWidgets": { + "undefined": true + }, + "key": "5a7y7jpy4y", + "backgroundColor": "transparent", + "isDeprecated": false, + "rightColumn": 42.0, + "itemBackgroundColor": "#FFFFFF", + "widgetId": "3c9elfu0p5", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": true, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/alignmentWithDynamicHeightDsl.json b/app/client/cypress/fixtures/alignmentWithDynamicHeightDsl.json new file mode 100644 index 0000000000..2bfc5b4714 --- /dev/null +++ b/app/client/cypress/fixtures/alignmentWithDynamicHeightDsl.json @@ -0,0 +1,426 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 2670, + "containerStyle": "none", + "snapRows": 121, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1220, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Text1", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 17, + "bottomRow": 82, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [], + "leftColumn": 5, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{appsmith.store.text}}", + "key": "9ccwuxni06", + "isDeprecated": false, + "rightColumn": 57, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "l3kr1x82zd", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 17, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "originalBottomRow": 22, + "fontSize": "1rem", + "minDynamicHeight": 4 + }, + { + "widgetName": "Text3", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 83, + "bottomRow": 165, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [], + "leftColumn": 27, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{appsmith.store.text}}", + "key": "9ccwuxni06", + "isDeprecated": false, + "rightColumn": 64, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "o3bwh27p3h", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 23, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "originalBottomRow": 28, + "fontSize": "1rem", + "minDynamicHeight": 4 + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button2", + "onClick": "{{storeValue('text', `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,`)}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "dynamicPropertyPathList": [ + { + "key": "onClick" + } + ], + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 89, + "bottomRow": 93, + "parentRowSpace": 10, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [ + { + "key": "onClick" + } + ], + "leftColumn": 5, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "text": "Small", + "isDisabled": false, + "key": "gpamqfjowi", + "isDeprecated": false, + "rightColumn": 21, + "isDefaultClickDisabled": true, + "widgetId": "pgpxxkqql0", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 29, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "originalBottomRow": 33, + "buttonVariant": "PRIMARY", + "placement": "CENTER" + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button1", + "onClick": "{{storeValue('text', `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum`)}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "dynamicPropertyPathList": [ + { + "key": "onClick" + } + ], + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 101, + "bottomRow": 105, + "parentRowSpace": 10, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [ + { + "key": "onClick" + } + ], + "leftColumn": 4, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "text": "Medium", + "isDisabled": false, + "key": "gpamqfjowi", + "isDeprecated": false, + "rightColumn": 20, + "isDefaultClickDisabled": true, + "widgetId": "oh3y8w2j1p", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 34, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "originalBottomRow": 38, + "buttonVariant": "PRIMARY", + "placement": "CENTER" + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button1Copy", + "onClick": "{{storeValue('text', `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\n\nWhy do we use it?\nIt is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).\n\n\nWhere does it come from?\nContrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of \"de Finibus Bonorum et Malorum\" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, \"Lorem ipsum dolor sit amet..\", comes from a line in section 1.10.32.\n\nThe standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from \"de Finibus Bonorum et Malorum\" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.\n\nWhere can I get some?\nThere are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model senten`)}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "dynamicPropertyPathList": [ + { + "key": "onClick" + } + ], + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 167, + "bottomRow": 171, + "parentRowSpace": 10, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [ + { + "key": "onClick" + } + ], + "leftColumn": 28, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "text": "Large", + "isDisabled": false, + "key": "gpamqfjowi", + "isDeprecated": false, + "rightColumn": 44, + "isDefaultClickDisabled": true, + "widgetId": "ter388gte5", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 39, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "originalBottomRow": 43, + "buttonVariant": "PRIMARY", + "placement": "CENTER" + }, + { + "widgetName": "Text2", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 181, + "bottomRow": 251, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [], + "leftColumn": 5, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{appsmith.store.text}}", + "key": "9ccwuxni06", + "isDeprecated": false, + "rightColumn": 53, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "22trl91ovs", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 44, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "originalBottomRow": 49, + "fontSize": "1rem", + "minDynamicHeight": 4 + }, + { + "widgetName": "Text4", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 187, + "bottomRow": 259, + "parentRowSpace": 10, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "parentColumnSpace": 15.265625, + "dynamicTriggerPathList": [], + "leftColumn": 16, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + }, + { + "key": "text" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "{{appsmith.store.text}}", + "key": "9ccwuxni06", + "isDeprecated": false, + "rightColumn": 60, + "textAlign": "LEFT", + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "wdz3pjzsi5", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 50, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "originalBottomRow": 55, + "fontSize": "1rem", + "minDynamicHeight": 4 + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/dynamicHeightContainerCheckboxdsl.json b/app/client/cypress/fixtures/dynamicHeightContainerCheckboxdsl.json new file mode 100644 index 0000000000..a945fec106 --- /dev/null +++ b/app/client/cypress/fixtures/dynamicHeightContainerCheckboxdsl.json @@ -0,0 +1,170 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1290, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "widgetName": "Container1", + "borderColor": "#E0DEDE", + "isCanvas": true, + "displayName": "Container", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "searchTags": [ + "div", + "parent", + "group" + ], + "topRow": 6, + "bottomRow": 16, + "parentRowSpace": 10, + "type": "CONTAINER_WIDGET", + "hideCard": false, + "shouldScrollContents": true, + "animateLoading": true, + "parentColumnSpace": 11.9375, + "leftColumn": 16, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas1", + "displayName": "Canvas", + "topRow": 0, + "bottomRow": 100, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": false, + "hideCard": true, + "minHeight": 100, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "isVisible": true, + "animateLoading": true, + "labelTextSize": "0.875rem", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "defaultSelectedValues": [ + "BLUE" + ], + "isDisabled": false, + "isInline": true, + "isRequired": false, + "labelText": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelWidth": 5, + "widgetName": "CheckboxGroup1", + "version": 2, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "type": "CHECKBOX_GROUP_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Checkbox Group", + "key": "px8e5kndcb", + "iconSVG": "/static/media/icon.ecb3847950c4515966ef642a32758afb.svg", + "widgetId": "li1gq4tzny", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 3.42578125, + "parentRowSpace": 10, + "leftColumn": 18, + "rightColumn": 41, + "topRow": 0, + "bottomRow": 6, + "parentId": "tbezx4vcxu", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ] + } + ], + "key": "49f4d77rwd", + "isDeprecated": false, + "rightColumn": 286.5, + "detachFromLayout": true, + "widgetId": "tbezx4vcxu", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "containerStyle": "none", + "isVisible": true, + "version": 1, + "parentId": "57nv0ufxq1", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "borderWidth": "1", + "key": "g4phrz9m3l", + "backgroundColor": "#FFFFFF", + "isDeprecated": false, + "rightColumn": 40, + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "57nv0ufxq1", + "containerStyle": "card", + "isVisible": true, + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "minDynamicHeight": 10 + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/dynamicHeightContainerdsl.json b/app/client/cypress/fixtures/dynamicHeightContainerdsl.json new file mode 100644 index 0000000000..a59bd714fb --- /dev/null +++ b/app/client/cypress/fixtures/dynamicHeightContainerdsl.json @@ -0,0 +1,109 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1292, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "backgroundColor": "#FFFFFF", + "widgetName": "Container1", + "containerStyle": "card", + "borderColor": "#E0DEDE", + "borderWidth": "1", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "animateLoading": true, + "children": [ + { + "isVisible": true, + "widgetName": "Canvas1", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "49f4d77rwd", + "containerStyle": "none", + "canExtend": false, + "children": [], + "minHeight": 100, + "widgetId": "tbezx4vcxu", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 286.5, + "topRow": 0, + "bottomRow": 100, + "parentId": "57nv0ufxq1", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 1, + "minDynamicHeight": 10, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "searchTags": [ + "div", + "parent", + "group" + ], + "type": "CONTAINER_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Container", + "key": "g4phrz9m3l", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "isCanvas": true, + "widgetId": "57nv0ufxq1", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 11.9375, + "parentRowSpace": 10, + "leftColumn": 20, + "rightColumn": 44, + "topRow": 23, + "bottomRow": 33, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/dynamicHeightFormSwitchdsl.json b/app/client/cypress/fixtures/dynamicHeightFormSwitchdsl.json new file mode 100644 index 0000000000..37bb35fb26 --- /dev/null +++ b/app/client/cypress/fixtures/dynamicHeightFormSwitchdsl.json @@ -0,0 +1,481 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1290, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "FIXED", + "shouldScrollContents": true, + "borderColor": "#E0DEDE", + "borderWidth": "1", + "animateLoading": true, + "widgetName": "Form1", + "backgroundColor": "#FFFFFF", + "children": [ + { + "isVisible": true, + "widgetName": "Canvas1", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "74zfu9g19a", + "containerStyle": "none", + "canExtend": false, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "FIXED", + "overflow": "NONE", + "text": "Form", + "fontSize": "1.25rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "shouldTruncate": false, + "version": 1, + "animateLoading": true, + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "qjy37lgbc3", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "widgetId": "8c07j99hsy", + "renderMode": "CANVAS", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 1.5, + "rightColumn": 25.5, + "topRow": 1, + "bottomRow": 5, + "parentId": "5fswq5m00l", + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "FIXED", + "widgetName": "SwitchGroup1", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "defaultSelectedValues": [ + "BLUE" + ], + "isDisabled": false, + "isRequired": false, + "isInline": true, + "animateLoading": true, + "alignment": "left", + "labelText": "Label", + "labelPosition": "Left", + "labelAlignment": "left", + "labelWidth": 5, + "version": 1, + "labelTextSize": "0.875rem", + "type": "SWITCH_GROUP_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Switch Group", + "key": "gu5yyvgl2n", + "iconSVG": "/static/media/icon.c98225eee52c61080cd91042d88545c8.svg", + "isCanvas": false, + "widgetId": "k6h69sgn79", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 4.1640625, + "parentRowSpace": 10, + "leftColumn": 9, + "rightColumn": 35, + "topRow": 27, + "bottomRow": 31, + "parentId": "5fswq5m00l", + "dynamicBindingPathList": [ + { + "key": "accentColor" + } + ], + "onSelectionChange": "{{showModal('Modal1')}}", + "dynamicTriggerPathList": [ + { + "key": "onSelectionChange" + } + ] + } + ], + "minHeight": 310, + "widgetId": "5fswq5m00l", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 286.5, + "topRow": 0, + "bottomRow": 310, + "parentId": "qx47bk6wx8", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "searchTags": [ + "group" + ], + "type": "FORM_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Form", + "key": "d7jgl29xqe", + "iconSVG": "/static/media/icon.ea3e08d130e59c56867ae40114c10eed.svg", + "isCanvas": true, + "widgetId": "qx47bk6wx8", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "isLoading": false, + "parentColumnSpace": 11.9375, + "parentRowSpace": 10, + "leftColumn": 14, + "rightColumn": 38, + "topRow": 10, + "bottomRow": 43, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + }, + { + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "width": 456, + "height": 240, + "canEscapeKeyClose": true, + "animateLoading": true, + "detachFromLayout": true, + "canOutsideClickClose": true, + "widgetName": "Modal1", + "children": [ + { + "isVisible": true, + "widgetName": "Canvas2", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "74zfu9g19a", + "canExtend": true, + "isDisabled": false, + "shouldScrollContents": false, + "children": [ + { + "isVisible": true, + "iconName": "cross", + "buttonVariant": "TERTIARY", + "isDisabled": false, + "widgetName": "IconButton1", + "version": 1, + "animateLoading": true, + "searchTags": [ + "click", + "submit" + ], + "type": "ICON_BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Icon Button", + "key": "oipik9z0jf", + "iconSVG": "/static/media/icon.1a0c634ac75f9fa6b6ae7a8df882a3ba.svg", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "iconSize": 24, + "widgetId": "h84z4pqq7d", + "renderMode": "CANVAS", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 58, + "rightColumn": 64, + "topRow": 0, + "bottomRow": 4, + "parentId": "b8h15fewxw", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "onClick": "{{closeModal('Modal1')}}" + }, + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "FIXED", + "overflow": "NONE", + "text": "Modal Title", + "fontSize": "1.5rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text2", + "shouldTruncate": false, + "version": 1, + "animateLoading": true, + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "qjy37lgbc3", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "widgetId": "j98rl5e7a1", + "renderMode": "CANVAS", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 1, + "rightColumn": 41, + "topRow": 1, + "bottomRow": 5, + "parentId": "b8h15fewxw", + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Close", + "buttonVariant": "SECONDARY", + "placement": "CENTER", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": false, + "resetFormOnClick": false, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "8fy9at549y", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "buttonStyle": "PRIMARY", + "widgetId": "w3gw0z0hul", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 31, + "rightColumn": 47, + "topRow": 18, + "bottomRow": 22, + "parentId": "b8h15fewxw", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "onClick": "{{closeModal('Modal1')}}" + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Confirm", + "buttonVariant": "PRIMARY", + "placement": "CENTER", + "widgetName": "Button2", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": false, + "resetFormOnClick": false, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "8fy9at549y", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "buttonStyle": "PRIMARY_BUTTON", + "widgetId": "dcky71d9uv", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 47, + "rightColumn": 63, + "topRow": 18, + "bottomRow": 22, + "parentId": "b8h15fewxw", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ] + } + ], + "minHeight": 0, + "widgetId": "b8h15fewxw", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 0, + "topRow": 0, + "bottomRow": 0, + "parentId": "d0r5dcpeqm", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 2, + "searchTags": [ + "dialog", + "popup", + "notification" + ], + "type": "MODAL_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Modal", + "key": "1y0e1crldi", + "iconSVG": "/static/media/icon.4975978e9a961fb0bfb4e38de7ecc7c5.svg", + "isCanvas": true, + "widgetId": "d0r5dcpeqm", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 0, + "topRow": 0, + "bottomRow": 0, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/dynamicHeightStatboxdsl.json b/app/client/cypress/fixtures/dynamicHeightStatboxdsl.json new file mode 100644 index 0000000000..d1b9422ba4 --- /dev/null +++ b/app/client/cypress/fixtures/dynamicHeightStatboxdsl.json @@ -0,0 +1,281 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1280, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1230, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1240, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "widgetName": "Statbox1", + "backgroundColor": "white", + "rightColumn": 21, + "dynamicHeight": "FIXED", + "widgetId": "3sii8uhhjs", + "topRow": 17, + "bottomRow": 33, + "parentRowSpace": 10, + "isVisible": true, + "type": "STATBOX_WIDGET", + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 19.8125, + "leftColumn": 5, + "borderRadius": "0px", + "children": [ + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "widgetName": "Canvas1", + "rightColumn": 317, + "detachFromLayout": true, + "widgetId": "l752czyef7", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 160, + "parentRowSpace": 1, + "isVisible": true, + "canExtend": false, + "type": "CANVAS_WIDGET", + "version": 1, + "parentId": "3sii8uhhjs", + "minHeight": 160, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "borderRadius": "0px", + "children": [ + { + "boxShadow": "none", + "widgetName": "Text1", + "dynamicPropertyPathList": [ + { + "key": "fontSize" + } + ], + "topRow": 0.5, + "bottomRow": 4.5, + "type": "TEXT_WIDGET", + "overflow": "NONE", + "fontFamily": "System Default", + "dynamicTriggerPathList": [], + "leftColumn": 1.5, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "text": "{{MockApi.data.users[0].id}}", + "labelTextSize": "0.875rem", + "rightColumn": 37.5, + "textAlign": "LEFT", + "dynamicHeight": "FIXED", + "widgetId": "4mtayc9eas", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#999999", + "version": 1, + "parentId": "l752czyef7", + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "fontSize": "0.75rem", + "minDynamicHeight": 4 + }, + { + "boxShadow": "none", + "widgetName": "Text2", + "dynamicPropertyPathList": [ + { + "key": "fontSize" + } + ], + "topRow": 5.5, + "bottomRow": 9.5, + "type": "TEXT_WIDGET", + "overflow": "NONE", + "fontFamily": "System Default", + "leftColumn": 1.5, + "text": "2.6 M", + "labelTextSize": "0.875rem", + "rightColumn": 37.5, + "textAlign": "LEFT", + "dynamicHeight": "FIXED", + "widgetId": "ii2tk6m48f", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "l752czyef7", + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "fontSize": "1.5rem", + "minDynamicHeight": 4 + }, + { + "boxShadow": "none", + "widgetName": "Text3", + "dynamicPropertyPathList": [ + { + "key": "fontSize" + } + ], + "topRow": 10, + "bottomRow": 14, + "type": "TEXT_WIDGET", + "overflow": "NONE", + "fontFamily": "System Default", + "leftColumn": 1.5, + "text": "21% more than last month", + "labelTextSize": "0.875rem", + "rightColumn": 37.5, + "textAlign": "LEFT", + "dynamicHeight": "FIXED", + "widgetId": "ptbhksx9p1", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#03B365", + "version": 1, + "parentId": "l752czyef7", + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "fontSize": "0.75rem", + "minDynamicHeight": 4 + }, + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "widgetName": "IconButton1", + "rightColumn": 61, + "iconName": "arrow-top-right", + "buttonColor": "#03B365", + "dynamicPropertyPathList": [ + { + "key": "borderRadius" + } + ], + "widgetId": "x35ni1hugn", + "topRow": 3, + "bottomRow": 11, + "isVisible": true, + "type": "ICON_BUTTON_WIDGET", + "version": 1, + "parentId": "l752czyef7", + "isLoading": false, + "borderRadius": "9999px", + "leftColumn": 45, + "buttonVariant": "PRIMARY", + "isDisabled": false + } + ] + } + ], + "maxDynamicHeight": 9000, + "minDynamicHeight": 4 + }, + { + "isVisible": true, + "backgroundColor": "#FFFFFF", + "widgetName": "Container1", + "containerStyle": "card", + "borderColor": "#E0DEDE", + "borderWidth": "1", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "animateLoading": true, + "children": [ + { + "isVisible": true, + "widgetName": "Canvas2", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "vhu8oytsk7", + "containerStyle": "none", + "canExtend": false, + "children": [], + "minHeight": 580, + "widgetId": "p7wc0hnc0o", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 286.5, + "topRow": 0, + "bottomRow": 580, + "parentId": "q00fefx59g", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 1, + "minDynamicHeight": 10, + "maxDynamicHeight": 12, + "dynamicHeight": "FIXED", + "shouldScrollContents": true, + "searchTags": [ + "div", + "parent", + "group" + ], + "type": "CONTAINER_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Container", + "key": "fcexgq4024", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "isCanvas": true, + "widgetId": "q00fefx59g", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 11.9375, + "parentRowSpace": 10, + "leftColumn": 22, + "rightColumn": 64, + "topRow": 3, + "bottomRow": 61, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/dynamicTabWidgetdsl.json b/app/client/cypress/fixtures/dynamicTabWidgetdsl.json new file mode 100644 index 0000000000..4dde753fa7 --- /dev/null +++ b/app/client/cypress/fixtures/dynamicTabWidgetdsl.json @@ -0,0 +1,448 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 400, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 64, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "widgetName": "Tabs1", + "borderColor": "#E0DEDE", + "isCanvas": true, + "displayName": "Tabs", + "iconSVG": "/static/media/icon.74a6d653c8201e66f1cd367a3fba2657.svg", + "topRow": 1, + "bottomRow": 20, + "parentRowSpace": 10, + "type": "TABS_WIDGET", + "hideCard": false, + "shouldScrollContents": true, + "animateLoading": true, + "parentColumnSpace": 9.96875, + "leftColumn": 18, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "children": [ + { + "tabId": "tab1", + "boxShadow": "none", + "widgetName": "Canvas1", + "displayName": "Canvas", + "topRow": 0, + "bottomRow": 190, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "hideCard": true, + "shouldScrollContents": false, + "minHeight": 190, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "animateLoading": true, + "labelTextSize": "0.875rem", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "defaultSelectedValues": [ + "BLUE" + ], + "isDisabled": false, + "isInline": true, + "isRequired": false, + "labelText": "Label", + "labelPosition": "Left", + "labelAlignment": "left", + "labelWidth": 5, + "widgetName": "CheckboxGroup1", + "version": 2, + "type": "CHECKBOX_GROUP_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Checkbox Group", + "key": "2hr4hycu4v", + "iconSVG": "/static/media/icon.ecb3847950c4515966ef642a32758afb.svg", + "widgetId": "9uc345gnqg", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 3.42578125, + "parentRowSpace": 10, + "leftColumn": 13, + "rightColumn": 36, + "topRow": 9, + "bottomRow": 19, + "parentId": "zpypn0rx6a", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [] + } + ], + "isDisabled": false, + "key": "wibrs53af5", + "isDeprecated": false, + "tabName": "Tab 1", + "rightColumn": 239.25, + "detachFromLayout": true, + "widgetId": "zpypn0rx6a", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": true, + "version": 1, + "parentId": "ynm15ab6f2", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + }, + { + "tabId": "tab2", + "boxShadow": "none", + "widgetName": "Canvas2", + "displayName": "Canvas", + "topRow": 0, + "bottomRow": 190, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "hideCard": true, + "shouldScrollContents": false, + "minHeight": 190, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "borderColor": "#E0DEDE", + "borderWidth": "1", + "animateLoading": true, + "widgetName": "Form1", + "backgroundColor": "#FFFFFF", + "children": [ + { + "isVisible": true, + "widgetName": "Canvas3", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "xp8804b2ms", + "containerStyle": "none", + "canExtend": false, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 0, + "maxDynamicHeight": 0, + "dynamicHeight": "FIXED", + "text": "Form", + "fontSize": "1.25rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "widgetName": "Text1", + "shouldTruncate": false, + "overflow": "NONE", + "version": 1, + "animateLoading": true, + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "qjibufjufj", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "widgetId": "oltjpz4xge", + "renderMode": "CANVAS", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 1.5, + "rightColumn": 25.5, + "topRow": 1, + "bottomRow": 5, + "parentId": "gfw87mvf4s", + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Submit", + "buttonVariant": "PRIMARY", + "placement": "CENTER", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": true, + "resetFormOnClick": true, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "fkiknl12i3", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "widgetId": "jdvk4o9jli", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 46, + "rightColumn": 62, + "topRow": 33, + "bottomRow": 37, + "parentId": "gfw87mvf4s", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Reset", + "buttonVariant": "SECONDARY", + "placement": "CENTER", + "widgetName": "Button2", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": false, + "resetFormOnClick": true, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "fkiknl12i3", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "widgetId": "1u527b0c32", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 30, + "rightColumn": 46, + "topRow": 33, + "bottomRow": 37, + "parentId": "gfw87mvf4s", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ] + } + ], + "minHeight": 390, + "widgetId": "gfw87mvf4s", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 100.91015625, + "topRow": 0, + "bottomRow": 390, + "parentId": "hxyieil09m", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "searchTags": [ + "group" + ], + "type": "FORM_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Form", + "key": "23o06odb25", + "iconSVG": "/static/media/icon.ea3e08d130e59c56867ae40114c10eed.svg", + "isCanvas": true, + "widgetId": "hxyieil09m", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "isLoading": false, + "parentColumnSpace": 4.20458984375, + "parentRowSpace": 10, + "leftColumn": 11, + "rightColumn": 35, + "topRow": 1, + "bottomRow": 40, + "parentId": "gmq0wikmsj", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + } + ], + "isDisabled": false, + "key": "wibrs53af5", + "isDeprecated": false, + "tabName": "Tab 2", + "rightColumn": 239.25, + "detachFromLayout": true, + "widgetId": "gmq0wikmsj", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": true, + "version": 1, + "parentId": "ynm15ab6f2", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "borderWidth": 1, + "key": "hf8p55gs4n", + "backgroundColor": "#FFFFFF", + "isDeprecated": false, + "rightColumn": 47, + "dynamicHeight": "FIXED", + "widgetId": "ynm15ab6f2", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "defaultTab": "Tab 1", + "shouldShowTabs": true, + "tabsObj": { + "tab1": { + "label": "Tab 1", + "id": "tab1", + "widgetId": "zpypn0rx6a", + "isVisible": true, + "index": 0 + }, + "tab2": { + "label": "Tab 2", + "id": "tab2", + "widgetId": "gmq0wikmsj", + "isVisible": true, + "index": 1 + } + }, + "isVisible": true, + "version": 3, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "minDynamicHeight": 4, + "dynamicTriggerPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/formResetDsl.json b/app/client/cypress/fixtures/formResetDsl.json index 0e12eb600b..373c79a90e 100644 --- a/app/client/cypress/fixtures/formResetDsl.json +++ b/app/client/cypress/fixtures/formResetDsl.json @@ -2,225 +2,247 @@ "dsl": { "widgetName": "MainContainer", "backgroundColor": "none", - "rightColumn": 656, + "rightColumn": 4896, "snapColumns": 64, "detachFromLayout": true, "widgetId": "0", "topRow": 0, - "bottomRow": 5120, + "bottomRow": 1060, "containerStyle": "none", - "snapRows": 128, + "snapRows": 125, "parentRowSpace": 1, "type": "CANVAS_WIDGET", "canExtend": true, - "version": 52, - "minHeight": 640, + "version": 67, + "minHeight": 1292, + "dynamicTriggerPathList": [], "parentColumnSpace": 1, "dynamicBindingPathList": [], "leftColumn": 0, "children": [ { - "widgetName": "Form1", - "backgroundColor": "white", - "rightColumn": 64, - "widgetId": "ozm6zwjk4b", - "topRow": 0, - "bottomRow": 56, - "parentRowSpace": 40, "isVisible": true, - "type": "FORM_WIDGET", - "version": 1, - "parentId": "0", - "blueprint": { - "view": [ - { - "type": "CANVAS_WIDGET", - "position": { "top": 0, "left": 0 }, - "props": { - "containerStyle": "none", - "canExtend": false, - "detachFromLayout": true, - "children": [], - "blueprint": { - "view": [ - { - "type": "TEXT_WIDGET", - "size": { "rows": 1, "cols": 12 }, - "position": { "top": 0, "left": 0 }, - "props": { - "text": "Form", - "textStyle": "HEADING" - } - }, - { - "type": "FORM_BUTTON_WIDGET", - "size": { "rows": 1, "cols": 4 }, - "position": { "top": 11, "left": 12 }, - "props": { - "text": "Submit", - "buttonStyle": "PRIMARY_BUTTON", - "disabledWhenInvalid": true, - "resetFormOnClick": true - } - }, - { - "type": "FORM_BUTTON_WIDGET", - "size": { "rows": 1, "cols": 4 }, - "position": { "top": 11, "left": 8 }, - "props": { - "text": "Reset", - "buttonStyle": "SECONDARY_BUTTON", - "disabledWhenInvalid": false, - "resetFormOnClick": true - } - } - ] - } - } - } - ] - }, - "isLoading": false, - "parentColumnSpace": 74, - "leftColumn": 0, - "dynamicBindingPathList": [], + "borderColor": "#E0DEDE", + "borderWidth": "1", + "animateLoading": true, + "widgetName": "Form1", + "backgroundColor": "#FFFFFF", "children": [ { - "widgetName": "Canvas1", - "rightColumn": 2072, - "detachFromLayout": true, - "widgetId": "qrqizehc5b", - "containerStyle": "none", - "topRow": 0, - "bottomRow": 540, - "parentRowSpace": 1, "isVisible": true, - "canExtend": false, - "type": "CANVAS_WIDGET", + "widgetName": "Canvas1", "version": 1, - "parentId": "ozm6zwjk4b", - "blueprint": { - "view": [ - { - "type": "TEXT_WIDGET", - "size": { "rows": 1, "cols": 12 }, - "position": { "top": 0, "left": 0 }, - "props": { "text": "Form", "textStyle": "HEADING" } - }, - { - "type": "FORM_BUTTON_WIDGET", - "size": { "rows": 1, "cols": 4 }, - "position": { "top": 11, "left": 12 }, - "props": { - "text": "Submit", - "buttonStyle": "PRIMARY_BUTTON", - "disabledWhenInvalid": true, - "resetFormOnClick": true - } - }, - { - "type": "FORM_BUTTON_WIDGET", - "size": { "rows": 1, "cols": 4 }, - "position": { "top": 11, "left": 8 }, - "props": { - "text": "Reset", - "buttonStyle": "SECONDARY_BUTTON", - "disabledWhenInvalid": false, - "resetFormOnClick": true - } - } - ] - }, - "minHeight": 520, - "isLoading": false, - "parentColumnSpace": 1, - "leftColumn": 0, - "dynamicBindingPathList": [], + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "lz0d5g90t3", + "containerStyle": "none", + "canExtend": false, "children": [ { - "widgetName": "Text1", - "rightColumn": 48, - "textAlign": "LEFT", - "widgetId": "c481ah2q0i", - "topRow": 0, - "bottomRow": 4, "isVisible": true, - "type": "TEXT_WIDGET", + "text": "Form", + "fontSize": "1.25rem", "fontStyle": "BOLD", - "version": 1, + "textAlign": "LEFT", "textColor": "#231F20", - "parentId": "qrqizehc5b", - "isLoading": false, - "leftColumn": 0, - "dynamicBindingPathList": [], - "fontSize": "HEADING1", - "text": "Form" - }, - { - "resetFormOnClick": true, - "widgetName": "FormButton1", - "rightColumn": 64, - "isDefaultClickDisabled": true, - "buttonColor": "#03B365", - "widgetId": "zsu1y41p1e", - "topRow": 48, - "bottomRow": 52, - "isVisible": true, - "type": "FORM_BUTTON_WIDGET", + "widgetName": "Text1", + "shouldTruncate": false, + "overflow": "NONE", "version": 1, - "recaptchaType": "V3", - "parentId": "qrqizehc5b", - "isLoading": false, - "disabledWhenInvalid": true, - "leftColumn": 48, - "dynamicBindingPathList": [], - "buttonVariant": "PRIMARY", - "text": "Submit" - }, - { - "resetFormOnClick": true, - "widgetName": "FormButton2", - "rightColumn": 48, - "isDefaultClickDisabled": true, - "buttonColor": "#03B365", - "widgetId": "7o0r2rp3s1", - "topRow": 48, - "bottomRow": 52, - "isVisible": true, - "type": "FORM_BUTTON_WIDGET", - "version": 1, - "recaptchaType": "V3", - "parentId": "qrqizehc5b", - "isLoading": false, - "disabledWhenInvalid": false, - "leftColumn": 32, - "dynamicBindingPathList": [], - "buttonVariant": "PRIMARY", - "text": "Reset" - }, - { - "widgetName": "Table1", - "columnOrder": [ - "id", - "email", - "userName", - "productName", - "orderAmount" + "animateLoading": true, + "searchTags": [ + "typography", + "paragraph", + "label" ], - "isVisibleDownload": true, - "topRow": 4, - "bottomRow": 25, - "parentRowSpace": 10, - "isSortable": true, - "type": "TABLE_WIDGET", - "parentColumnSpace": 71.5, - "dynamicTriggerPathList": [], - "leftColumn": 0, + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "i5p29w8v0a", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "widgetId": "4c54s08v23", + "renderMode": "CANVAS", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "leftColumn": 1.5, + "rightColumn": 25.5, + "topRow": 1, + "bottomRow": 5, + "parentId": "muk1vy75ao", "dynamicBindingPathList": [ - { "key": "tableData" }, - { "key": "primaryColumns.id.computedValue" }, - { "key": "primaryColumns.email.computedValue" }, - { "key": "primaryColumns.userName.computedValue" }, + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Submit", + "buttonVariant": "PRIMARY", + "placement": "CENTER", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": true, + "resetFormOnClick": true, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "j7tqbmn5hc", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "widgetId": "ig1u4ut3pd", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 40, + "rightColumn": 51, + "topRow": 62, + "bottomRow": 66, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "text": "Reset", + "buttonVariant": "SECONDARY", + "placement": "CENTER", + "widgetName": "Button2", + "isDisabled": false, + "isDefaultClickDisabled": true, + "disabledWhenInvalid": false, + "resetFormOnClick": true, + "recaptchaType": "V3", + "version": 1, + "searchTags": [ + "click", + "submit" + ], + "type": "BUTTON_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Button", + "key": "j7tqbmn5hc", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "widgetId": "2atavwomlu", + "renderMode": "CANVAS", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "leftColumn": 29, + "rightColumn": 38, + "topRow": 62, + "bottomRow": 66, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ] + }, + { + "isVisible": true, + "animateLoading": true, + "defaultSelectedRowIndex": 0, + "defaultSelectedRowIndices": [ + 0 + ], + "label": "Data", + "widgetName": "Table1", + "searchKey": "", + "textSize": "0.875rem", + "horizontalAlignment": "LEFT", + "verticalAlignment": "CENTER", + "totalRecordsCount": 0, + "defaultPageSize": 0, + "dynamicPropertyPathList": [], + "borderColor": "#E0DEDE", + "borderWidth": "1", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + }, + { + "key": "childStylesheet.button.buttonColor" + }, + { + "key": "childStylesheet.button.borderRadius" + }, + { + "key": "childStylesheet.menuButton.menuColor" + }, + { + "key": "childStylesheet.menuButton.borderRadius" + }, + { + "key": "childStylesheet.iconButton.buttonColor" + }, + { + "key": "childStylesheet.iconButton.borderRadius" + }, + { + "key": "childStylesheet.editActions.saveButtonColor" + }, + { + "key": "childStylesheet.editActions.saveBorderRadius" + }, + { + "key": "childStylesheet.editActions.discardButtonColor" + }, + { + "key": "childStylesheet.editActions.discardBorderRadius" + }, + { + "key": "tableData" + }, + { + "key": "primaryColumns.id.computedValue" + }, + { + "key": "primaryColumns.email.computedValue" + }, + { + "key": "primaryColumns.userName.computedValue" + }, { "key": "primaryColumns.productName.computedValue" }, @@ -230,255 +252,473 @@ ], "primaryColumns": { "id": { + "allowCellWrapping": false, "index": 0, "width": 150, + "originalId": "id", "id": "id", + "alias": "id", "horizontalAlignment": "LEFT", "verticalAlignment": "CENTER", - "columnType": "text", - "textSize": "PARAGRAPH", - "fontStyle": "REGULAR", + "columnType": "number", + "textSize": "0.875rem", "enableFilter": true, "enableSort": true, "isVisible": true, "isDisabled": false, + "isCellEditable": false, + "isEditable": false, "isCellVisible": true, "isDerived": false, "label": "id", - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.id))}}" + "isSaveVisible": true, + "isDiscardVisible": true, + "computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"id\"]))}}", + "validation": {} }, "email": { + "allowCellWrapping": false, "index": 1, "width": 150, + "originalId": "email", "id": "email", + "alias": "email", "horizontalAlignment": "LEFT", "verticalAlignment": "CENTER", "columnType": "text", - "textSize": "PARAGRAPH", - "fontStyle": "REGULAR", + "textSize": "0.875rem", "enableFilter": true, "enableSort": true, "isVisible": true, "isDisabled": false, + "isCellEditable": false, + "isEditable": false, "isCellVisible": true, "isDerived": false, "label": "email", - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.email))}}" + "isSaveVisible": true, + "isDiscardVisible": true, + "computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"email\"]))}}", + "validation": {} }, "userName": { + "allowCellWrapping": false, "index": 2, "width": 150, + "originalId": "userName", "id": "userName", + "alias": "userName", "horizontalAlignment": "LEFT", "verticalAlignment": "CENTER", "columnType": "text", - "textSize": "PARAGRAPH", - "fontStyle": "REGULAR", + "textSize": "0.875rem", "enableFilter": true, "enableSort": true, "isVisible": true, "isDisabled": false, + "isCellEditable": false, + "isEditable": false, "isCellVisible": true, "isDerived": false, "label": "userName", - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.userName))}}" + "isSaveVisible": true, + "isDiscardVisible": true, + "computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"userName\"]))}}", + "validation": {} }, "productName": { + "allowCellWrapping": false, "index": 3, "width": 150, + "originalId": "productName", "id": "productName", + "alias": "productName", "horizontalAlignment": "LEFT", "verticalAlignment": "CENTER", "columnType": "text", - "textSize": "PARAGRAPH", - "fontStyle": "REGULAR", + "textSize": "0.875rem", "enableFilter": true, "enableSort": true, "isVisible": true, "isDisabled": false, + "isCellEditable": false, + "isEditable": false, "isCellVisible": true, "isDerived": false, "label": "productName", - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.productName))}}" + "isSaveVisible": true, + "isDiscardVisible": true, + "computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"productName\"]))}}", + "validation": {} }, "orderAmount": { + "allowCellWrapping": false, "index": 4, "width": 150, + "originalId": "orderAmount", "id": "orderAmount", + "alias": "orderAmount", "horizontalAlignment": "LEFT", "verticalAlignment": "CENTER", - "columnType": "text", - "textSize": "PARAGRAPH", - "fontStyle": "REGULAR", + "columnType": "number", + "textSize": "0.875rem", "enableFilter": true, "enableSort": true, "isVisible": true, "isDisabled": false, + "isCellEditable": false, + "isEditable": false, "isCellVisible": true, "isDerived": false, "label": "orderAmount", - "computedValue": "{{Table1.sanitizedTableData.map((currentRow) => ( currentRow.orderAmount))}}" + "isSaveVisible": true, + "isDiscardVisible": true, + "computedValue": "{{Table1.processedTableData.map((currentRow, currentIndex) => ( currentRow[\"orderAmount\"]))}}", + "validation": {} } }, - "delimiter": ",", - "migrated": true, - "derivedColumns": {}, - "rightColumn": 64, - "textSize": "PARAGRAPH", - "widgetId": "xptqefixji", - "isVisibleFilters": true, "tableData": "{{[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]}}", - "isVisible": true, - "label": "Data", - "searchKey": "", - "fontStyle": "REGULAR", - "version": 3, - "parentId": "qrqizehc5b", - "isLoading": false, - "horizontalAlignment": "LEFT", + "columnWidthMap": { + "task": 245, + "step": 62, + "status": 75 + }, + "columnOrder": [ + "id", + "email", + "userName", + "productName", + "orderAmount" + ], + "enableClientSideSearch": true, "isVisibleSearch": true, + "isVisibleFilters": true, + "isVisibleDownload": true, "isVisiblePagination": true, - "verticalAlignment": "CENTER" - }, - { - "isRequired": true, - "widgetName": "Input1", - "rightColumn": 20, - "widgetId": "r3xvjtuhad", - "topRow": 30, - "bottomRow": 34, - "parentRowSpace": 40, - "isVisible": true, - "label": "", - "type": "INPUT_WIDGET_V2", + "isSortable": true, + "delimiter": ",", "version": 1, - "parentId": "qrqizehc5b", - "isLoading": false, - "parentColumnSpace": 71.5, - "dynamicTriggerPathList": [], - "leftColumn": 0, - "dynamicBindingPathList": [{ "key": "defaultText" }], - "resetOnSubmit": true, - "inputType": "TEXT", - "defaultText": "{{Table1.selectedRow.email}}" - }, - { - "widgetName": "Text2", - "rightColumn": 16, - "textAlign": "LEFT", - "widgetId": "672gf8vm2q", - "topRow": 26, - "bottomRow": 30, - "parentRowSpace": 40, - "isVisible": true, - "type": "TEXT_WIDGET", - "fontStyle": "BOLD", - "version": 1, - "textColor": "#231F20", - "parentId": "qrqizehc5b", - "isLoading": false, - "parentColumnSpace": 71.5, - "leftColumn": 0, - "dynamicBindingPathList": [], - "fontSize": "PARAGRAPH", - "text": "Email" - }, - { - "isRequired": true, - "rightColumn": 64, - "widgetName": "Dropdown1", - "widgetId": "dwh49bulj9", - "topRow": 36, - "bottomRow": 40, - "parentRowSpace": 40, - "isVisible": true, - "label": "", - "type": "MULTI_SELECT_WIDGET_V2", - "version": 1, - "parentId": "qrqizehc5b", - "isLoading": false, - "defaultOptionValue": "", - "selectionType": "MULTI_SELECT", - "dynamicTriggerPathList": [], - "parentColumnSpace": 71.5, - "dynamicBindingPathList": [], - "leftColumn": 24, - "options": "[\n {\n \"label\": \"Option 1\",\n \"value\": \"1\"\n },\n {\n \"label\": \"Option 2\",\n \"value\": \"2\"\n },\n {\n \"label\": \"Option 3\",\n \"value\": \"3\"\n },\n {\n \"label\": \"Option 4\",\n \"value\": \"4\"\n },\n {\n \"label\": \"Option 5\",\n \"value\": \"5\"\n }\n]" - }, - { - "widgetName": "PhoneInput1", - "dialCode": "+1", - "displayName": "Phone Input", - "iconSVG": "/static/media/icon.ec4f5c23.svg", - "topRow": 30, - "bottomRow": 34, - "parentRowSpace": 10, - "autoFocus": false, - "type": "PHONE_INPUT_WIDGET", + "inlineEditingSaveOption": "ROW_LEVEL", + "type": "TABLE_WIDGET_V2", "hideCard": false, - "animateLoading": true, - "parentColumnSpace": 9.75, - "dynamicTriggerPathList": [], - "resetOnSubmit": true, - "leftColumn": 24, - "dynamicBindingPathList": [{ "key": "defaultText" }], - "countryCode": "US", - "labelStyle": "", - "isDisabled": false, - "key": "2en0bgew0x", - "isRequired": true, - "rightColumn": 44, - "widgetId": "kqxx5c8dug", - "allowDialCodeChange": false, - "isVisible": true, - "label": "", - "version": 1, - "parentId": "qrqizehc5b", - "allowFormatting": true, + "isDeprecated": false, + "displayName": "Table", + "key": "0up8a0wm4i", + "iconSVG": "/static/media/icon.db8a9cbd2acd22a31ea91cc37ea2a46c.svg", + "widgetId": "ydkc7cm6w4", "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "childStylesheet": { + "button": { + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "menuButton": { + "menuColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "iconButton": { + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "editActions": { + "saveButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "saveBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "discardButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "discardBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + }, "isLoading": false, - "iconAlign": "left", - "defaultText": "{{Table1.selectedRow.id}}" + "parentColumnSpace": 7.4755859375, + "parentRowSpace": 10, + "leftColumn": 4, + "rightColumn": 55, + "topRow": 6, + "bottomRow": 34, + "parentId": "muk1vy75ao", + "dynamicTriggerPathList": [] }, { - "widgetName": "CurrencyInput1", - "displayName": "Currency Input", - "iconSVG": "/static/media/icon.01a1e03d.svg", - "topRow": 36, - "bottomRow": 40, - "parentRowSpace": 10, + "isVisible": true, + "label": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelTextSize": "0.875rem", + "labelWidth": 5, + "widgetName": "Input1", + "version": 2, + "defaultText": "{{Table1.selectedRow.email}}\n", + "iconAlign": "left", "autoFocus": false, + "labelStyle": "", + "resetOnSubmit": true, + "isRequired": false, + "isDisabled": false, + "animateLoading": true, + "inputType": "TEXT", + "searchTags": [ + "form", + "text input", + "number", + "textarea" + ], + "type": "INPUT_WIDGET_V2", + "hideCard": false, + "isDeprecated": false, + "displayName": "Input", + "key": "pm3gwntdcm", + "iconSVG": "/static/media/icon.9f505595da61a34f563dba82adeb06ec.svg", + "widgetId": "bp031y8spl", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "parentColumnSpace": 9.18896484375, + "parentRowSpace": 10, + "leftColumn": 6, + "rightColumn": 26, + "topRow": 38, + "bottomRow": 45, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "defaultText" + } + ], + "dynamicTriggerPathList": [] + }, + { + "isVisible": true, + "label": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelTextSize": "0.875rem", + "labelWidth": 5, + "widgetName": "CurrencyInput1", + "version": 1, + "defaultText": "{{Table1.selectedRow.orderAmount}}", + "iconAlign": "left", + "autoFocus": false, + "labelStyle": "", + "resetOnSubmit": true, + "isRequired": false, + "isDisabled": false, + "animateLoading": true, + "allowCurrencyChange": false, + "defaultCurrencyCode": "USD", + "decimals": 0, + "searchTags": [ + "amount", + "total" + ], "type": "CURRENCY_INPUT_WIDGET", "hideCard": false, - "animateLoading": true, - "parentColumnSpace": 9.75, - "dynamicTriggerPathList": [], - "resetOnSubmit": true, - "leftColumn": 0, - "dynamicBindingPathList": [{ "key": "defaultText" }], - "labelStyle": "", - "isDisabled": false, - "key": "8ked77j728", - "isRequired": true, - "rightColumn": 20, - "widgetId": "067zok1ef3", - "isVisible": true, - "label": "", - "allowCurrencyChange": false, - "version": 1, - "parentId": "qrqizehc5b", + "isDeprecated": false, + "displayName": "Currency Input", + "key": "x8yxtaq8v3", + "iconSVG": "/static/media/icon.f312efcb48ce4dafb08c20291635b30b.svg", + "widgetId": "s9twabojbt", "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", "isLoading": false, - "decimals": 2, + "parentColumnSpace": 9.18896484375, + "parentRowSpace": 10, + "leftColumn": 6, + "rightColumn": 26, + "topRow": 50, + "bottomRow": 57, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "defaultText" + } + ], + "dynamicTriggerPathList": [] + }, + { + "isVisible": true, + "label": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelTextSize": "0.875rem", + "labelWidth": 5, + "widgetName": "PhoneInput1", + "version": 1, + "defaultText": "{{Table1.selectedRow.id}}\n", "iconAlign": "left", - "defaultText": "{{Table1.selectedRow.orderAmount}}", - "currencyCode": "USD" + "autoFocus": false, + "labelStyle": "", + "resetOnSubmit": true, + "isRequired": false, + "isDisabled": false, + "animateLoading": true, + "defaultDialCode": "+1", + "allowDialCodeChange": false, + "allowFormatting": true, + "searchTags": [ + "call" + ], + "type": "PHONE_INPUT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Phone Input", + "key": "kjhejqspa9", + "iconSVG": "/static/media/icon.108789d7165de30306435ab3c24e6cad.svg", + "widgetId": "pjvwkdpty6", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "parentColumnSpace": 9.18896484375, + "parentRowSpace": 10, + "leftColumn": 33, + "rightColumn": 53, + "topRow": 39, + "bottomRow": 45, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "defaultText" + } + ], + "dynamicTriggerPathList": [] + }, + { + "isVisible": true, + "animateLoading": true, + "labelText": "Label", + "labelPosition": "Top", + "labelAlignment": "left", + "labelWidth": 5, + "labelTextSize": "0.875rem", + "options": "[\n {\n \"label\": \"Option 1\",\n \"value\": \"1\"\n },\n {\n \"label\": \"Option 2\",\n \"value\": \"2\"\n },\n {\n \"label\": \"Option 3\",\n \"value\": \"3\"\n },\n {\n \"label\": \"Option 4\",\n \"value\": \"4\"\n },\n {\n \"label\": \"Option 5\",\n \"value\": \"5\"\n }\n]", + "widgetName": "MultiSelect1", + "isFilterable": true, + "serverSideFiltering": false, + "defaultOptionValue": "{{ ((options, serverSideFiltering) => ( `[\n \n]`))(MultiSelect1.options, MultiSelect1.serverSideFiltering) }}", + "version": 1, + "isRequired": false, + "isDisabled": false, + "placeholderText": "Select option(s)", + "searchTags": [ + "dropdown", + "tags" + ], + "type": "MULTI_SELECT_WIDGET_V2", + "hideCard": false, + "isDeprecated": false, + "displayName": "MultiSelect", + "key": "y1jg4jsxok", + "iconSVG": "/static/media/icon.a3495809ae48291a64404f3bb04b0e69.svg", + "widgetId": "0vdcx3nycj", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "isLoading": false, + "parentColumnSpace": 9.18896484375, + "parentRowSpace": 10, + "leftColumn": 33, + "rightColumn": 53, + "topRow": 47, + "bottomRow": 54, + "parentId": "muk1vy75ao", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "defaultOptionValue" + } + ], + "dynamicTriggerPathList": [] + } + ], + "minHeight": 400, + "widgetId": "muk1vy75ao", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 239.25, + "topRow": 0, + "bottomRow": 680, + "parentId": "v6qsaaxfcm", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" } ] } + ], + "searchTags": [ + "group" + ], + "type": "FORM_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Form", + "key": "c1wmb3xdrf", + "iconSVG": "/static/media/icon.ea3e08d130e59c56867ae40114c10eed.svg", + "isCanvas": true, + "widgetId": "v6qsaaxfcm", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "isLoading": false, + "parentColumnSpace": 9.96875, + "parentRowSpace": 10, + "leftColumn": 3, + "rightColumn": 64, + "topRow": 27, + "bottomRow": 98, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } ] } ] } } - diff --git a/app/client/cypress/fixtures/invisibleWidgetdsl.json b/app/client/cypress/fixtures/invisibleWidgetdsl.json new file mode 100644 index 0000000000..402d33c21e --- /dev/null +++ b/app/client/cypress/fixtures/invisibleWidgetdsl.json @@ -0,0 +1,287 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 570, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 65, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "widgetName": "Container1", + "borderColor": "#E0DEDE", + "isCanvas": true, + "displayName": "Container", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "searchTags": [ + "div", + "parent", + "group" + ], + "topRow": 16, + "bottomRow": 40, + "parentRowSpace": 10, + "type": "CONTAINER_WIDGET", + "hideCard": false, + "shouldScrollContents": true, + "animateLoading": true, + "parentColumnSpace": 9.96875, + "dynamicTriggerPathList": [], + "leftColumn": 8, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas1", + "displayName": "Canvas", + "topRow": 0, + "bottomRow": 240, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": false, + "hideCard": true, + "minHeight": 240, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Input1", + "dynamicPropertyPathList": [ + { + "key": "isVisible" + } + ], + "displayName": "Input", + "iconSVG": "/static/media/icon.9f505595da61a34f563dba82adeb06ec.svg", + "searchTags": [ + "form", + "text input", + "number", + "textarea" + ], + "topRow": 5, + "bottomRow": 12, + "parentRowSpace": 10, + "labelWidth": 5, + "autoFocus": false, + "type": "INPUT_WIDGET_V2", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 3.42578125, + "dynamicTriggerPathList": [], + "resetOnSubmit": true, + "leftColumn": 18, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "isVisible" + } + ], + "labelPosition": "Top", + "labelStyle": "", + "inputType": "TEXT", + "isDisabled": false, + "key": "xeqahmz2n0", + "labelTextSize": "0.875rem", + "isRequired": false, + "isDeprecated": false, + "rightColumn": 38, + "dynamicHeight": "FIXED", + "widgetId": "8bfucv7r1x", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": "{{!Checkbox1.isChecked}}", + "label": "Off", + "version": 2, + "parentId": "7k7dojtq6h", + "labelAlignment": "left", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "iconAlign": "left", + "defaultText": "", + "minDynamicHeight": 4 + }, + { + "boxShadow": "none", + "widgetName": "Input2", + "dynamicPropertyPathList": [ + { + "key": "isVisible" + } + ], + "displayName": "Input", + "iconSVG": "/static/media/icon.9f505595da61a34f563dba82adeb06ec.svg", + "searchTags": [ + "form", + "text input", + "number", + "textarea" + ], + "topRow": 15, + "bottomRow": 22, + "parentRowSpace": 10, + "labelWidth": 5, + "autoFocus": false, + "type": "INPUT_WIDGET_V2", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 6.38525390625, + "dynamicTriggerPathList": [], + "resetOnSubmit": true, + "leftColumn": 16, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + }, + { + "key": "isVisible" + } + ], + "labelPosition": "Top", + "labelStyle": "", + "inputType": "TEXT", + "isDisabled": false, + "key": "xeqahmz2n0", + "labelTextSize": "0.875rem", + "isRequired": false, + "isDeprecated": false, + "rightColumn": 36, + "dynamicHeight": "FIXED", + "widgetId": "xiw6br6yni", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": "{{Checkbox1.isChecked}}", + "label": "On", + "version": 2, + "parentId": "7k7dojtq6h", + "labelAlignment": "left", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "iconAlign": "left", + "defaultText": "", + "minDynamicHeight": 4 + } + ], + "key": "j130wuw4eb", + "isDeprecated": false, + "rightColumn": 239.25, + "detachFromLayout": true, + "widgetId": "7k7dojtq6h", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "containerStyle": "none", + "isVisible": true, + "version": 1, + "parentId": "9ti9l1m9zc", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "borderWidth": "1", + "key": "0hl3710evc", + "backgroundColor": "#FFFFFF", + "isDeprecated": false, + "rightColumn": 51, + "dynamicHeight": "AUTO_HEIGHT", + "widgetId": "9ti9l1m9zc", + "containerStyle": "card", + "isVisible": true, + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "originalTopRow": 16, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "originalBottomRow": 59, + "minDynamicHeight": 4 + }, + { + "widgetName": "Checkbox1", + "displayName": "Checkbox", + "iconSVG": "/static/media/icon.aaab032b43383e4fa53ffc0ef40c90ef.svg", + "searchTags": [ + "boolean" + ], + "topRow": 45, + "bottomRow": 49, + "parentRowSpace": 10, + "type": "CHECKBOX_WIDGET", + "alignWidget": "LEFT", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 9.96875, + "leftColumn": 22, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "labelPosition": "Left", + "isDisabled": false, + "key": "u8vvhzphvk", + "isRequired": false, + "isDeprecated": false, + "rightColumn": 34, + "dynamicHeight": "FIXED", + "widgetId": "vdfj8it9cv", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": true, + "label": "Label", + "version": 1, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "defaultCheckedState": true, + "maxDynamicHeight": 9000, + "minDynamicHeight": 4, + "originalTopRow": 45, + "originalBottomRow": 49 + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/jsonFormDynamicHeightDsl.json b/app/client/cypress/fixtures/jsonFormDynamicHeightDsl.json new file mode 100644 index 0000000000..f91dbb6a17 --- /dev/null +++ b/app/client/cypress/fixtures/jsonFormDynamicHeightDsl.json @@ -0,0 +1,776 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1292, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 65, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "boxShadow": "none", + "widgetName": "Modal1", + "isCanvas": true, + "displayName": "Modal", + "iconSVG": "/static/media/icon.4975978e9a961fb0bfb4e38de7ecc7c5.svg", + "searchTags": [ + "dialog", + "popup", + "notification" + ], + "topRow": 0, + "bottomRow": 0, + "parentRowSpace": 1, + "type": "MODAL_WIDGET", + "hideCard": false, + "shouldScrollContents": true, + "animateLoading": true, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas2", + "displayName": "Canvas", + "topRow": 0, + "bottomRow": 240, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "hideCard": true, + "shouldScrollContents": false, + "minHeight": 240, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ], + "children": [ + { + "boxShadow": "none", + "widgetName": "IconButton1", + "onClick": "{{closeModal('Modal1')}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "displayName": "Icon Button", + "iconSVG": "/static/media/icon.1a0c634ac75f9fa6b6ae7a8df882a3ba.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 0, + "bottomRow": 4, + "type": "ICON_BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "leftColumn": 58, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "iconSize": 24, + "isDisabled": false, + "key": "oipik9z0jf", + "isDeprecated": false, + "rightColumn": 64, + "iconName": "cross", + "widgetId": "h84z4pqq7d", + "isVisible": true, + "version": 1, + "parentId": "b8h15fewxw", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "TERTIARY" + }, + { + "widgetName": "Text2", + "displayName": "Text", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "topRow": 1, + "bottomRow": 5, + "type": "TEXT_WIDGET", + "hideCard": false, + "animateLoading": true, + "overflow": "NONE", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "leftColumn": 1, + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "shouldTruncate": false, + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "text": "Modal Title", + "key": "qjy37lgbc3", + "isDeprecated": false, + "rightColumn": 41, + "dynamicHeight": "FIXED", + "textAlign": "LEFT", + "widgetId": "j98rl5e7a1", + "isVisible": true, + "fontStyle": "BOLD", + "textColor": "#231F20", + "version": 1, + "parentId": "b8h15fewxw", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 9000, + "fontSize": "1.5rem", + "minDynamicHeight": 4 + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button1", + "onClick": "{{closeModal('Modal1')}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 18, + "bottomRow": 22, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "leftColumn": 31, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "text": "Close", + "isDisabled": false, + "key": "8fy9at549y", + "isDeprecated": false, + "rightColumn": 47, + "isDefaultClickDisabled": true, + "widgetId": "w3gw0z0hul", + "buttonStyle": "PRIMARY", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "b8h15fewxw", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "SECONDARY", + "placement": "CENTER" + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button2", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": [ + "click", + "submit" + ], + "topRow": 18, + "bottomRow": 22, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "leftColumn": 47, + "dynamicBindingPathList": [ + { + "key": "buttonColor" + }, + { + "key": "borderRadius" + } + ], + "text": "Confirm", + "isDisabled": false, + "key": "8fy9at549y", + "isDeprecated": false, + "rightColumn": 63, + "isDefaultClickDisabled": true, + "widgetId": "dcky71d9uv", + "buttonStyle": "PRIMARY_BUTTON", + "isVisible": true, + "recaptchaType": "V3", + "version": 1, + "parentId": "b8h15fewxw", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "PRIMARY", + "placement": "CENTER" + } + ], + "isDisabled": false, + "key": "74zfu9g19a", + "isDeprecated": false, + "rightColumn": 0, + "detachFromLayout": true, + "widgetId": "b8h15fewxw", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isVisible": true, + "version": 1, + "parentId": "d0r5dcpeqm", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + } + ], + "key": "1y0e1crldi", + "height": 240, + "isDeprecated": false, + "rightColumn": 0, + "detachFromLayout": true, + "dynamicHeight": "FIXED", + "widgetId": "d0r5dcpeqm", + "canOutsideClickClose": true, + "canEscapeKeyClose": true, + "version": 2, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "maxDynamicHeight": 0, + "width": 456, + "minDynamicHeight": 4 + }, + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "FIXED", + "animateLoading": true, + "backgroundColor": "#fff", + "disabledWhenInvalid": true, + "fixedFooter": true, + "schema": { + "__root_schema__": { + "children": { + "name": { + "children": {}, + "dataType": "string", + "defaultValue": "{{((sourceData, formData, fieldState) => (sourceData.name))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "fieldType": "Text Input", + "sourceData": "John", + "isCustomField": false, + "accessor": "name", + "identifier": "name", + "position": 0, + "originalIdentifier": "name", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Name" + }, + "date_of_birth": { + "children": {}, + "dataType": "string", + "defaultValue": "{{((sourceData, formData, fieldState) => (moment(sourceData.date_of_birth, \"DD/MM/YYYY\").format(\"YYYY-MM-DDTHH:mm:ss.sssZ\")))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "fieldType": "Datepicker", + "sourceData": "20/02/1990", + "isCustomField": false, + "accessor": "date_of_birth", + "identifier": "date_of_birth", + "position": 1, + "originalIdentifier": "date_of_birth", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "closeOnSelection": false, + "convertToISO": false, + "dateFormat": "DD/MM/YYYY", + "isDisabled": false, + "isRequired": false, + "isVisible": true, + "label": "Date Of Birth", + "maxDate": "2121-12-31T18:29:00.000Z", + "minDate": "1920-12-31T18:30:00.000Z", + "shortcuts": false, + "timePrecision": "minute", + "labelTextSize": "0.875rem" + }, + "employee_id": { + "children": {}, + "dataType": "number", + "defaultValue": "{{((sourceData, formData, fieldState) => (sourceData.employee_id))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "fieldType": "Number Input", + "sourceData": 1001, + "isCustomField": false, + "accessor": "employee_id", + "identifier": "employee_id", + "position": 2, + "originalIdentifier": "employee_id", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Employee Id" + }, + "customField1": { + "children": {}, + "dataType": "string", + "fieldType": "Text Input", + "sourceData": "", + "isCustomField": true, + "accessor": "customField1", + "identifier": "customField1", + "position": 3, + "originalIdentifier": "customField1", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Custom Field 1" + }, + "customField2": { + "children": {}, + "dataType": "string", + "fieldType": "Text Input", + "sourceData": "", + "isCustomField": true, + "accessor": "customField2", + "identifier": "customField2", + "position": 4, + "originalIdentifier": "customField2", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Custom Field 2" + }, + "customField3": { + "children": {}, + "dataType": "string", + "fieldType": "Text Input", + "sourceData": "", + "isCustomField": true, + "accessor": "customField3", + "identifier": "customField3", + "position": 5, + "originalIdentifier": "customField3", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Custom Field 3" + }, + "customField4": { + "children": {}, + "dataType": "string", + "fieldType": "Text Input", + "sourceData": "", + "isCustomField": true, + "accessor": "customField4", + "identifier": "customField4", + "position": 6, + "originalIdentifier": "customField4", + "accentColor": "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "iconAlign": "left", + "isDisabled": false, + "isRequired": false, + "isSpellCheck": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "Custom Field 4" + } + }, + "dataType": "object", + "defaultValue": "{{((sourceData, formData, fieldState) => (sourceData))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "fieldType": "Object", + "sourceData": { + "name": "John", + "date_of_birth": "20/02/1990", + "employee_id": 1001 + }, + "isCustomField": false, + "accessor": "__root_schema__", + "identifier": "__root_schema__", + "position": -1, + "originalIdentifier": "__root_schema__", + "borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "boxShadow": "none", + "cellBorderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "cellBoxShadow": "none", + "isDisabled": false, + "isRequired": false, + "isVisible": true, + "labelTextSize": "0.875rem", + "label": "" + } + }, + "scrollContents": true, + "showReset": true, + "title": "Form", + "version": 1, + "borderWidth": "1", + "borderColor": "#E0DEDE", + "widgetName": "JSONForm1", + "autoGenerateForm": true, + "fieldLimitExceeded": false, + "sourceData": { + "name": "John", + "date_of_birth": "20/02/1990", + "employee_id": 1001 + }, + "submitButtonLabel": "Submit", + "resetButtonLabel": "Reset", + "type": "JSON_FORM_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "JSON Form", + "key": "wrvbzxoh9c", + "iconSVG": "/static/media/icon.5b428de12db9ad6a591955ead07f86e9.svg", + "widgetId": "s10ovhzkte", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "submitButtonStyles": { + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "buttonVariant": "PRIMARY" + }, + "resetButtonStyles": { + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "buttonVariant": "SECONDARY" + }, + "childStylesheet": { + "ARRAY": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "cellBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "cellBoxShadow": "none" + }, + "OBJECT": { + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none", + "cellBorderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "cellBoxShadow": "none" + }, + "CHECKBOX": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}" + }, + "CURRENCY_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "DATEPICKER": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "EMAIL_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "MULTISELECT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "MULTILINE_TEXT_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "NUMBER_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "PASSWORD_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "PHONE_NUMBER_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "RADIO_GROUP": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "boxShadow": "none" + }, + "SELECT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + }, + "SWITCH": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "boxShadow": "none" + }, + "TEXT_INPUT": { + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "boxShadow": "none" + } + }, + "isLoading": false, + "parentColumnSpace": 9.96875, + "parentRowSpace": 10, + "leftColumn": 15, + "rightColumn": 40, + "topRow": 28, + "bottomRow": 78, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + }, + { + "key": "submitButtonStyles.buttonColor" + }, + { + "key": "submitButtonStyles.borderRadius" + }, + { + "key": "resetButtonStyles.buttonColor" + }, + { + "key": "resetButtonStyles.borderRadius" + }, + { + "key": "childStylesheet.ARRAY.accentColor" + }, + { + "key": "childStylesheet.ARRAY.borderRadius" + }, + { + "key": "childStylesheet.ARRAY.cellBorderRadius" + }, + { + "key": "childStylesheet.OBJECT.borderRadius" + }, + { + "key": "childStylesheet.OBJECT.cellBorderRadius" + }, + { + "key": "childStylesheet.CHECKBOX.accentColor" + }, + { + "key": "childStylesheet.CHECKBOX.borderRadius" + }, + { + "key": "childStylesheet.CURRENCY_INPUT.accentColor" + }, + { + "key": "childStylesheet.CURRENCY_INPUT.borderRadius" + }, + { + "key": "childStylesheet.DATEPICKER.accentColor" + }, + { + "key": "childStylesheet.DATEPICKER.borderRadius" + }, + { + "key": "childStylesheet.EMAIL_INPUT.accentColor" + }, + { + "key": "childStylesheet.EMAIL_INPUT.borderRadius" + }, + { + "key": "childStylesheet.MULTISELECT.accentColor" + }, + { + "key": "childStylesheet.MULTISELECT.borderRadius" + }, + { + "key": "childStylesheet.MULTILINE_TEXT_INPUT.accentColor" + }, + { + "key": "childStylesheet.MULTILINE_TEXT_INPUT.borderRadius" + }, + { + "key": "childStylesheet.NUMBER_INPUT.accentColor" + }, + { + "key": "childStylesheet.NUMBER_INPUT.borderRadius" + }, + { + "key": "childStylesheet.PASSWORD_INPUT.accentColor" + }, + { + "key": "childStylesheet.PASSWORD_INPUT.borderRadius" + }, + { + "key": "childStylesheet.PHONE_NUMBER_INPUT.accentColor" + }, + { + "key": "childStylesheet.PHONE_NUMBER_INPUT.borderRadius" + }, + { + "key": "childStylesheet.RADIO_GROUP.accentColor" + }, + { + "key": "childStylesheet.SELECT.accentColor" + }, + { + "key": "childStylesheet.SELECT.borderRadius" + }, + { + "key": "childStylesheet.SWITCH.accentColor" + }, + { + "key": "childStylesheet.TEXT_INPUT.accentColor" + }, + { + "key": "childStylesheet.TEXT_INPUT.borderRadius" + }, + { + "key": "schema.__root_schema__.children.name.defaultValue" + }, + { + "key": "schema.__root_schema__.children.name.accentColor" + }, + { + "key": "schema.__root_schema__.children.name.borderRadius" + }, + { + "key": "schema.__root_schema__.children.date_of_birth.defaultValue" + }, + { + "key": "schema.__root_schema__.children.date_of_birth.accentColor" + }, + { + "key": "schema.__root_schema__.children.date_of_birth.borderRadius" + }, + { + "key": "schema.__root_schema__.children.employee_id.defaultValue" + }, + { + "key": "schema.__root_schema__.children.employee_id.accentColor" + }, + { + "key": "schema.__root_schema__.children.employee_id.borderRadius" + }, + { + "key": "schema.__root_schema__.defaultValue" + }, + { + "key": "schema.__root_schema__.borderRadius" + }, + { + "key": "schema.__root_schema__.cellBorderRadius" + }, + { + "key": "schema.__root_schema__.children.customField1.accentColor" + }, + { + "key": "schema.__root_schema__.children.customField1.borderRadius" + }, + { + "key": "schema.__root_schema__.children.customField2.accentColor" + }, + { + "key": "schema.__root_schema__.children.customField2.borderRadius" + }, + { + "key": "schema.__root_schema__.children.customField3.accentColor" + }, + { + "key": "schema.__root_schema__.children.customField3.borderRadius" + }, + { + "key": "schema.__root_schema__.children.customField4.accentColor" + }, + { + "key": "schema.__root_schema__.children.customField4.borderRadius" + } + ], + "dynamicPropertyPathList": [ + { + "key": "schema.__root_schema__.children.date_of_birth.defaultValue" + } + ], + "dynamicTriggerPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/multipleContainerdsl.json b/app/client/cypress/fixtures/multipleContainerdsl.json new file mode 100644 index 0000000000..0e6a2be59d --- /dev/null +++ b/app/client/cypress/fixtures/multipleContainerdsl.json @@ -0,0 +1,344 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 600, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 69, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "backgroundColor": "#FFFFFF", + "widgetName": "Container1", + "containerStyle": "card", + "borderColor": "#E0DEDE", + "borderWidth": "1", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "animateLoading": true, + "children": [ + { + "isVisible": true, + "widgetName": "Canvas1", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "mqfi7o9374", + "containerStyle": "none", + "canExtend": false, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "backgroundColor": "#FFFFFF", + "widgetName": "Container2", + "containerStyle": "card", + "borderColor": "#E0DEDE", + "borderWidth": "1", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "animateLoading": true, + "children": [ + { + "isVisible": true, + "widgetName": "Canvas2", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "mqfi7o9374", + "containerStyle": "none", + "canExtend": false, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "shouldScrollContents": true, + "backgroundColor": "#FFFFFF", + "widgetName": "Container3", + "containerStyle": "card", + "borderColor": "#E0DEDE", + "borderWidth": "1", + "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}", + "animateLoading": true, + "children": [ + { + "isVisible": true, + "widgetName": "Canvas3", + "version": 1, + "detachFromLayout": true, + "type": "CANVAS_WIDGET", + "hideCard": true, + "isDeprecated": false, + "displayName": "Canvas", + "key": "mqfi7o9374", + "containerStyle": "none", + "canExtend": false, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "animateLoading": true, + "labelTextSize": "0.875rem", + "options": [ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + } + ], + "defaultSelectedValues": [ + "BLUE" + ], + "isDisabled": false, + "isInline": true, + "isRequired": false, + "labelText": "Label", + "labelPosition": "Left", + "labelAlignment": "left", + "labelWidth": 5, + "widgetName": "CheckboxGroup1", + "version": 2, + "type": "CHECKBOX_GROUP_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Checkbox Group", + "key": "hm1iv7bfuy", + "iconSVG": "/static/media/icon.ecb3847950c4515966ef642a32758afb.svg", + "widgetId": "v37fp7if60", + "renderMode": "CANVAS", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 2.9258722066879272, + "parentRowSpace": 10, + "leftColumn": 8, + "rightColumn": 57, + "topRow": 33, + "bottomRow": 45, + "parentId": "05i6e0t0fa", + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [] + } + ], + "minHeight": 470, + "widgetId": "05i6e0t0fa", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 47.16357421875, + "topRow": 0, + "bottomRow": 470, + "parentId": "l44iyd101d", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 1, + "searchTags": [ + "div", + "parent", + "group" + ], + "type": "CONTAINER_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Container", + "key": "ojfkbwpxhu", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "isCanvas": true, + "widgetId": "l44iyd101d", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 1.96514892578125, + "parentRowSpace": 10, + "leftColumn": 4, + "rightColumn": 59, + "topRow": 1, + "bottomRow": 48, + "parentId": "wor0jspl6z", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + } + ], + "minHeight": 500, + "widgetId": "wor0jspl6z", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 145.76953125, + "topRow": 0, + "bottomRow": 500, + "parentId": "429zp6cdxf", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 1, + "searchTags": [ + "div", + "parent", + "group" + ], + "type": "CONTAINER_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Container", + "key": "ojfkbwpxhu", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "isCanvas": true, + "widgetId": "429zp6cdxf", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 6.07373046875, + "parentRowSpace": 10, + "leftColumn": 9, + "rightColumn": 52, + "topRow": 5, + "bottomRow": 55, + "parentId": "stwsepims4", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + } + ], + "minHeight": 570, + "widgetId": "stwsepims4", + "renderMode": "CANVAS", + "boxShadow": "none", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "isLoading": false, + "parentColumnSpace": 1, + "parentRowSpace": 1, + "leftColumn": 0, + "rightColumn": 239.25, + "topRow": 0, + "bottomRow": 570, + "parentId": "v7qrdx618b", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "accentColor" + } + ] + } + ], + "version": 1, + "searchTags": [ + "div", + "parent", + "group" + ], + "type": "CONTAINER_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Container", + "key": "ojfkbwpxhu", + "iconSVG": "/static/media/icon.1977dca3370505e2db3a8e44cfd54907.svg", + "isCanvas": true, + "widgetId": "v7qrdx618b", + "renderMode": "CANVAS", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 9.96875, + "parentRowSpace": 10, + "leftColumn": 18, + "rightColumn": 59, + "topRow": 1, + "bottomRow": 58, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "borderRadius" + }, + { + "key": "boxShadow" + } + ], + "dynamicTriggerPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/textWidgetDynamicdsl.json b/app/client/cypress/fixtures/textWidgetDynamicdsl.json new file mode 100644 index 0000000000..81b7a20939 --- /dev/null +++ b/app/client/cypress/fixtures/textWidgetDynamicdsl.json @@ -0,0 +1,77 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 400, + "containerStyle": "none", + "snapRows": 125, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 64, + "minHeight": 1292, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "minDynamicHeight": 4, + "maxDynamicHeight": 9000, + "dynamicHeight": "AUTO_HEIGHT", + "text": "Test Auto Height", + "fontSize": "1rem", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}", + "widgetName": "Text1", + "shouldTruncate": false, + "overflow": "NONE", + "version": 1, + "animateLoading": true, + "searchTags": [ + "typography", + "paragraph", + "label" + ], + "type": "TEXT_WIDGET", + "hideCard": false, + "isDeprecated": false, + "displayName": "Text", + "key": "t24h8uem36", + "iconSVG": "/static/media/icon.97c59b523e6f70ba6f40a10fc2c7c5b5.svg", + "widgetId": "nwkbwkxa62", + "renderMode": "CANVAS", + "fontFamily": "{{appsmith.theme.fontFamily.appFont}}", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "isLoading": false, + "parentColumnSpace": 9.96875, + "parentRowSpace": 10, + "leftColumn": 19, + "rightColumn": 35, + "topRow": 11, + "bottomRow": 15, + "parentId": "0", + "dynamicBindingPathList": [ + { + "key": "truncateButtonColor" + }, + { + "key": "fontFamily" + }, + { + "key": "borderRadius" + } + ], + "dynamicTriggerPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/autocomplete_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/autocomplete_spec.js index 06436faf57..c190c7e04a 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/autocomplete_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/autocomplete_spec.js @@ -7,6 +7,9 @@ describe("Dynamic input autocomplete", () => { cy.addDsl(dsl); }); it("opens autocomplete for bindings", () => { + cy.selectEntityByName("TestModal"); + cy.wait(3000); + cy.selectEntityByName("Aditya"); cy.openPropertyPane("buttonwidget"); cy.get(dynamicInputLocators.input) .first() diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts index 038551c5a0..146b0353d9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug16702_Spec.ts @@ -49,6 +49,7 @@ describe("Binding Expressions should not be truncated in Url and path extraction // }) //.trigger("mouseover") .dblclick() + .dblclick() .type("{{JSObject1."); agHelper.GetNAssertElementText(locator._hints, "offsetValue", "have.text", 1); agHelper.Sleep(); @@ -65,6 +66,7 @@ describe("Binding Expressions should not be truncated in Url and path extraction .contains("__limit__") //.trigger("mouseover") .dblclick() + .dblclick() .type("{{JSObject1."); agHelper.GetNClickByContains(locator._hints, "limitValue"); agHelper.Sleep(2000); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_Limit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_Limit_spec.js new file mode 100644 index 0000000000..499bc1ef4a --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_Limit_spec.js @@ -0,0 +1,48 @@ +const dsl = require("../../../../fixtures/dynamicHeightContainerdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation with limits", function() { + it("Validate change in auto height with limits width for widgets and highlight section validation", function() { + cy.addDsl(dsl); + cy.wait(3000); //for dsl to settle + cy.openPropertyPane("containerwidget"); + cy.changeLayoutHeight(commonlocators.autoHeightWithLimits); + cy.wait(3000); //for dsl to settle + //cy.checkMinDefaultValue(commonlocators.minHeight,"4") + //cy.testJsontext(commonlocators.minHeight, "5"); + //cy.get(commonlocators.overlayMin).should("be.visible"); + cy.get("[data-cy='t--auto-height-overlay-handles-min']").trigger( + "mouseover", + ); + cy.contains("Min-Height: 10 rows"); + cy.get("[data-cy='t--auto-height-overlay-handles-min']").should( + "be.visible", + ); + cy.get("[data-cy='t--auto-height-overlay-handles-min'] div") + .eq(0) + .should("have.css", "background-color", "rgb(243, 43, 139)"); + /*cy.get(commonlocators.overlayMin).should( + "have.css", + "background-color", + "rgba(243, 43, 139, 0.1)", + );*/ + cy.get("[data-cy='t--auto-height-overlay-handles-max']").trigger( + "mouseover", + ); + cy.contains("Max-Height: 12 rows"); + //cy.checkMaxDefaultValue(commonlocators.maxHeight,"40") + //cy.testJsontext(commonlocators.maxHeight, "60"); + cy.get("[data-cy='t--auto-height-overlay-handles-max']").should( + "be.visible", + ); + cy.get("[data-cy='t--auto-height-overlay-handles-max'] div") + .eq(0) + .should("have.css", "background-color", "rgb(243, 43, 139)"); + //cy.contains("Max-Height: 60 rows"); + cy.changeLayoutHeight(commonlocators.fixed); + cy.changeLayoutHeight(commonlocators.autoHeightWithLimits); + //cy.contains("Min-Height: 5 rows"); + //cy.checkMinDefaultValue(commonlocators.minHeight,"5") + // cy.checkMaxDefaultValue(commonlocators.maxHeight,"60") + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_spec.js new file mode 100644 index 0000000000..86c2bd1468 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Auto_Height_spec.js @@ -0,0 +1,38 @@ +const dsl = require("../../../../fixtures/dynamicHeightContainerCheckboxdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation", function() { + it("Validate change with auto height width for widgets", function() { + cy.addDsl(dsl); + cy.wait(3000); //for dsl to settle + cy.openPropertyPane("containerwidget"); + //cy.changeLayoutHeight(commonlocators.autoHeight); + cy.openPropertyPane("checkboxgroupwidget"); + //cy.changeLayoutHeight(commonlocators.autoHeight); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((height) => { + cy.get(".t--widget-checkboxgroupwidget") + .invoke("css", "height") + .then((checkboxheight) => { + cy.get(commonlocators.addOption).click(); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(".t--widget-checkboxgroupwidget") + .invoke("css", "height") + .then((newcheckboxheight) => { + expect(checkboxheight).to.not.equal(newcheckboxheight); + }); + }); + cy.wait(2000); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((newheight) => { + expect(height).to.not.equal(newheight); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Form_With_SwitchGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Form_With_SwitchGroup_spec.js new file mode 100644 index 0000000000..1d98ca2d30 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Form_With_SwitchGroup_spec.js @@ -0,0 +1,95 @@ +const dsl = require("../../../../fixtures/dynamicHeightFormSwitchdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation", function() { + it("Validate change with auto height width for Form/Switch", function() { + cy.addDsl(dsl); + cy.wait(3000); //for dsl to settle + cy.openPropertyPane("formwidget"); + cy.get(".t--widget-formwidget") + .invoke("css", "height") + .then((formheight) => { + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.openPropertyPane("switchgroupwidget"); + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.get(".t--widget-switchgroupwidget") + .invoke("css", "height") + .then((switchheight) => { + cy.get(".t--widget-formwidget") + .invoke("css", "height") + .then((newformheight) => { + //expect(formheight).to.not.equal(newformheight) + cy.updateCodeInput( + ".t--property-control-options", + `[ + { + "label": "Blue", + "value": "BLUE" + }, + { + "label": "Green", + "value": "GREEN" + }, + { + "label": "Red", + "value": "RED" + }, + { + "label": "Yellow", + "value": "YELLOW" + }, + { + "label": "Purple", + "value": "PURPLE" + }, + { + "label": "Pink", + "value": "PINK" + }, + { + "label": "Black", + "value": "BLACK" + }, + { + "label": "Grey", + "value": "GREY" + }, + { + "label": "Orange", + "value": "ORANGE" + }, + { + "label": "Cream", + "value": "CREAM" + } + ]`, + ); + cy.get(".t--widget-switchgroupwidget") + .invoke("css", "height") + .then((newswitchheight) => { + cy.get(".t--widget-formwidget") + .invoke("css", "height") + .then((updatedformheight) => { + expect(newformheight).to.not.equal(updatedformheight); + expect(switchheight).to.not.equal(newswitchheight); + }); + }); + }); + }); + }); + cy.get(".t--draggable-switchgroupwidget .bp3-control-indicator") + .first() + .click({ force: true }); + cy.wait(3000); + cy.get(".t--modal-widget").should("have.length", 1); + cy.get(".t--widget-propertypane-toggle") + .first() + .click({ force: true }); + //cy.changeLayoutHeight(commonlocators.autoHeightWithLimits); + //cy.checkMinDefaultValue(commonlocators.minHeight,"4") + //cy.checkMaxDefaultValue(commonlocators.maxHeight,"24") + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.wait(3000); + cy.get("button:contains('Close')").click({ force: true }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_JsonForm_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_JsonForm_spec.js new file mode 100644 index 0000000000..9f9abe5573 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_JsonForm_spec.js @@ -0,0 +1,62 @@ +const dsl = require("../../../../fixtures/jsonFormDynamicHeightDsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation", function() { + it("Validate change with auto height width for JsonForm", function() { + cy.addDsl(dsl); + cy.wait(3000); //for dsl to settle + cy.openPropertyPane("jsonformwidget"); + cy.get(".t--widget-jsonformwidget") + .invoke("css", "height") + .then((formheight) => { + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.wait(5000); + cy.get(".t--widget-jsonformwidget") + .invoke("css", "height") + .then((newformheight) => { + expect(formheight).to.not.equal(newformheight); + cy.get(".t--show-column-btn") + .eq(0) + .click({ force: true }); + cy.get(".t--show-column-btn") + .eq(1) + .click({ force: true }); + cy.get(".t--show-column-btn") + .eq(2) + .click({ force: true }); + // cy.get("[data-cy='t--resizable-handle-TOP']") + // .within(($el) => { + // cy.window().then((win) => { + // const after = win.getComputedStyle($el[0], "::after"); + // expect(after).not.to.exist + // }); + // }); + // cy.get("[data-cy='t--resizable-handle-BOTTOM']").should("not.exist"); + cy.changeLayoutHeight(commonlocators.fixed); + cy.wait(5000); + cy.get(".t--widget-jsonformwidget") + .invoke("css", "height") + .then((updatedformheight) => { + expect(newformheight).to.not.equal(updatedformheight); + cy.get(".t--show-column-btn") + .eq(2) + .click({ force: true }); + cy.get(".t--show-column-btn") + .eq(1) + .click({ force: true }); + // cy.get("[data-cy='t--resizable-handle-TOP']").should("exist"); + // cy.get("[data-cy='t--resizable-handle-BOTTOM']").should("exist"); + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.wait(5000); + cy.get(".t--widget-jsonformwidget") + .invoke("css", "height") + .then((newupdatedformheight) => { + expect(updatedformheight).to.not.equal( + newupdatedformheight, + ); + }); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Multiple_Container_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Multiple_Container_spec.js new file mode 100644 index 0000000000..e62feac13b --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Multiple_Container_spec.js @@ -0,0 +1,71 @@ +const dsl = require("../../../../fixtures/multipleContainerdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation for multiple container", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Validate change in auto height width with multiple containers", function() { + cy.wait(3000); //for dsl to settle + cy.openPropertyPaneWithIndex("containerwidget", 0); + cy.changeLayoutHeight(commonlocators.fixed); + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.openPropertyPaneWithIndex("containerwidget", 1); + cy.changeLayoutHeight(commonlocators.fixed); + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.openPropertyPane("checkboxgroupwidget"); + cy.changeLayoutHeight(commonlocators.fixed); + cy.changeLayoutHeight(commonlocators.autoHeight); + cy.wait(2000); + cy.get(".t--widget-containerwidget") + .eq(0) + .invoke("css", "height") + .then((oheight) => { + cy.get(".t--widget-containerwidget") + .eq(1) + .invoke("css", "height") + .then((mheight) => { + cy.get(".t--widget-containerwidget") + .eq(2) + .invoke("css", "height") + .then((iheight) => { + cy.get(".t--widget-checkboxgroupwidget") + .invoke("css", "height") + .then((checkboxheight) => { + cy.get(commonlocators.addOption).click({ force: true }); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.wait(3000); + cy.get(".t--widget-checkboxgroupwidget") + .invoke("css", "height") + .then((newcheckboxheight) => { + expect(checkboxheight).to.not.equal(newcheckboxheight); + }); + }); + cy.wait(2000); + cy.get(".t--widget-containerwidget") + .eq(0) + .invoke("css", "height") + .then((onewheight) => { + expect(oheight).to.not.equal(onewheight); + }); + cy.get(".t--widget-containerwidget") + .eq(1) + .invoke("css", "height") + .then((mnewheight) => { + expect(mheight).to.not.equal(mnewheight); + }); + cy.get(".t--widget-containerwidget") + .eq(2) + .invoke("css", "height") + .then((inewheight) => { + expect(iheight).to.not.equal(inewheight); + }); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Tab_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Tab_spec.js new file mode 100644 index 0000000000..0269c11e52 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Tab_spec.js @@ -0,0 +1,64 @@ +const dsl = require("../../../../fixtures/dynamicTabWidgetdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation for Tab widget", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Tab widget validation of height with dynamic height feature", function() { + //changing the Text Name and verifying + cy.wait(3000); + cy.openPropertyPane("tabswidget"); + cy.changeLayoutHeightWithoutWait(commonlocators.autoHeight); + cy.get(".t--tabid-tab1").click({ force: true }); + cy.wait(3000); + cy.get(".t--widget-tabswidget") + .invoke("css", "height") + .then((theight) => { + cy.get(".t--tabid-tab2").click({ force: true }); + cy.wait(3000); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + //cy.get(".t--draggable-checkboxwidget .bp3-control-indicator").click({ force: true }) + cy.get(".t--widget-tabswidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.not.equal(tnewheight); + cy.reload(); + cy.openPropertyPane("tabswidget"); + expect(theight).to.equal(theight); + }); + }); + cy.changeLayoutHeight(commonlocators.fixed); + cy.get(".t--tabid-tab1").click({ force: true }); + cy.wait(3000); + cy.get(".t--widget-tabswidget") + .invoke("css", "height") + .then((theight) => { + cy.get(".t--tabid-tab2").click({ force: true }); + cy.wait(3000); + //cy.get(".t--draggable-checkboxwidget .bp3-control-indicator").click({ force: true }) + cy.get(".t--widget-tabswidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.equal(tnewheight); + cy.get(commonlocators.showTabsControl).click({ force: true }); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(".t--widget-tabswidget") + .invoke("css", "height") + .then((upheight) => { + expect(tnewheight).to.equal(upheight); + cy.get(".t--tabid-tab1").should("not.exist"); + cy.get(".t--tabid-tab2").should("not.exist"); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_Widget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_Widget_spec.js new file mode 100644 index 0000000000..8f6d3e9d27 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_Widget_spec.js @@ -0,0 +1,41 @@ +const dsl = require("../../../../fixtures/textWidgetDynamicdsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation for text widget", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Text widget validation of height with dynamic height feature", function() { + const textMsg = "Dynamic panel validation for text widget wrt height"; + //changing the Text Name and verifying + cy.openPropertyPane("textwidget"); + cy.changeLayoutHeightWithoutWait(commonlocators.autoHeight); + cy.get(".t--widget-textwidget") + .invoke("css", "height") + .then((theight) => { + //Changing the text label + cy.testCodeMirror(textMsg); + cy.moveToStyleTab(); + cy.ChangeTextStyle( + this.data.TextHeading, + commonlocators.headingTextStyle, + textMsg, + ); + cy.wait("@updateLayout"); + cy.get(".t--widget-textwidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.not.equal(tnewheight); + }); + cy.PublishtheApp(); + cy.get(commonlocators.headingTextStyle) + .should("have.text", textMsg) + .should("have.css", "font-size", "16px"); + cy.get(".t--widget-textwidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.not.equal(tnewheight); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_With_Different_Size_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_With_Different_Size_spec.js new file mode 100644 index 0000000000..40e5278db2 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Text_With_Different_Size_spec.js @@ -0,0 +1,138 @@ +const dsl = require("../../../../fixtures/alignmentWithDynamicHeightDsl.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Dynamic Height Width validation", function() { + function validateCssProperties(property) { + cy.get("button:contains('Small')").click({ force: true }); + cy.wait(3000); + cy.selectEntityByName("Text1"); + cy.get(".t--widget-textwidget") + .eq(0) + .invoke("css", property) + .then((firstText) => { + cy.selectEntityByName("Text2"); + cy.get(".t--widget-textwidget") + .eq(1) + .invoke("css", property) + .then((secondText) => { + cy.selectEntityByName("Text3"); + cy.get(".t--widget-textwidget") + .eq(2) + .invoke("css", property) + .then((thirdText) => { + cy.selectEntityByName("Text4"); + cy.get(".t--widget-textwidget") + .eq(3) + .invoke("css", property) + .then((fourthText) => { + cy.get("button:contains('Large')").click({ force: true }); + cy.selectEntityByName("Text1"); + cy.get(".t--widget-textwidget") + .eq(0) + .invoke("css", property) + .then((largefirstText) => { + cy.selectEntityByName("Text2"); + cy.get(".t--widget-textwidget") + .eq(1) + .invoke("css", property) + .then((largesecondText) => { + cy.selectEntityByName("Text3"); + cy.get(".t--widget-textwidget") + .eq(2) + .invoke("css", property) + .then((largethirdText) => { + cy.selectEntityByName("Text4"); + cy.get(".t--widget-textwidget") + .eq(3) + .invoke("css", property) + .then((largefourthText) => { + if (property == "left") { + expect(firstText).to.equal( + largefirstText, + ); + expect(secondText).to.equal( + largesecondText, + ); + expect(thirdText).to.equal( + largethirdText, + ); + expect(fourthText).to.equal( + largefourthText, + ); + } else { + expect(firstText).to.not.equal( + largefirstText, + ); + expect(secondText).to.not.equal( + largesecondText, + ); + expect(thirdText).to.not.equal( + largethirdText, + ); + expect(fourthText).to.not.equal( + largefourthText, + ); + } + cy.get("button:contains('Small')").click({ + force: true, + }); + cy.wait(3000); + cy.selectEntityByName("Text1"); + cy.get(".t--widget-textwidget") + .eq(0) + .invoke("css", property) + .then((updatelargefirstText) => { + cy.selectEntityByName("Text2"); + cy.get(".t--widget-textwidget") + .eq(1) + .invoke("css", property) + .then((updatelargesecondText) => { + cy.selectEntityByName("Text3"); + cy.get(".t--widget-textwidget") + .eq(2) + .invoke("css", property) + .then((updatelargethirdText) => { + cy.selectEntityByName("Text4"); + cy.get(".t--widget-textwidget") + .eq(3) + .invoke("css", property) + .then( + (updatelargefourthText) => { + //expect(firstText).to.equal(updatelargefirstText); + expect( + secondText, + ).to.equal( + updatelargesecondText, + ); + expect( + thirdText, + ).to.equal( + updatelargethirdText, + ); + expect( + fourthText, + ).to.equal( + updatelargefourthText, + ); + }, + ); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + } + it("Validate change with auto height width for text widgets", function() { + cy.addDsl(dsl); + cy.wait(30000); //for dsl to settled + validateCssProperties("height"); + //validateCssProperties("top"); + validateCssProperties("left"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Visibility_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Visibility_spec.js new file mode 100644 index 0000000000..17c2fac37c --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicHeight/DynamicHeight_Visibility_spec.js @@ -0,0 +1,52 @@ +const commonlocators = require("../../../../locators/commonlocators.json"); +const dsl = require("../../../../fixtures/invisibleWidgetdsl.json"); + +describe("Dynamic Height Width validation for Visibility", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Validating visbility/invisiblity of widget with dynamic height feature", function() { + //changing the Text Name and verifying + cy.wait(3000); + cy.openPropertyPane("containerwidget"); + cy.changeLayoutHeightWithoutWait(commonlocators.autoHeight); + cy.openPropertyPaneWithIndex("inputwidgetv2", 0); + cy.changeLayoutHeightWithoutWait(commonlocators.autoHeight); + cy.openPropertyPaneWithIndex("inputwidgetv2", 1); + cy.changeLayoutHeightWithoutWait(commonlocators.autoHeight); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((theight) => { + cy.get(commonlocators.checkboxIndicator).click({ force: true }); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.equal(tnewheight); + cy.get("label:Contains('On')").should("not.be.enabled"); + }); + }); + cy.PublishtheApp(); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((theight) => { + cy.get(".bp3-control-indicator").click({ force: true }); + cy.wait(2000); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((tnewheight) => { + expect(theight).to.not.equal(tnewheight); + cy.get("label:Contains('On')").should("not.exist"); + cy.get("label:Contains('Off')").should("be.visible"); + cy.get(".bp3-control-indicator").click({ force: true }); + cy.wait(2000); + cy.get(".t--widget-containerwidget") + .invoke("css", "height") + .then((tonheight) => { + expect(tonheight).to.not.equal(tnewheight); + cy.get("label:Contains('Off')").should("not.exist"); + cy.get("label:Contains('On')").should("be.visible"); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js index 4dd800f29f..d95033b6ec 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -37,7 +37,6 @@ describe("Entity explorer Drag and Drop widgets testcases", function() { * @param{toggleButton Css} Assert to be checked */ cy.moveToContentTab(); - cy.togglebar(commonlocators.scrollView); cy.get(formWidgetsPage.formD) .scrollTo("bottom") .should("be.visible"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/Canvas_Context_Bug_Fixes.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/Canvas_Context_Bug_Fixes.js new file mode 100644 index 0000000000..5ded3386b1 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/Canvas_Context_Bug_Fixes.js @@ -0,0 +1,23 @@ +const dsl = require("../../../../fixtures/Bugs/CheckboxGroupInListWidgetDsl.json"); + +describe("Canvas context Property Pane", function() { + it("Bug Fix: Unable to delete checkbox child when it is inside list widget #18191", () => { + cy.addDsl(dsl); + cy.openPropertyPane("checkboxgroupwidget"); + //check number of options + cy.get(".t--property-control-options > div:nth-child(2) > div").should( + "have.length", + 3, + ); + //click on delete button + cy.get( + ".t--property-control-options > div:nth-child(2) > div:nth-child(2) > button", + ).click(); + + //verify deletion + cy.get(".t--property-control-options > div:nth-child(2) > div").should( + "have.length", + 2, + ); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js index f176a67522..8d4ad3230e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js @@ -4,6 +4,8 @@ import { ObjectsRegistry } from "../../../../support/Objects/Registry"; const homePage = ObjectsRegistry.HomePage; const agHelper = ObjectsRegistry.AggregateHelper; +const ee = ObjectsRegistry.EntityExplorer; +const apiPage = ObjectsRegistry.ApiPage; describe("MaintainContext&Focus", function() { it("1. Import the test application", () => { @@ -135,4 +137,12 @@ describe("MaintainContext&Focus", function() { cy.SearchEntityandOpen("JSObject2"); cy.assertCursorOnCodeInput(".js-editor", { ch: 2, line: 2 }); }); + + it("7. Check if selected tab on right tab persists", () => { + ee.SelectEntityByName("Rest_Api_1"); + apiPage.SelectRightPaneTab("connections"); + ee.SelectEntityByName("SQL_Query"); + ee.SelectEntityByName("Rest_Api_1"); + apiPage.AssertRightPaneSelectedTab("connections"); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Linting/BasicLint_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Linting/BasicLint_spec.ts index e679e96f4e..1c9c44820c 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Linting/BasicLint_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Linting/BasicLint_spec.ts @@ -271,9 +271,10 @@ describe("Linting", () => { apiPage.CreateAndFillApi("https://jsonplaceholder.typicode.com/"); createMySQLDatasourceQuery(); - + agHelper.RefreshPage();//Since this seems failing a bit clickButtonAndAssertLintError(false); }); + it("8. Doesn't show lint errors for supported web apis", () => { const JS_OBJECT_WITH_WEB_API = `export default { myFun1: () => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/PreviewMode_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/PreviewMode_spec.js index dced5ce447..ff4ee09730 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/PreviewMode_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/PreviewMode_spec.js @@ -17,6 +17,7 @@ describe("Preview mode functionality", function() { it("checks if widgets can be selected or not", function() { // in preview mode, entity explorer and property pane are not visible + // Also, draggable and resizable components are not available. const selector = `.t--draggable-buttonwidget`; cy.wait(500); cy.get(selector) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js index 1f969e2c13..7e93e8a296 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Resize_spec.js @@ -16,7 +16,7 @@ describe("Canvas Resize", function() { cy.get(commonlocators.dropTarget).should( "have.css", "height", - `${dsl.bottomRow}px`, + `${dsl.minHeight}px`, ); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js index f7551b2c28..21a32ceb21 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Theme_MultiSelectWidget_spec.js @@ -10,7 +10,7 @@ let propPane = ObjectsRegistry.PropertyPane, ee = ObjectsRegistry.EntityExplorer; describe("Theme validation usecase for multi-select widget", function() { - it("Drag and drop multi-select widget and validate Default font and list of font validation + Bug 15007", function() { + it("1. Drag and drop multi-select widget and validate Default font and list of font validation + Bug 15007", function() { //cy.reload(); // To remove the rename tooltip ee.DragDropWidgetNVerify("multiselectwidgetv2", 300, 80); cy.get(themelocator.canvas).click({ force: true }); @@ -96,7 +96,7 @@ describe("Theme validation usecase for multi-select widget", function() { cy.contains("Color").click({ force: true }); }); - it.skip("Publish the App and validate Font across the app + Bug 15007", function() { + it.skip("2. Publish the App and validate Font across the app + Bug 15007", function() { //Skipping due to mentioned bug cy.PublishtheApp(); cy.get(".rc-select-selection-item > .rc-select-selection-item-content") @@ -118,7 +118,7 @@ describe("Theme validation usecase for multi-select widget", function() { cy.goToEditFromPublish(); }); - it("Validate current theme feature", function() { + it("3. Validate current theme feature", function() { cy.get("#canvas-selection-0").click({ force: true }); //Change the Theme cy.get(commonlocators.changeThemeBtn).click({ force: true }); @@ -138,7 +138,7 @@ describe("Theme validation usecase for multi-select widget", function() { }); }); - it("Publish the App and validate change of Theme across the app in publish mode", function() { + it("4. Publish the App and validate change of Theme across the app in publish mode", function() { cy.PublishtheApp(); cy.get(".rc-select-selection-item > .rc-select-selection-item-content") .first() diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js index dd58a820b5..3729deac88 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js @@ -1,7 +1,8 @@ describe("Visual regression tests", () => { // for any changes in UI, update the screenshot in snapshot folder, to do so: // 1. Delete the required screenshot which you want to update - // 2. Run test in headless mode with any browser (to maintain same resolution in CI) + // 2. Run test in headless mode with chrome (to maintain same resolution in CI) + // command: "npx cypress run --spec cypress/integration/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js --browser chrome" // 3. New screenshot will be generated in the snapshot folder it("Verify SwitchGroup inline enable/disbale", () => { @@ -18,7 +19,7 @@ describe("Visual regression tests", () => { //Unchecking & verify snap cy.get(".t--property-control-inline input") .uncheck({ force: true }) - .wait(200) + .wait(2000) .should("not.be.checked"); cy.get("[data-testid=switchgroup-container]").matchImageSnapshot( "inlineDisabled", @@ -27,7 +28,7 @@ describe("Visual regression tests", () => { //Checking again & verify snap cy.get(".t--property-control-inline input") .check({ force: true }) - .wait(200) + .wait(2000) .should("be.checked"); cy.get("[data-testid=switchgroup-container]").matchImageSnapshot( @@ -37,7 +38,7 @@ describe("Visual regression tests", () => { //Unchecking again & verify snap cy.get(".t--property-control-inline input") .uncheck({ force: true }) - .wait(200) + .wait(2000) .should("not.be.checked"); // taking screenshot of app home page in edit mode cy.get("[data-testid=switchgroup-container]").matchImageSnapshot( diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormReset_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormReset_spec.js index 9941b6f673..9aa0124fdc 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormReset_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/FormReset_spec.js @@ -12,20 +12,18 @@ describe("Form reset functionality", function() { .eq(2) .click() .should("have.class", "selected-row"); - // Select three options - cy.get(widgets.multiSelectWidget).click({ force: true }); - cy.get(widgets.multiSelectWidget).type("Option"); + cy.wait(2000); + cy.get(".rc-select-selection-overflow").click({ force: true }); cy.dropdownMultiSelectDynamic("Option 1"); cy.dropdownMultiSelectDynamic("Option 2"); cy.dropdownMultiSelectDynamic("Option 3"); // Verify input should include the name "lindsay.ferguson@reqres.in" - cy.get(widgets.inputWidget + " " + "input") + cy.get(".text-input-wrapper input") + .eq(0) .invoke("attr", "value") .should("contain", "lindsay.ferguson@reqres.in"); // Reset the form - cy.get(widgets.formButtonWidget) - .contains("Reset") - .click({ force: true }); + cy.get("button:contains('Reset')").click({ force: true }); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(500); // verify table should not have selected row @@ -39,32 +37,27 @@ describe("Form reset functionality", function() { }, ); // Verify input should not include "lindsay.ferguson@reqres.in" - cy.get(widgets.inputWidget + " " + "input") + cy.get(".text-input-wrapper input") + .eq(0) .invoke("attr", "value") .should("not.contain", "lindsay.ferguson@reqres.in"); // input widgets should not be in error state - cy.get(widgets.inputWidget + " " + "input").should( - "not.have.css", - "border-color", - "rgb(242, 43, 43)", - ); + cy.get(".text-input-wrapper input") + .eq(0) + .should("not.have.css", "border-color", "rgb(242, 43, 43)"); - cy.get(widgets.currencyInputWidget + " " + "input").should( - "not.have.css", - "border-color", - "rgb(242, 43, 43)", - ); + cy.get(".text-input-wrapper input") + .eq(0) + .should("not.have.css", "border-color", "rgb(242, 43, 43)"); - cy.get(widgets.phoneInputWidget + " " + "input").should( - "not.have.css", - "border-color", - "rgb(242, 43, 43)", - ); + cy.get(".text-input-wrapper input") + .eq(1) + .should("not.have.css", "border-color", "rgb(242, 43, 43)"); // Earlier select widget used to remain in error state which wasn't an expected behavior after reset // now even select widget will not show error after reset. - cy.get(`.rc-select-selector`).should( + cy.get(`.rc-select-selection-overflow`).should( "not.have.css", "border-color", "rgb(242, 43, 43)", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Input/Input_MaxChar_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Input/Input_MaxChar_spec.js index 6440f83395..175fa58ea1 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Input/Input_MaxChar_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Input/Input_MaxChar_spec.js @@ -18,7 +18,9 @@ describe("Input Widget Max Char Functionality", function() { it("Text Input maxChar shows error if defaultText longer", () => { cy.get(widgetsPage.innertext).click(); cy.get(".bp3-popover-content").should(($x) => { - expect($x).contain("Default text length must be less than 5 characters"); + expect($x).contain( + "Default text length must be less than or equal to 5 characters", + ); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FieldProperties_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FieldProperties_spec.js index 5c331428b7..6a776e0d47 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FieldProperties_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FieldProperties_spec.js @@ -38,7 +38,9 @@ describe("Text Field Property Control", () => { cy.testJsontext("maxchars", 5); cy.get(`${fieldPrefix}-name input`).click(); cy.get(".bp3-popover-content").should(($x) => { - expect($x).contain("Default text length must be less than 5 characters"); + expect($x).contain( + "Default text length must be less than or equal to 5 characters", + ); }); cy.testJsontext("maxchars", ""); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_HiddenFields_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_HiddenFields_spec.js index 1be037bda0..5c02495515 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_HiddenFields_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_HiddenFields_spec.js @@ -83,7 +83,7 @@ describe("JSON Form Hidden fields", () => { cy.testJsontext("text", "{{JSON.stringify(JSONForm1.formData)}}"); }); - it("can hide Array Field", () => { + it("1. can hide Array Field", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("education"); hideAndVerifyProperties("education", [ @@ -94,7 +94,7 @@ describe("JSON Form Hidden fields", () => { ]); }); - it("can hide Array Field's inner fields", () => { + it("2. can hide Array Field's inner fields", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("education"); cy.openFieldConfiguration("__array_item__"); @@ -105,7 +105,7 @@ describe("JSON Form Hidden fields", () => { }); }); - it("can hide Checkbox Field", () => { + it("3. can hide Checkbox Field", () => { // Add new custom field addCustomField("Checkbox"); @@ -114,7 +114,7 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("can hide Currency Field", () => { + it("4. can hide Currency Field", () => { const defaultValue = 1000; // Add new custom field addCustomField("Currency Input"); @@ -123,28 +123,28 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("can hide Date Field", () => { + it("5. can hide Date Field", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("dob"); hideAndVerifyProperties("dob", "10/12/1992"); }); - it("can hide Input Field", () => { + it("6. can hide Input Field", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("name"); hideAndVerifyProperties("name", "John"); }); - it("can hide Multiselect Field", () => { + it("7. can hide Multiselect Field", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("hobbies"); hideAndVerifyProperties("hobbies", ["travelling", "swimming"]); }); - it("can hide Object Field", () => { + it("8. can hide Object Field", () => { cy.openPropertyPane("jsonformwidget"); cy.openFieldConfiguration("address"); @@ -154,7 +154,7 @@ describe("JSON Form Hidden fields", () => { }); }); - it("can hide Phone Number Input Field", () => { + it("9. can hide Phone Number Input Field", () => { const defaultValue = "1000"; // Add new custom field addCustomField("Phone Number Input"); @@ -166,7 +166,7 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("can hide Radio Group Field", () => { + it("10. can hide Radio Group Field", () => { const defaultValue = "Y"; // Add new custom field addCustomField("Phone Number Input"); @@ -178,7 +178,7 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("can hide Select Field", () => { + it("11. can hide Select Field", () => { const defaultValue = "BLUE"; // Add new custom field addCustomField(/^Select/); @@ -190,7 +190,7 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("can hide Switch Field", () => { + it("12. can hide Switch Field", () => { // Add new custom field addCustomField("Switch"); @@ -199,7 +199,7 @@ describe("JSON Form Hidden fields", () => { removeCustomField(); }); - it("hides fields on first load", () => { + it("13. hides fields on first load", () => { cy.openPropertyPane("jsonformwidget"); // hide education field @@ -214,6 +214,7 @@ describe("JSON Form Hidden fields", () => { // publish the app cy.PublishtheApp(); + cy.wait(1000); // Check if name is hidden cy.get(`${fieldPrefix}-name`).should("not.exist"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Statbox_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Statbox_spec.js index 879a282d55..2e8bcaf676 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Statbox_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Statbox_spec.js @@ -1,4 +1,5 @@ const dsl = require("../../../../../fixtures/StatboxDsl.json"); +const dsl1 = require("../../../../../fixtures/dynamicHeightStatboxdsl.json"); const explorer = require("../../../../../locators/explorerlocators.json"); const data = require("../../../../../fixtures/example.json"); const widgetsPage = require("../../../../../locators/Widgets.json"); @@ -87,9 +88,10 @@ describe("Statbox Widget Functionality", function() { }); it("5. Verify Statbox can be placed inside another widget", () => { + cy.addDsl(dsl1); cy.get(explorer.addWidget).click(); // placing statbox widget inside container widget - cy.dragAndDropToCanvas("containerwidget", { x: 500, y: 300 }); + //cy.dragAndDropToCanvas("containerwidget", { x: 500, y: 300 }); cy.dragAndDropToWidget("statboxwidget", "containerwidget", { x: 100, y: 100, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js index 3b1cd8eede..fa30c0b995 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js @@ -233,7 +233,7 @@ describe("RichTextEditor Widget Functionality", function() { }); }); - it.only("Check if the cursor position is at the end for the RTE widget", function() { + it("Check if the cursor position is at the end for the RTE widget", function() { const tinyMceId = "rte-6h8j08u7ea"; const testString = "Test Content"; const testStringLen = testString.length; @@ -274,6 +274,18 @@ describe("RichTextEditor Widget Functionality", function() { }); }); + it("Check if button for Underline exists within the Toolbar of RTE widget", () => { + cy.get('[aria-label="Underline"]').should("exist"); + }); + + it("Check if button for Background Color is rendered only once within the Toolbar of RTE widget", () => { + cy.get('[aria-label="Background color"]').should("have.length", 1); + }); + + it("Check if button for Text Color is rendered only once within the Toolbar of RTE widget", () => { + cy.get('[aria-label="Text color"]').should("have.length", 1); + }); + afterEach(() => { cy.goToEditFromPublish(); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/NumberSlider_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/NumberSlider_spec.ts index a4a30f6117..66da317d67 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/NumberSlider_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/NumberSlider_spec.ts @@ -85,13 +85,13 @@ describe("Number Slider spec", () => { propPane.UpdatePropertyFieldValue("Default Value", "-10"); agHelper.VerifyEvaluatedErrorMessage( - "This value must be greater than min value", + "This value must be greater than or equal to the min value", ); propPane.UpdatePropertyFieldValue("Default Value", "110"); agHelper.VerifyEvaluatedErrorMessage( - "This value must be less than max value", + "This value must be less than or equal to the max value", ); propPane.UpdatePropertyFieldValue("Default Value", "asd"); @@ -121,6 +121,8 @@ describe("Number Slider spec", () => { .focus() .type("{rightArrow}"); + agHelper.Sleep(2000); //for the changes to reflect in text widget + // Assert the Text widget has value 20 agHelper.GetText(getWidgetSelector(WIDGET.TEXT)).then(($label) => { expect($label).to.eq("20"); @@ -133,8 +135,7 @@ describe("Number Slider spec", () => { .type("{leftArrow}") .type("{leftArrow}"); - agHelper.Sleep(200); - + agHelper.Sleep(2000); //for the changes to reflect in text widget // Assert the Text widget has value 0 agHelper.GetText(getWidgetSelector(WIDGET.TEXT)).then(($label) => { expect($label).to.eq("0"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/RangeSlider_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/RangeSlider_spec.ts index a10e97624d..ef1adaf62f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/RangeSlider_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Sliders/RangeSlider_spec.ts @@ -39,8 +39,6 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be a number"); propPane.UpdatePropertyFieldValue("Min. Value", "0"); - - // agHelper.VerifyEvaluatedValue("0"); }); it("2. Validates Max. Value", () => { @@ -59,8 +57,6 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be a number"); propPane.UpdatePropertyFieldValue("Max. Value", "100"); - - // agHelper.VerifyEvaluatedValue("100"); }); it("3. Validates Step Size", () => { @@ -79,8 +75,6 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be a number"); propPane.UpdatePropertyFieldValue("Step Size", "1"); - - // agHelper.VerifyEvaluatedValue("1"); }); it("4. Validates Min Range", () => { @@ -101,15 +95,13 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be less than 100"); propPane.UpdatePropertyFieldValue("Min. Range", "10"); - - // agHelper.VerifyEvaluatedValue("10"); }); it("5. Validates Default Start Value", () => { propPane.UpdatePropertyFieldValue("Default Start Value", "-100"); agHelper.VerifyEvaluatedErrorMessage( - "This value must be greater than min value", + "This value must be greater than or equal to the min value", ); propPane.UpdatePropertyFieldValue("Default Start Value", "110"); @@ -123,8 +115,6 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be a number"); propPane.UpdatePropertyFieldValue("Default Start Value", "10"); - - // agHelper.VerifyEvaluatedValue("10"); }); it("6. Validates Default End Value", () => { @@ -137,7 +127,7 @@ describe("Range Slider spec", () => { propPane.UpdatePropertyFieldValue("Default End Value", "110"); agHelper.VerifyEvaluatedErrorMessage( - "This value must be less than max value", + "This value must be less than or equal to the max value", ); propPane.UpdatePropertyFieldValue("Default End Value", "asd"); @@ -145,8 +135,6 @@ describe("Range Slider spec", () => { agHelper.VerifyEvaluatedErrorMessage("This value must be a number"); propPane.UpdatePropertyFieldValue("Default End Value", "100"); - - // agHelper.VerifyEvaluatedValue("100"); }); it("7. Change Step Size and check if binding value changes", () => { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Add_new_row_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Add_new_row_spec.js index abc1712f09..d52ae3ebae 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Add_new_row_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Add_new_row_spec.js @@ -16,7 +16,7 @@ describe("Table widget Add new row feature's", () => { cy.addDsl(dsl); }); - it("should test that allow Add new row property is present", () => { + it("1.1. should test that allow Add new row property is present", () => { cy.openPropertyPane("tablewidgetv2"); cy.get(".t--property-control-allowaddingarow").should("exist"); cy.get( @@ -24,7 +24,7 @@ describe("Table widget Add new row feature's", () => { ).should("exist"); }); - it("should test that Add new row link appears on the UI when the allow add new row property is enabled", () => { + it("1.2. should test that Add new row link appears on the UI when the allow add new row property is enabled", () => { cy.get(".t--add-new-row").should("not.exist"); propPane.ToggleOnOrOff("Allow adding a row", "On"); cy.get(".t--add-new-row").should("exist"); @@ -32,7 +32,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--add-new-row").should("not.exist"); }); - it("should test that onSave, onDiscard and default row are showing up only when the allow add new property is enabled", () => { + it("1.3. should test that onSave, onDiscard and default row are showing up only when the allow add new property is enabled", () => { cy.get(".t--property-control-onsave").should("not.exist"); cy.get(".t--property-control-ondiscard").should("not.exist"); cy.get(".t--property-control-defaultvalues").should("not.exist"); @@ -42,14 +42,14 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--property-control-defaultvalues").should("exist"); }); - it("should test that add new row link is disabled during the inline editing flow", () => { + it("1.4. should test that add new row link is disabled during the inline editing flow", () => { cy.get(".t--add-new-row.disabled").should("not.exist"); cy.makeColumnEditable("step"); cy.editTableCell(0, 0); cy.get(".t--add-new-row.disabled").should("exist"); }); - it("should test that clicking on add new row link adds an empty row at the top of the table", () => { + it("1.5. should test that clicking on add new row link adds an empty row at the top of the table", () => { cy.openPropertyPane("tablewidgetv2"); cy.get(".tableWrap .new-row").should("not.exist"); cy.get(".t--add-new-row").click(); @@ -57,7 +57,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that new row is getting populated with the default row property value", () => { + it("1.6. should test that new row is getting populated with the default row property value", () => { cy.updateCodeInput( ".t--property-control-defaultvalues", "{{{step: 'newStepCell'}}}", @@ -70,7 +70,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that inline editing, row selection, pagination, search, filters are actions cannot be performed while in add new row feature", () => { + it("1.7. should test that inline editing, row selection, pagination, search, filters are actions cannot be performed while in add new row feature", () => { cy.get(".t--widget-tablewidgetv2 .t--search-input").should("exist"); cy.get(".t--widget-tablewidgetv2 .t--table-filter-toggle-btn").should( "exist", @@ -111,7 +111,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that only editable column cells are in editmode in the new row", () => { + it("1.8. should test that only editable column cells are in editmode in the new row", () => { cy.get(".t--add-new-row").click(); cy.get( `[data-colindex=0][data-rowindex=0] .t--inlined-cell-editor`, @@ -136,7 +136,7 @@ describe("Table widget Add new row feature's", () => { ).should("not.exist"); }); - it("should test that newRow property holds the entered data", () => { + it("1.9. should test that newRow property holds the entered data", () => { cy.makeColumnEditable("step"); cy.makeColumnEditable("task"); cy.enterTableCellValue(0, 0, "22"); @@ -150,7 +150,7 @@ describe("Table widget Add new row feature's", () => { ); }); - it("should test that non data (iconBitton, button, menubutton) column cells are not showing up", () => { + it("1.10. should test that non data (iconBitton, button, menubutton) column cells are not showing up", () => { cy.openPropertyPane("tablewidgetv2"); cy.editColumn("step"); ["Button", "Menu Button", "Icon Button"].forEach((columnType) => { @@ -174,7 +174,7 @@ describe("Table widget Add new row feature's", () => { cy.addDsl(dsl); }); - it("should test that validation is working for a new row cell", () => { + it("2.1. should test that validation is working for a new row cell", () => { cy.openPropertyPane("tablewidgetv2"); propPane.ToggleOnOrOff("Allow adding a row", "On"); cy.get(".t--add-new-row").click(); @@ -262,7 +262,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that validation variable isNewRow is working", () => { + it("2.2. should test that validation variable isNewRow is working", () => { propPane.UpdatePropertyFieldValue( "Valid", "{{isNewRow ? (editedValue === 1) : (editedValue === 2)}}", @@ -290,7 +290,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that validation is working for more than one add new row cell at a time", () => { + it("2.3. should test that validation is working for more than one add new row cell at a time", () => { propPane.UpdatePropertyFieldValue("Valid", "{{editedValue === 1}}"); cy.get(".t--property-pane-back-btn").click(); cy.wait(500); @@ -306,7 +306,7 @@ describe("Table widget Add new row feature's", () => { cy.get(`.t--inlined-cell-editor-has-error`).should("have.length", 2); }); - it("should test that validation error message only appears when a cell is in focus", () => { + it("2.4. should test that validation error message only appears when a cell is in focus", () => { cy.get(".error-tooltip .bp3-popover-content").should("not.exist"); cy.get(`[data-colindex=1][data-rowindex=0] input`).focus(); cy.get(".error-tooltip .bp3-popover-content").should("have.length", 1); @@ -316,7 +316,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".error-tooltip .bp3-popover-content").should("have.length", 1); }); - it("should test that save button is disabled when there is an error", () => { + it("2.5. should test that save button is disabled when there is an error", () => { cy.get(".t--save-new-row").should("be.disabled"); cy.get(`.t--inlined-cell-editor-has-error`).should("have.length", 2); cy.enterTableCellValue(0, 0, "1"); @@ -335,7 +335,7 @@ describe("Table widget Add new row feature's", () => { cy.addDsl(dsl); }); - it("should test that discard button is undoing the add new feature", () => { + it("3.1. should test that discard button is undoing the add new feature", () => { cy.openPropertyPane("tablewidgetv2"); propPane.ToggleOnOrOff("Allow adding a row", "On"); cy.get(".tableWrap .new-row").should("not.exist"); @@ -344,7 +344,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".t--discard-new-row").click({ force: true }); }); - it("should test that discard events is triggered when user clicks on the discard button", () => { + it("3.2. should test that discard events is triggered when user clicks on the discard button", () => { cy.get( ".t--property-control-ondiscard .t--open-dropdown-Select-Action", ).click({ force: true }); @@ -354,16 +354,11 @@ describe("Table widget Add new row feature's", () => { cy.get(".tableWrap .new-row").should("exist"); cy.get(".t--discard-new-row").click({ force: true }); cy.get(widgetsPage.toastAction).should("be.visible"); - cy.get(widgetsPage.toastActionText) - .last() - .invoke("text") - .then((text) => { - expect(text).to.equal("discarded!!"); - }); + agHelper.AssertContains("discarded!!"); cy.get(".tableWrap .new-row").should("not.exist"); }); - it("should test that save event is triggered when user clicks on the save button", () => { + it("3.3. should test that save event is triggered when user clicks on the save button", () => { cy.get( ".t--property-control-onsave .t--open-dropdown-Select-Action", ).click({ force: true }); @@ -373,12 +368,7 @@ describe("Table widget Add new row feature's", () => { cy.get(".tableWrap .new-row").should("exist"); cy.get(".t--save-new-row").click({ force: true }); cy.get(widgetsPage.toastAction).should("be.visible"); - cy.get(widgetsPage.toastActionText) - .last() - .invoke("text") - .then((text) => { - expect(text).to.equal("saved!!"); - }); + agHelper.AssertContains("saved!!"); cy.get(".tableWrap .new-row").should("not.exist"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js index 39906e06f6..4eca007482 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js @@ -2,6 +2,7 @@ const dsl = require("../../../../../fixtures/Table/InlineEditingDSL.json"); const commonlocators = require("../../../../../locators/commonlocators.json"); const widgetsPage = require("../../../../../locators/Widgets.json"); import { ObjectsRegistry } from "../../../../../support/Objects/Registry"; +import { PROPERTY_SELECTOR } from "../../../../../locators/WidgetLocators"; const agHelper = ObjectsRegistry.AggregateHelper; describe("Table widget inline editing functionality", () => { @@ -711,4 +712,104 @@ describe("Table widget inline editing functionality", () => { "[data-colindex='0'][data-rowindex='0'] .t--inlined-cell-editor", ).should("not.have.css", "height", "34px"); }); + + it("26. should check if updatedRowIndex is getting updated for single row update mode", () => { + cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 }); + cy.get(".t--widget-textwidget").should("exist"); + cy.updateCodeInput( + ".t--property-control-text", + `{{Table1.updatedRowIndex}}`, + ); + + cy.dragAndDropToCanvas("buttonwidget", { x: 300, y: 300 }); + cy.get(".t--widget-buttonwidget").should("exist"); + cy.get(PROPERTY_SELECTOR.onClick) + .find(".t--js-toggle") + .click(); + cy.updateCodeInput(".t--property-control-label", "Reset"); + cy.updateCodeInput( + PROPERTY_SELECTOR.onClick, + `{{resetWidget("Table1",true)}}`, + ); + + // case 1: check if updatedRowIndex has -1 as the default value: + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + + cy.openPropertyPane("tablewidgetv2"); + + cy.makeColumnEditable("step"); + cy.wait(1000); + + // case 2: check if updatedRowIndex is 0, when cell at row 0 is updated. + cy.editTableCell(0, 0); + cy.enterTableCellValue(0, 0, "#12").type("{enter}"); + cy.get(commonlocators.textWidgetContainer).should("contain.text", 0); + + // case 3: check if updatedRowIndex is -1 when changes are discarded. + cy.discardTableRow(4, 0); + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + + // case 4: check if the updateRowIndex is -1 when widget is reset + cy.editTableCell(0, 1); + cy.enterTableCellValue(0, 1, "#13").type("{enter}"); + cy.contains("Reset").click({ force: true }); + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + + // case 5: check if the updatedRowIndex changes to -1 when the table data changes. + cy.wait(1000); + cy.editTableCell(0, 2); + cy.enterTableCellValue(0, 2, "#14").type("{enter}"); + cy.openPropertyPane("tablewidgetv2"); + cy.get(widgetsPage.tabedataField).type("{backspace}"); + cy.wait(300); + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + }); + + it.only("27. should check if updatedRowIndex is getting updated for multi row update mode", () => { + cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 }); + cy.get(".t--widget-textwidget").should("exist"); + cy.updateCodeInput( + ".t--property-control-text", + `{{Table1.updatedRowIndex}}`, + ); + + cy.dragAndDropToCanvas("buttonwidget", { x: 300, y: 300 }); + cy.get(".t--widget-buttonwidget").should("exist"); + cy.get(PROPERTY_SELECTOR.onClick) + .find(".t--js-toggle") + .click(); + cy.updateCodeInput(".t--property-control-label", "Reset"); + cy.updateCodeInput( + PROPERTY_SELECTOR.onClick, + `{{resetWidget("Table1",true)}}`, + ); + + cy.openPropertyPane("tablewidgetv2"); + + cy.makeColumnEditable("step"); + cy.get(".t--button-tab-CUSTOM").click({ force: true }); + cy.wait(1000); + + // case 1: check if updatedRowIndex is 0, when cell at row 0 is updated. + cy.editTableCell(0, 0); + cy.enterTableCellValue(0, 0, "#12").type("{enter}"); + cy.get(commonlocators.textWidgetContainer).should("contain.text", 0); + + // case 2: check if the updateRowIndex is -1 when widget is reset + cy.editTableCell(0, 1); + cy.enterTableCellValue(0, 1, "#13").type("{enter}"); + cy.get(commonlocators.textWidgetContainer).should("contain.text", 1); + cy.contains("Reset").click({ force: true }); + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + + // case 3: check if the updatedRowIndex changes to -1 when the table data changes. + cy.wait(1000); + cy.editTableCell(0, 2); + cy.enterTableCellValue(0, 2, "#14").type("{enter}"); + cy.get(commonlocators.textWidgetContainer).should("contain.text", 2); + cy.openPropertyPane("tablewidgetv2"); + cy.get(widgetsPage.tabedataField).type("{backspace}"); + cy.wait(300); + cy.get(commonlocators.textWidgetContainer).should("contain.text", -1); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Widget_Copy_Paste_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Widget_Copy_Paste_spec.js index f2850c8f49..c3e0d8fb19 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Widget_Copy_Paste_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_Widget_Copy_Paste_spec.js @@ -44,7 +44,7 @@ describe("Test Suite to validate copy/paste table Widget V2", function() { cy.hoverAndClickParticularIndex(1); cy.selectAction("Show Bindings"); cy.get(apiwidget.propertyList).then(function($lis) { - expect($lis).to.have.length(19); + expect($lis).to.have.length(20); expect($lis.eq(0)).to.contain("{{Table1Copy.selectedRow}}"); expect($lis.eq(1)).to.contain("{{Table1Copy.selectedRows}}"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js index 9aebab1576..d19a4eee98 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js @@ -12,6 +12,15 @@ describe("Text Widget color/font/alignment Functionality", function() { cy.openPropertyPane("textwidget"); }); it("Test to validate parsing link", function() { + // Add link to text widget + cy.testCodeMirror("app.appsmith.com"); + // check if it's a link when no http or https is passed, + cy.get(`${commonlocators.headingTextStyle} a`).should( + "have.attr", + "href", + "http://app.appsmith.com", + ); + // Add link to text widget cy.testCodeMirror("https://app.appsmith.com"); // check if it's parsed as link diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/AuthenticatedApiDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/AuthenticatedApiDatasource_spec.js index abbe2eebe5..1575188951 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/AuthenticatedApiDatasource_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/AuthenticatedApiDatasource_spec.js @@ -1,6 +1,10 @@ const apiwidget = require("../../../../locators/apiWidgetslocator.json"); const datasourceFormData = require("../../../../fixtures/datasources.json"); const datasourceEditor = require("../../../../locators/DatasourcesEditor.json"); +const testdata = require("../../../../fixtures/testdata.json"); + +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; +let dataSources = ObjectsRegistry.DataSources; describe("Authenticated API Datasource", function() { const URL = datasourceFormData["authenticatedApiUrl"]; @@ -44,4 +48,28 @@ describe("Authenticated API Datasource", function() { cy.contains(queryParams).should("not.exist"); cy.deleteDatasource("FakeAuthenticatedApi"); }); + + it("4. Bug: 18051 - Save and Authorise should return to datasource page in view mode and not new datasource page", () => { + cy.NavigateToAPI_Panel(); + cy.get(apiwidget.createAuthApiDatasource).click(); + cy.generateUUID().then((uuid) => { + cy.renameDatasource(uuid); + cy.fillAuthenticatedAPIForm(); + cy.addOAuth2AuthorizationCodeDetails( + testdata.accessTokenUrl, + testdata.clientID, + testdata.clientSecret, + testdata.authorizationURL, + ); + dataSources.AuthAPISaveAndAuthorize(); + cy.xpath('//input[@name="email"]').type("Test@email.com"); + cy.xpath('//input[@name="email"]').type("Test"); + cy.xpath("//input[@name='password']").type("Test@123"); + cy.xpath("//input[@id='login-submit']").click(); + cy.wait(2000); + cy.reload(); + cy.get(".t--edit-datasource").should("be.visible"); + dataSources.DeleteDatasouceFromActiveTab(uuid); + }); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceSchema_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceSchema_spec.ts new file mode 100644 index 0000000000..b15a7a808a --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceSchema_spec.ts @@ -0,0 +1,28 @@ +const testdata = require("../../../../fixtures/testdata.json"); +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; + +const agHelper = ObjectsRegistry.AggregateHelper, + dataSources = ObjectsRegistry.DataSources; + +describe("Datasource form related tests", function() { + + it("1. Verify datasource structure refresh on save", () => { + agHelper.GenerateUUID(); + cy.get("@guid").then((uid) => { + const guid = uid; + const dataSourceName = "Postgres " + guid; + cy.get(dataSources._dsEntityItem).click(); + dataSources.NavigateToDSCreateNew(); + dataSources.CreatePlugIn("PostgreSQL"); + agHelper.RenameWithInPane(dataSourceName, false); + dataSources.FillPostgresDSForm(false, "docker", "wrongPassword"); + dataSources.verifySchema("Failed to initialize pool"); + cy.get(dataSources._activeDS) + .contains(dataSourceName) + .click(); + dataSources.updatePassword("docker"); + dataSources.verifySchema("public."); + dataSources.DeleteDatasouceFromActiveTab(dataSourceName); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/EmbedSettings/EmbedSettings_spec.js b/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/EmbedSettings/EmbedSettings_spec.js new file mode 100644 index 0000000000..963f9b345f --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite_Fat/ClientSideTests/EmbedSettings/EmbedSettings_spec.js @@ -0,0 +1,143 @@ +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; +const adminSettings = require("../../../../locators/AdminsSettings"); + +describe("Embed settings options", function() { + const { + AggregateHelper: agHelper, + DeployMode: deployMode, + EntityExplorer: ee, + HomePage: homePage, + } = ObjectsRegistry; + + const getIframeBody = () => { + // get the iframe > document > body + // and retry until the body element is not empty + return ( + cy + .get(".t--widget-iframewidget iframe") + .its("0.contentDocument.body") + .should("not.be.empty") + // wraps "body" DOM element to allow + // chaining more Cypress commands, like ".find(...)" + // https://on.cypress.io/wrap + .then(cy.wrap) + ); + }; + + before(() => { + ee.DragDropWidgetNVerify("buttonwidget", 100, 100); + deployMode.DeployApp(); + cy.get("[data-cy='viewmode-share']").click(); + cy.get(".t--deployed-url input") + .invoke("attr", "value") + .as("embeddedAppUrl"); + cy.enablePublicAccess(); + cy.get(".t--back-to-home").click(); + homePage.CreateNewApplication(); + ee.DragDropWidgetNVerify("iframewidget", 100, 100); + cy.get("@embeddedAppUrl").then((url) => { + cy.testJsontext("url", url); + }); + // cy.testJsontext("url", this.embeddedAppUrl); + deployMode.DeployApp(); + cy.get("[data-cy='viewmode-share']").click(); + cy.get(".t--deployed-url input") + .invoke("attr", "value") + .as("deployUrl"); + cy.enablePublicAccess(); + cy.wait(6000); + getIframeBody() + .contains("Submit") + .should("exist"); + }); + + beforeEach(() => { + agHelper.RestoreLocalStorageCache(); + }); + + afterEach(() => { + agHelper.SaveLocalStorageCache(); + }); + + describe("Wrapper to get access to the alias in all tests", () => { + it("1. Allow embedding everywhere", function() { + cy.log(this.deployUrl); + cy.get(".t--back-to-home").click(); + cy.get(".admin-settings-menu-option").click(); + cy.get(".t--admin-settings-APPSMITH_ALLOWED_FRAME_ANCESTORS").within( + () => { + cy.get("input") + .eq(0) + .click(); + }, + ); + cy.get(adminSettings.saveButton).click(); + cy.wait(60000); + cy.wait(["@getEnvVariables", "@getEnvVariables"]).then((interception) => { + const { + APPSMITH_ALLOWED_FRAME_ANCESTORS, + } = interception[1].response.body.data; + expect(APPSMITH_ALLOWED_FRAME_ANCESTORS).to.equal("*"); + }); + cy.get(adminSettings.restartNotice).should("not.exist"); + cy.visit(this.deployUrl); + getIframeBody() + .contains("Submit") + .should("exist"); + }); + + it("2. Limit embedding", function() { + cy.log(this.deployUrl); + cy.get(".t--back-to-home").click(); + cy.get(".admin-settings-menu-option").click(); + cy.get(".t--admin-settings-APPSMITH_ALLOWED_FRAME_ANCESTORS").within( + () => { + cy.get("input") + .eq(1) + .click(); + cy.get(".bp3-tag-remove") + .eq(1) + .click(); + cy.get(".bp3-tag-remove") + .eq(0) + .click(); + cy.get(".bp3-input-ghost") + .type(window.location.origin) + .blur(); + }, + ); + cy.get(adminSettings.saveButton).click(); + cy.wait(50000); + cy.get(adminSettings.restartNotice).should("not.exist"); + cy.visit(this.deployUrl); + getIframeBody() + .contains("Submit") + .should("exist"); + }); + it("3. Disable everywhere", function() { + cy.log(this.deployUrl); + cy.get(".t--back-to-home").click(); + cy.get(".admin-settings-menu-option").click(); + cy.get(".t--admin-settings-APPSMITH_ALLOWED_FRAME_ANCESTORS").within( + () => { + cy.get("input") + .last() + .click(); + }, + ); + cy.get(adminSettings.saveButton).click(); + cy.wait(60000); + cy.get(adminSettings.restartNotice).should("not.exist"); + cy.visit(this.deployUrl); + cy.wait(["@getEnvVariables", "@getEnvVariables"]).then((interception) => { + const { + APPSMITH_ALLOWED_FRAME_ANCESTORS, + } = interception[1].response.body.data; + expect(APPSMITH_ALLOWED_FRAME_ANCESTORS).to.equal("'none'"); + }); + getIframeBody() + .contains("Submit") + .should("not.exist"); + }); + }); +}); diff --git a/app/client/cypress/locators/AdminsSettings.js b/app/client/cypress/locators/AdminsSettings.js index ebfc82bd16..b0bc515d87 100644 --- a/app/client/cypress/locators/AdminsSettings.js +++ b/app/client/cypress/locators/AdminsSettings.js @@ -25,4 +25,5 @@ export default { disconnectBtn: "[data-testid='disconnect-service-button']", formSignupDisabled: "[data-cy='APPSMITH_SIGNUP_DISABLED']", formLoginDisabled: "[data-cy='APPSMITH_FORM_LOGIN_DISABLED']", + embedSettings: ".t--admin-settings-APPSMITH_ALLOWED_FRAME_ANCESTORS", }; diff --git a/app/client/cypress/locators/commonlocators.json b/app/client/cypress/locators/commonlocators.json index c5a62b979e..dcb8be32d4 100644 --- a/app/client/cypress/locators/commonlocators.json +++ b/app/client/cypress/locators/commonlocators.json @@ -192,5 +192,16 @@ "cancelActionExecution": ".t--cancel-action-button", "codeScannerScannerLayout": ".t--property-control-scannerlayout", "codeScannerVideo": ".code-scanner-camera-container video", - "codeScannerDisabledSVGIcon": ".code-scanner-camera-container div[disabled] svg" + "codeScannerDisabledSVGIcon": ".code-scanner-camera-container div[disabled] svg", + "layoutHeightDropdown": ".t--property-control-height .remixicon-icon", + "fixed": "[data-cy='t--dropdown-option-Fixed']", + "autoHeight": "[data-cy='t--dropdown-option-Auto Height']", + "autoHeightWithLimits": "[data-cy='t--dropdown-option-Auto Height with limits']", + "minHeight": "minheight\\(inrows\\)", + "maxHeight": "maxheight\\(inrows\\)", + "overlayMin": "[data-cy='t--auto-height-overlay-min']", + "overlayMax": "[data-cy='t--auto-height-overlay-max']", + "addOption": ".t--property-control-options-add", + "showTabsControl": ".t--property-control-showtabs .bp3-control-indicator", + "checkboxIndicator": ".t--draggable-checkboxwidget .bp3-control-indicator" } diff --git a/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineDisabled.snap.png b/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineDisabled.snap.png index bb646fa2d0..34696421e1 100644 Binary files a/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineDisabled.snap.png and b/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineDisabled.snap.png differ diff --git a/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineEnabled.snap.png b/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineEnabled.snap.png index 8b40409612..02c692a097 100644 Binary files a/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineEnabled.snap.png and b/app/client/cypress/snapshots/Smoke_TestSuite/ClientSideTests/VisualTests/WidgetsLayout_spec.js/inlineEnabled.snap.png differ diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 2167f12db8..dfe356c27a 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -426,9 +426,14 @@ export class AggregateHelper { .invoke("text"); } - public EnterActionValue(actionName: string, value: string, paste = true) { + public EnterActionValue( + actionName: string, + value: string, + paste = true, + index = 0, + ) { cy.xpath(this.locator._actionTextArea(actionName)) - .first() + .eq(index) .scrollIntoView() .focus() .type("{uparrow}", { force: true }) @@ -437,7 +442,7 @@ export class AggregateHelper { if ($cm.contents != "") { cy.log("The field is not empty"); cy.xpath(this.locator._actionTextArea(actionName)) - .first() + .eq(index) .scrollIntoView() .click({ force: true }) .focused() @@ -447,7 +452,7 @@ export class AggregateHelper { } this.Sleep(); cy.xpath(this.locator._actionTextArea(actionName)) - .first() + .eq(index) .scrollIntoView() .then((el: any) => { if (paste) { diff --git a/app/client/cypress/support/Pages/ApiPage.ts b/app/client/cypress/support/Pages/ApiPage.ts index 851e1d0a20..b0c7302c4c 100644 --- a/app/client/cypress/support/Pages/ApiPage.ts +++ b/app/client/cypress/support/Pages/ApiPage.ts @@ -1,4 +1,7 @@ import { ObjectsRegistry } from "../Objects/Registry"; + +type RightPaneTabs = "datasources" | "connections"; + export class ApiPage { public agHelper = ObjectsRegistry.AggregateHelper; public locator = ObjectsRegistry.CommonLocators; @@ -30,6 +33,7 @@ export class ApiPage { verb + "')]"; private _bodySubTab = (subTab: string) => `[data-cy='tab--${subTab}']`; + private _rightPaneTab = (tab: string) => `[data-cy='t--tab-${tab}']`; _visibleTextSpan = (spanText: string) => "//span[text()='" + spanText + "']"; _visibleTextDiv = (divText: string) => "//div[text()='" + divText + "']"; _noBodyMessageDiv = "#NoBodyMessageDiv"; @@ -241,6 +245,17 @@ export class ApiPage { this.agHelper.GetNClick(this._bodySubTab(subTabName)); } + AssertRightPaneSelectedTab(tabName: RightPaneTabs) { + cy.get(this._rightPaneTab(tabName)).should( + "have.class", + "react-tabs__tab--selected", + ); + } + + SelectRightPaneTab(tabName: RightPaneTabs) { + this.agHelper.GetNClick(this._rightPaneTab(tabName)); + } + ValidateQueryParams(param: { key: string; value: string }) { this.SelectPaneTab("Params"); this.agHelper.ValidateCodeEditorContent(this._paramKey(0), param.key); diff --git a/app/client/cypress/support/Pages/DataSources.ts b/app/client/cypress/support/Pages/DataSources.ts index 71b43f437f..efa918e08a 100644 --- a/app/client/cypress/support/Pages/DataSources.ts +++ b/app/client/cypress/support/Pages/DataSources.ts @@ -28,7 +28,9 @@ export class DataSources { "input[name = 'datasourceConfiguration.authentication.password']"; private _testDs = ".t--test-datasource"; private _saveDs = ".t--save-datasource"; + private _saveAndAuthorizeDS = ".t--save-and-authorize-datasource"; private _datasourceCard = ".t--datasource"; + _dsEntityItem = "[data-guided-tour-id='explorer-entity-Datasources']"; _activeDS = "[data-testid='active-datasource-name']"; _templateMenu = ".t--template-menu"; _templateMenuOption = (action: string) => @@ -45,6 +47,7 @@ export class DataSources { "//div[contains(@class, 't--ds-list')]//span[text()='" + dbName + "']"; _runQueryBtn = ".t--run-query"; _newDatabases = "#new-datasources"; + _newDatasourceContainer = "#new-integrations-wrapper" _selectDatasourceDropdown = "[data-cy=t--datasource-dropdown]"; _selectTableDropdown = "[data-cy=t--table-dropdown]"; _selectSheetNameDropdown = "[data-cy=t--sheetName-dropdown]"; @@ -106,6 +109,7 @@ export class DataSources { _gsScopeOptions = ".ads-dropdown-options-wrapper div > span div span"; private _queryTimeout = "//input[@name='actionConfiguration.timeoutInMillisecond']"; + _getStructureReq = "/api/v1/datasources/*/structure?ignoreCache=true"; public StartDataSourceRoutes() { cy.intercept("PUT", "/api/v1/datasources/*").as("saveDatasource"); @@ -217,6 +221,7 @@ export class DataSources { // cy.get(this._dsCreateNewTab) // .should("be.visible") // .click({ force: true }); + cy.get(this._newDatasourceContainer).scrollTo("bottom"); cy.get(this._newDatabases).should("be.visible"); } @@ -331,6 +336,11 @@ export class DataSources { // }).should("have.nested.property", "response.body.responseMeta.status", 200); } + public AuthAPISaveAndAuthorize() { + cy.get(this._saveAndAuthorizeDS).click(); + this.agHelper.ValidateNetworkStatus("@saveDatasource", 200); + } + public DeleteDatasouceFromActiveTab( datasourceName: string, expectedRes = 200, @@ -680,4 +690,19 @@ export class DataSources { this.agHelper.AssertAutoSave(); this.agHelper.GetNClick(this._queryResponse("QUERY")); } + + //Update with new password in the datasource conf page + public updatePassword(newPassword: string){ + cy.get(this._sectionAuthentication).click(); + cy.get(this._password).type(newPassword); + } + + //Fetch schema from server and validate UI for the updates + public verifySchema(schema: string){ + cy.intercept("GET", this._getStructureReq).as("getDSStructure"); + this.SaveDatasource(); + cy.wait("@getDSStructure").then(() => { + cy.get(".bp3-collapse-body").contains(schema); + }); + } } diff --git a/app/client/cypress/support/Pages/PropertyPane.ts b/app/client/cypress/support/Pages/PropertyPane.ts index 6a354a4550..1c91d2612d 100644 --- a/app/client/cypress/support/Pages/PropertyPane.ts +++ b/app/client/cypress/support/Pages/PropertyPane.ts @@ -95,7 +95,9 @@ export class PropertyPane { this.agHelper.GetNClick(this._colorPickerV2Popover); this.agHelper.GetNClick(this._colorPickerV2Color, colorIndex); } else { - this.agHelper.GetElement(this._colorInput(type)).clear(); + this.agHelper.GetElement(this._colorInput(type)).clear().wait(200); + this.agHelper.TypeText(this._colorInput(type), colorIndex); + this.agHelper.GetElement(this._colorInput(type)).clear().wait(200); this.agHelper.TypeText(this._colorInput(type), colorIndex); //this.agHelper.UpdateInput(this._colorInputField(type), colorIndex);//not working! } diff --git a/app/client/cypress/support/widgetCommands.js b/app/client/cypress/support/widgetCommands.js index 8e4a6dfa46..e90f151492 100644 --- a/app/client/cypress/support/widgetCommands.js +++ b/app/client/cypress/support/widgetCommands.js @@ -1581,3 +1581,62 @@ Cypress.Commands.add("moveToContentTab", () => { .first() .click({ force: true }); }); + +Cypress.Commands.add("openPropertyPaneWithIndex", (widgetType, index) => { + const selector = `.t--draggable-${widgetType}`; + cy.wait(500); + cy.get(selector) + .eq(index) + .scrollIntoView() + .trigger("mouseover", { force: true }) + .wait(500); + cy.get( + `${selector}:first-of-type .t--widget-propertypane-toggle > .t--widget-name`, + ) + .eq(index) + .scrollIntoView() + .click({ force: true }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); +}); + +Cypress.Commands.add("changeLayoutHeight", (locator) => { + cy.get(".t--property-control-height .remixicon-icon") + .scrollIntoView() + .click({ force: true }); + cy.get(locator).click({ force: true }); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); +}); + +Cypress.Commands.add("changeLayoutHeightWithoutWait", (locator) => { + cy.get(".t--property-control-height .remixicon-icon") + .scrollIntoView() + .click({ force: true }); + cy.get(locator).click({ force: true }); +}); + +Cypress.Commands.add("checkMinDefaultValue", (endp, value) => { + cy.get(".cm-m-null") + .first() + .invoke("text") + .then((text) => { + const someText = text; + cy.log(someText); + expect(someText).to.equal(value); + }); +}); + +Cypress.Commands.add("checkMaxDefaultValue", (endp, value) => { + cy.get(".cm-m-null") + .last() + .invoke("text") + .then((text) => { + const someText = text; + cy.log(someText); + expect(someText).to.equal(value); + }); +}); diff --git a/app/client/generators/widget/templates/index.js.hbs b/app/client/generators/widget/templates/index.js.hbs index 49d9092a0b..99ca3872db 100644 --- a/app/client/generators/widget/templates/index.js.hbs +++ b/app/client/generators/widget/templates/index.js.hbs @@ -7,6 +7,9 @@ export const CONFIG = { iconSVG: IconSVG, needsMeta: false, // Defines if this widget adds any meta properties isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets + features: { + dynamicHeight: false, + }, defaults: { widgetName: "{{name}}", rows: 1, diff --git a/app/client/package.json b/app/client/package.json index c082750844..44e5d4ad40 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -13,7 +13,6 @@ "@blueprintjs/icons": "^3.10.0", "@blueprintjs/popover2": "^0.5.0", "@blueprintjs/select": "^3.10.0", - "@craco/craco": "^7.0.0-alpha.3", "@draft-js-plugins/editor": "^4.1.0", "@draft-js-plugins/mention": "^4.5.1", "@fusioncharts/powercharts": "^3.16.0", @@ -37,7 +36,6 @@ "algoliasearch": "^4.2.0", "astring": "^1.7.5", "axios": "^0.27.2", - "caniuse-lite": "^1.0.30001208", "classnames": "^2.3.1", "codemirror": "^5.59.2", "codemirror-graphql": "^1.2.14", @@ -50,12 +48,10 @@ "design-system": "npm:@appsmithorg/design-system@1.0.32", "downloadjs": "^1.4.7", "draft-js": "^0.11.7", - "emoji-mart": "^3.0.1", "exceljs-lightweight": "^1.14.0", "fast-deep-equal": "^3.1.3", "fast-xml-parser": "^3.17.5", "fastdom": "^1.0.11", - "flow-bin": "^0.148.0", "focus-trap-react": "^8.9.2", "fuse.js": "^3.4.5", "fusioncharts": "^3.18.0", @@ -66,20 +62,17 @@ "husky": "^3.0.5", "immer": "^9.0.6", "instantsearch.css": "^7.4.2", - "instantsearch.js": "^4.4.1", "interweave": "^12.7.2", "interweave-autolink": "^4.4.2", "js-beautify": "^1.14.0", "js-sha256": "^0.9.0", "jshint": "^2.13.4", - "json-fn": "^1.1.1", "klona": "^2.0.5", "libphonenumber-js": "^1.9.44", "lint-staged": "^13.0.3", "localforage": "^1.7.3", "lodash": "^4.17.21", "lodash-es": "4.17.21", - "lodash-move": "^1.1.1", "loglevel": "^1.7.1", "lottie-web": "^5.7.4", "mammoth": "^1.4.19", @@ -102,7 +95,6 @@ "rc-tree-select": "^5.4.0", "re-reselect": "^3.4.0", "react": "^16.12.0", - "react-base-table": "^1.9.1", "react-beautiful-dnd": "^12.2.0", "react-custom-scrollbars": "^4.2.1", "react-device-detect": "^2.2.2", @@ -117,12 +109,10 @@ "react-google-recaptcha": "^2.1.0", "react-helmet": "^5.2.1", "react-hook-form": "^7.28.0", - "react-infinite-scroller": "^1.2.4", "react-instantsearch-dom": "^6.4.0", "react-json-view": "^1.21.3", "react-masonry-css": "^1.0.16", "react-media-recorder": "^1.6.1", - "react-mentions": "^4.1.1", "react-modal": "^3.15.1", "react-page-visibility": "^7.0.0", "react-paginating": "^1.4.0", @@ -141,7 +131,6 @@ "react-tabs": "^3.0.0", "react-timer-hook": "^3.0.4", "react-toastify": "^5.5.0", - "react-transition-group": "^4.3.0", "react-use-gesture": "^7.0.4", "react-virtuoso": "^1.9.0", "react-webcam": "^7.0.1", @@ -198,6 +187,7 @@ "devDependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-string-parser": "^7.19.4", + "@craco/craco": "^7.0.0", "@faker-js/faker": "^7.4.0", "@sentry/webpack-plugin": "^1.18.9", "@testing-library/jest-dom": "5.16.1", diff --git a/app/client/src/RouteBuilder.ts b/app/client/src/RouteBuilder.ts index 0c7a9b4a54..f13fad24c4 100644 --- a/app/client/src/RouteBuilder.ts +++ b/app/client/src/RouteBuilder.ts @@ -17,6 +17,7 @@ export type URLBuilderParams = { hash?: string; params?: Record; pageId: string; + persistExistingParams?: boolean; }; export const fillPathname = ( diff --git a/app/client/src/actions/apiPaneActions.ts b/app/client/src/actions/apiPaneActions.ts index 78fa168edb..d9dc492845 100644 --- a/app/client/src/actions/apiPaneActions.ts +++ b/app/client/src/actions/apiPaneActions.ts @@ -116,3 +116,10 @@ export const setApiPaneResponsePaneHeight: ( type: ReduxActionTypes.SET_API_PANE_RESPONSE_PANE_HEIGHT, payload: { height: payload }, }); + +export const setApiRightPaneSelectedTab: ( + payload: number, +) => ReduxAction<{ selectedTab: number }> = (payload: number) => ({ + type: ReduxActionTypes.SET_API_RIGHT_PANE_SELECTED_TAB, + payload: { selectedTab: payload }, +}); diff --git a/app/client/src/actions/autoHeightActions.ts b/app/client/src/actions/autoHeightActions.ts index 50cdca16ed..72247d1180 100644 --- a/app/client/src/actions/autoHeightActions.ts +++ b/app/client/src/actions/autoHeightActions.ts @@ -20,13 +20,13 @@ export function setAutoHeightLayoutTreeAction( } export function generateAutoHeightLayoutTreeAction( - shouldCheckContainersForDynamicHeightUpdates: boolean, + shouldCheckContainersForAutoHeightUpdates: boolean, layoutUpdated?: boolean, ) { return { type: ReduxActionTypes.GENERATE_AUTO_HEIGHT_LAYOUT_TREE, payload: { - shouldCheckContainersForDynamicHeightUpdates, + shouldCheckContainersForAutoHeightUpdates, layoutUpdated: !!layoutUpdated, }, }; diff --git a/app/client/src/actions/controlActions.tsx b/app/client/src/actions/controlActions.tsx index 1962f772e7..7bc6442f54 100644 --- a/app/client/src/actions/controlActions.tsx +++ b/app/client/src/actions/controlActions.tsx @@ -1,6 +1,7 @@ import { ReduxActionTypes, ReduxAction, + ReduxActionType, } from "@appsmith/constants/ReduxActionConstants"; import { UpdateWidgetsPayload } from "reducers/entityReducers/canvasWidgetsReducer"; import { DynamicPath } from "utils/DynamicBindingUtils"; @@ -24,6 +25,7 @@ export interface BatchPropertyUpdatePayload { modify?: Record; //Key value pairs of paths and values to update remove?: string[]; //Array of paths to delete triggerPaths?: string[]; // Array of paths in the modify and remove list which are trigger paths + postUpdateAction?: ReduxActionType; // Array of action types we need to dispatch after propert updates. } export const batchUpdateWidgetProperty = ( diff --git a/app/client/src/actions/editorActions.ts b/app/client/src/actions/editorActions.ts index 4021eb6f3a..990a1acae5 100644 --- a/app/client/src/actions/editorActions.ts +++ b/app/client/src/actions/editorActions.ts @@ -36,17 +36,12 @@ export const deleteCanvasCardsState = () => ({ * action that update canvas layout * * @param width - * @param height * @returns */ -export const updateCanvasLayoutAction = ( - width: number, - height: number | undefined, -) => { +export const updateCanvasLayoutAction = (width: number) => { return { type: ReduxActionTypes.UPDATE_CANVAS_LAYOUT, payload: { - height, width, }, }; diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index a6afeee3e9..0f4b2d9afa 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -9,6 +9,7 @@ import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; import { WidgetProps } from "widgets/BaseWidget"; +import { UpdateWidgetsPayload } from "reducers/entityReducers/canvasWidgetsReducer"; export const executeTrigger = ( payload: ExecuteTriggerPayload, @@ -154,3 +155,12 @@ export const groupWidgets = () => { type: ReduxActionTypes.GROUP_WIDGETS_INIT, }; }; + +export const updateMultipleWidgetProperties = ( + widgetsToUpdate: UpdateWidgetsPayload, +) => { + return { + type: ReduxActionTypes.UPDATE_MULTIPLE_WIDGET_PROPERTIES, + payload: widgetsToUpdate, + }; +}; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 6d66f730d8..cd3d106deb 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -688,6 +688,7 @@ export const ReduxActionTypes = { SET_API_PANE_CONFIG_SELECTED_TAB: "SET_API_PANE_CONFIG_SELECTED_TAB", SET_API_PANE_RESPONSE_SELECTED_TAB: "SET_API_PANE_RESPONSE_SELECTED_TAB", SET_API_PANE_RESPONSE_PANE_HEIGHT: "SET_API_PANE_RESPONSE_PANE_HEIGHT", + SET_API_RIGHT_PANE_SELECTED_TAB: "SET_API_RIGHT_PANE_SELECTED_TAB", GENERATE_KEY_AND_SET_CODE_EDITOR_LAST_FOCUS: "GENERATE_KEY_AND_SET_CODE_EDITOR_LAST_FOCUS", SET_CODE_EDITOR_CURSOR_HISTORY: "SET_CODE_EDITOR_CURSOR_HISTORY", @@ -712,6 +713,7 @@ export const ReduxActionTypes = { CHECK_CONTAINERS_FOR_AUTO_HEIGHT: "CHECK_CONTAINERS_FOR_AUTO_HEIGHT", UPDATE_WIDGET_AUTO_HEIGHT: "UPDATE_WIDGET_AUTO_HEIGHT", SET_LINT_ERRORS: "SET_LINT_ERRORS", + SET_AUTO_HEIGHT_WITH_LIMITS_CHANGING: "SET_AUTO_HEIGHT_WITH_LIMITS_CHANGING", PROCESS_AUTO_HEIGHT_UPDATES: "PROCESS_AUTO_HEIGHT_UPDATES", }; diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 4af02e116e..e62b23b9e3 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -28,7 +28,7 @@ export const ERROR_EMPTY_APPLICATION_NAME = () => export const API_PATH_START_WITH_SLASH_ERROR = () => `Path cannot start with /`; export const FIELD_REQUIRED_ERROR = () => `This field is required`; export const INPUT_DEFAULT_TEXT_MAX_CHAR_ERROR = (max: number) => - `Default text length must be less than ${max} characters`; + `Default text length must be less than or equal to ${max} characters`; export const INPUT_TEXT_MAX_CHAR_ERROR = (max: number) => `Input text length must be less than ${max} characters`; export const INPUT_DEFAULT_TEXT_MAX_NUM_ERROR = () => diff --git a/app/client/src/ce/pages/AdminSettings/config/general.tsx b/app/client/src/ce/pages/AdminSettings/config/general.tsx index ef7d2e1f8c..58e5762d24 100644 --- a/app/client/src/ce/pages/AdminSettings/config/general.tsx +++ b/app/client/src/ce/pages/AdminSettings/config/general.tsx @@ -10,6 +10,10 @@ import { Setting, } from "@appsmith/pages/AdminSettings/config/types"; import BrandingBadge from "pages/AppViewer/BrandingBadge"; +import { TagInput } from "design-system"; +import QuestionFillIcon from "remixicon-react/QuestionFillIcon"; +import localStorage from "utils/localStorage"; +import isUndefined from "lodash/isUndefined"; export const APPSMITH_INSTANCE_NAME_SETTING_SETTING: Setting = { id: "APPSMITH_INSTANCE_NAME", @@ -81,6 +85,83 @@ export const APPSMITH_HIDE_WATERMARK_SETTING: Setting = { "Hello, I would like to upgrade and remove the watermark.", }; +export const APPSMITH_ALLOWED_FRAME_ANCESTORS_SETTING: Setting = { + id: "APPSMITH_ALLOWED_FRAME_ANCESTORS", + name: "APPSMITH_ALLOWED_FRAME_ANCESTORS", + category: SettingCategories.GENERAL, + controlType: SettingTypes.RADIO, + label: "Embed Settings", + controlTypeProps: { + options: [ + { + badge: "NOT RECOMMENDED", + tooltip: { + icon: , + text: + "Lets all domains, including malicious ones, embed your Appsmith apps. ", + linkText: "SEE WHY THIS IS RISKY", + link: + "https://docs.appsmith.com/getting-started/setup/instance-configuration/frame-ancestors#why-should-i-control-this", + }, + label: "Allow embedding everywhere", + value: "ALLOW_EMBEDDING_EVERYWHERE", + }, + { + label: "Limit embedding to certain URLs", + value: "LIMIT_EMBEDDING", + nodeLabel: "You can add one or more URLs", + node: , + nodeInputPath: "input", + nodeParentClass: "tag-input", + }, + { + label: "Disable embedding everywhere", + value: "DISABLE_EMBEDDING_EVERYWHERE", + }, + ], + }, + format: (value: string) => { + if (value === "*") { + return { + value: "ALLOW_EMBEDDING_EVERYWHERE", + }; + } else if (value === "'none'") { + return { + value: "DISABLE_EMBEDDING_EVERYWHERE", + }; + } else { + return { + value: "LIMIT_EMBEDDING", + additionalData: value ? value.replaceAll(" ", ",") : "", + }; + } + }, + parse: (value: { value: string; additionalData?: any }) => { + // Retrieve values from local storage while switching to limit by url option + const sources = isUndefined(value.additionalData) + ? localStorage.getItem("ALLOWED_FRAME_ANCESTORS") ?? "" + : value.additionalData.replaceAll(",", " "); + // If they are one of the other options we don't store it in storage since it will + // set in the env variable on save + if (sources !== "*" && sources !== "'none'") { + localStorage.setItem("ALLOWED_FRAME_ANCESTORS", sources); + } + + if (value.value === "ALLOW_EMBEDDING_EVERYWHERE") { + return "*"; + } else if (value.value === "DISABLE_EMBEDDING_EVERYWHERE") { + return "'none'"; + } else { + return sources; + } + }, + validate: (value: string) => { + if (!value) { + return "This field cannot be empty"; + } + }, +}; + export const config: AdminConfigType = { icon: "settings-2-line", type: SettingCategories.GENERAL, @@ -93,5 +174,6 @@ export const config: AdminConfigType = { APPSMITH_DOWNLOAD_DOCKER_COMPOSE_FILE_SETTING, APPSMITH_DISABLE_TELEMETRY_SETTING, APPSMITH_HIDE_WATERMARK_SETTING, + APPSMITH_ALLOWED_FRAME_ANCESTORS_SETTING, ], } as AdminConfigType; diff --git a/app/client/src/ce/pages/AdminSettings/config/types.ts b/app/client/src/ce/pages/AdminSettings/config/types.ts index f144a2c1a1..49bb965fa8 100644 --- a/app/client/src/ce/pages/AdminSettings/config/types.ts +++ b/app/client/src/ce/pages/AdminSettings/config/types.ts @@ -2,8 +2,32 @@ import React from "react"; import { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; import { Dispatch } from "react"; import { EventName } from "utils/AnalyticsUtil"; +import { RadioProps } from "pages/Settings/FormGroup/Radio"; + +type ControlType = { + [K in keyof ControlPropsType]: { + controlType: K; + controlTypeProps?: ControlPropsType[K]; + }; +}[keyof ControlPropsType]; + +type ControlPropsType = { + [SettingTypes.RADIO]: RadioProps; + [SettingTypes.TEXTINPUT]: unknown; + [SettingTypes.TOGGLE]: unknown; + [SettingTypes.LINK]: unknown; + [SettingTypes.BUTTON]: unknown; + [SettingTypes.GROUP]: unknown; + [SettingTypes.TEXT]: unknown; + [SettingTypes.UNEDITABLEFIELD]: unknown; + [SettingTypes.ACCORDION]: unknown; + [SettingTypes.TAGINPUT]: unknown; + [SettingTypes.DROPDOWN]: unknown; + [SettingTypes.CHECKBOX]: unknown; +}; export enum SettingTypes { + RADIO = "RADIO", TEXTINPUT = "TEXTINPUT", TOGGLE = "TOGGLE", LINK = "LINK", @@ -25,11 +49,12 @@ export enum SettingSubtype { PASSWORD = "password", } -export interface Setting { +export type Setting = ControlType & { id: string; category?: string; - controlType: SettingTypes; controlSubType?: SettingSubtype; + format?: (value: string) => any; + parse?: (value: any) => any; helpText?: string; label?: string; name?: string; @@ -60,7 +85,7 @@ export interface Setting { needsUpgrade?: boolean; upgradeLogEventName?: EventName; upgradeIntercomMessage?: string; -} +}; export interface Category { title: string; diff --git a/app/client/src/ce/reducers/index.tsx b/app/client/src/ce/reducers/index.tsx index d8f1c5d215..70f830dafc 100644 --- a/app/client/src/ce/reducers/index.tsx +++ b/app/client/src/ce/reducers/index.tsx @@ -70,6 +70,7 @@ import { AutoHeightLayoutTreeReduxState } from "reducers/entityReducers/autoHeig import { CanvasLevelsReduxState } from "reducers/entityReducers/autoHeightReducers/canvasLevelsReducer"; import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers"; import lintErrorReducer from "reducers/lintingReducers"; +import { AutoHeightUIState } from "reducers/uiReducers/autoHeightReducer"; export const reducerObject = { entities: entityReducer, @@ -126,6 +127,7 @@ export interface AppState { mainCanvas: MainCanvasReduxState; focusHistory: FocusHistoryState; editorContext: EditorContextState; + autoHeightUI: AutoHeightUIState; }; entities: { canvasWidgetsStructure: CanvasWidgetStructure; diff --git a/app/client/src/ce/sagas/index.tsx b/app/client/src/ce/sagas/index.tsx index 110c4e5ec7..fbba9dda76 100644 --- a/app/client/src/ce/sagas/index.tsx +++ b/app/client/src/ce/sagas/index.tsx @@ -41,6 +41,7 @@ import SuperUserSagas from "@appsmith/sagas/SuperUserSagas"; import NavigationSagas from "sagas/NavigationSagas"; import editorContextSagas from "sagas/editorContextSagas"; import PageVisibilitySaga from "sagas/PageVisibilitySagas"; +import AutoHeightSagas from "sagas/autoHeightSagas"; import tenantSagas from "@appsmith/sagas/tenantSagas"; export const sagas = [ @@ -87,5 +88,6 @@ export const sagas = [ NavigationSagas, editorContextSagas, PageVisibilitySaga, + AutoHeightSagas, tenantSagas, ]; diff --git a/app/client/src/components/autoHeight/AutoHeightContainer.test.tsx b/app/client/src/components/autoHeight/AutoHeightContainer.test.tsx index 466c4fb123..a7e5094224 100644 --- a/app/client/src/components/autoHeight/AutoHeightContainer.test.tsx +++ b/app/client/src/components/autoHeight/AutoHeightContainer.test.tsx @@ -16,15 +16,16 @@ describe("", () => { maxDynamicHeight={0} minDynamicHeight={0} onHeightUpdate={onHeightUpdate} + widgetHeightInPixels={200} >
, ) .toJSON(); - expect(tree).toHaveStyleRule("height", "auto"); + expect(tree).toHaveStyleRule("height", "auto !important"); }); - describe("when isAutoHeightWithLimits is false", () => { + describe("when isAutoHeightWithLimits is false.", () => { it("should wrap the children in a simple div with class auto-height-container", async () => { const getTestComponent = () => ( ", () => { maxDynamicHeight={0} minDynamicHeight={0} onHeightUpdate={onHeightUpdate} + widgetHeightInPixels={200} >
@@ -53,6 +55,7 @@ describe("", () => { maxDynamicHeight={0} minDynamicHeight={0} onHeightUpdate={onHeightUpdate} + widgetHeightInPixels={200} >
diff --git a/app/client/src/components/autoHeight/AutoHeightContainer.tsx b/app/client/src/components/autoHeight/AutoHeightContainer.tsx index 30b91d1af5..a9be60af32 100644 --- a/app/client/src/components/autoHeight/AutoHeightContainer.tsx +++ b/app/client/src/components/autoHeight/AutoHeightContainer.tsx @@ -1,10 +1,13 @@ -import React, { PropsWithChildren, useRef, useEffect, useState } from "react"; -import { GridDefaults } from "constants/WidgetConstants"; +import React, { PropsWithChildren, useEffect, useRef, useState } from "react"; +import { GridDefaults, WIDGET_PADDING } from "constants/WidgetConstants"; import styled from "styled-components"; +import { WidgetProps } from "widgets/BaseWidget"; const StyledAutoHeightContainer = styled.div<{ isOverflow?: boolean }>` overflow-y: ${(props) => (props.isOverflow ? "auto" : "unset")}; overflow-x: ${(props) => (props.isOverflow ? "hidden" : "unset")}; + padding-right: 4px; + height: 100%; `; interface AutoHeightContainerProps { @@ -12,10 +15,12 @@ interface AutoHeightContainerProps { minDynamicHeight: number; isAutoHeightWithLimits: boolean; onHeightUpdate: (height: number) => void; + widgetHeightInPixels: number; + widgetProps?: WidgetProps; } const SimpleContainer = styled.div` - height: auto; + height: auto !important; `; export default function AutoHeightContainer({ @@ -24,12 +29,14 @@ export default function AutoHeightContainer({ maxDynamicHeight, minDynamicHeight, onHeightUpdate, + widgetHeightInPixels, + widgetProps, }: PropsWithChildren) { const [expectedHeight, setExpectedHeight] = useState(0); const ref = useRef(null); - const observer = useRef( + const observer = React.useRef( new ResizeObserver((entries) => { const height = entries[0].contentRect.height; setExpectedHeight(height); @@ -53,15 +60,34 @@ export default function AutoHeightContainer({ onHeightUpdate(expectedHeight); }, [minDynamicHeight, maxDynamicHeight]); + useEffect(() => { + if ( + widgetHeightInPixels !== + Math.ceil( + Math.ceil(expectedHeight + WIDGET_PADDING * 2) / + GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + ) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT + ) { + onHeightUpdate(expectedHeight); + } + }, [widgetHeightInPixels]); + if (isAutoHeightWithLimits) { const expectedHeightInRows = Math.ceil( expectedHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT, ); + const backgroundColor = + widgetProps?.type === "TEXT_WIDGET" + ? widgetProps?.backgroundColor + : undefined; + return ( {children} diff --git a/app/client/src/components/autoHeight/AutoHeightContainerWrapper.tsx b/app/client/src/components/autoHeight/AutoHeightContainerWrapper.tsx new file mode 100644 index 0000000000..b0a228e88d --- /dev/null +++ b/app/client/src/components/autoHeight/AutoHeightContainerWrapper.tsx @@ -0,0 +1,51 @@ +import { GridDefaults } from "constants/WidgetConstants"; +import React, { ReactNode } from "react"; +import useWidgetConfig from "utils/hooks/useWidgetConfig"; +import { DynamicHeight } from "utils/WidgetFeatures"; +import { WidgetProps } from "widgets/BaseWidget"; +import { + getWidgetMaxAutoHeight, + getWidgetMinAutoHeight, +} from "widgets/WidgetUtils"; +import AutoHeightContainer from "./AutoHeightContainer"; + +export type AutoHeightWrapperProps = { + widgetProps: WidgetProps; + children: ReactNode; + onUpdateDynamicHeight: (height: number) => void; +}; + +function AutoHeightContainerWrapper(props: AutoHeightWrapperProps) { + const { children, widgetProps } = props; + const isCanvas = useWidgetConfig(widgetProps.type, "isCanvas"); + // eslint-disable-next-line react/jsx-no-useless-fragment + if (isCanvas) return <>{children}; + + const onHeightUpdate = (height: number) => { + props.onUpdateDynamicHeight(height); + }; + + const maxDynamicHeight = getWidgetMaxAutoHeight(widgetProps); + const minDynamicHeight = getWidgetMinAutoHeight(widgetProps); + + const widgetHeightInPixels = + (widgetProps.bottomRow - widgetProps.topRow) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + const isAutoHeightWithLimits = + widgetProps.dynamicHeight === DynamicHeight.AUTO_HEIGHT_WITH_LIMITS; + + return ( + + {children} + + ); +} + +export default AutoHeightContainerWrapper; diff --git a/app/client/src/components/autoHeightOverlay/AutoHeightLimitHandleGroup.tsx b/app/client/src/components/autoHeightOverlay/AutoHeightLimitHandleGroup.tsx new file mode 100644 index 0000000000..91c5104329 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/AutoHeightLimitHandleGroup.tsx @@ -0,0 +1,164 @@ +import React, { useRef } from "react"; +import styled from "styled-components"; +import AutoHeightLimitHandleBorder from "./ui/AutoHeightLimitHandleBorder"; +import { useDrag } from "react-use-gesture"; +import { heightToRows } from "./utils"; +import AutoHeightLimitHandleLabel from "./ui/AutoHeightLimitHandleLabel"; +import { onDragCallbacksProps, onMouseHoverCallbacksProps } from "./types"; +import AutoHeightLimitHandleDot from "./ui/AutoHeightLimitHandleDot"; + +const AutoHeightLimitHandleGroupContainer = styled.div` + position: absolute; + left: 50%; + transform: translateX(-50%); + pointer-events: all; + width: 100%; + z-index: 1; +`; + +interface AutoHeightLimitHandleGroupProps { + isMaxDotActive: boolean; + isMinDotActive: boolean; + isMaxDotDragging: boolean; + isMinDotDragging: boolean; + maxY: number; + minY: number; + onMaxLimitDragCallbacks: onDragCallbacksProps; + onMinLimitDragCallbacks: onDragCallbacksProps; + onMaxHeightSet: (height: number) => void; + onMinHeightSet: (height: number) => void; + onMaxLimitMouseHoverCallbacks: onMouseHoverCallbacksProps; + onMinLimitMouseHoverCallbacks: onMouseHoverCallbacksProps; +} + +interface AutoHeightLimitHandleContainerProps { + height: number; +} + +const AutoHeightLimitHandleContainer = styled.div< + AutoHeightLimitHandleContainerProps +>` + position: absolute; + display: flex; + align-items: center; + width: 100%; + height: 13px; + transform: translateY(${(props) => props.height - 6}px); + cursor: ns-resize; + display: flex; + align-items: center; +`; + +interface AutoHeightLimitHandleProps { + cypressDataID: string; + height: number; + isActive: boolean; + isColliding: boolean; + isDragging: boolean; + label: string; + onDragCallbacks: onDragCallbacksProps; + onMouseHoverFunctions: onMouseHoverCallbacksProps; +} + +const AutoHeightLimitHandle = ({ + cypressDataID, + height, + isActive, + isColliding, + isDragging, + label, + onDragCallbacks, + onMouseHoverFunctions, +}: AutoHeightLimitHandleProps) => { + const ref = useRef(null); + const { onStart, onStop, onUpdate } = onDragCallbacks; + + const bind = useDrag((state) => { + if (state.first) { + onStart(); + return; + } + + if (state.last) { + onStop(); + return; + } + const [mx, my] = state.movement; + + onUpdate(mx, my); + }); + + const bindings = bind(); + + return ( + { + e.stopPropagation(); + }} + onDragStart={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + onMouseDown={(e) => { + e.preventDefault(); + e.stopPropagation(); + bindings?.onMouseDown && bindings.onMouseDown(e); + }} + {...onMouseHoverFunctions} + > + + + {!isColliding ? ( + + {label}: {heightToRows(height)} rows + + ) : null} + + ); +}; + +const AutoHeightLimitHandleGroup: React.FC = ({ + isMaxDotActive, + isMaxDotDragging, + isMinDotActive, + isMinDotDragging, + maxY, + minY, + onMaxLimitDragCallbacks, + onMaxLimitMouseHoverCallbacks, + onMinLimitDragCallbacks, + onMinLimitMouseHoverCallbacks, +}) => { + const isColliding = maxY === minY; + + return ( + + + + + ); +}; + +export default AutoHeightLimitHandleGroup; diff --git a/app/client/src/components/autoHeightOverlay/constants.ts b/app/client/src/components/autoHeightOverlay/constants.ts new file mode 100644 index 0000000000..67144554ff --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/constants.ts @@ -0,0 +1,5 @@ +export const OVERLAY_COLOR = "#F32B8B"; + +// During dragging we have to scale the size +// of the dot to increase its focus +export const OVERLAY_HANDLE_DOT_DRAGGING_SCALE = "1.67"; diff --git a/app/client/src/components/autoHeightOverlay/hooks.ts b/app/client/src/components/autoHeightOverlay/hooks.ts new file mode 100644 index 0000000000..6243f6d811 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/hooks.ts @@ -0,0 +1,144 @@ +import { CONTAINER_GRID_PADDING } from "constants/WidgetConstants"; +import { CSSProperties, useEffect, useMemo, useState } from "react"; +import { CallbackHandlerEventType } from "utils/CallbackHandler/CallbackHandlerEventType"; +import DynamicHeightCallbackHandler from "utils/CallbackHandler/DynamicHeightCallbackHandler"; +import { onMouseHoverCallbacksProps } from "./types"; + +type UseHoverStateReturnType = [boolean, onMouseHoverCallbacksProps]; + +export function useHoverState(): UseHoverStateReturnType { + const [isActive, setIsActive] = useState(false); + + function handleMouseEnter(state: boolean) { + setIsActive(state); + } + + return [ + isActive, + { + onMouseEnter: () => handleMouseEnter(true), + onMouseLeave: () => handleMouseEnter(false), + }, + ]; +} + +interface UsePositionedStylesProps { + bottomRow: number; + leftColumn: number; + noContainerOffset?: boolean; + parentColumnSpace: number; + parentRowSpace: number; + rightColumn: number; + topRow: number; +} + +export const usePositionedStyles = ({ + bottomRow, + leftColumn, + noContainerOffset, + parentColumnSpace, + parentRowSpace, + rightColumn, + topRow, +}: UsePositionedStylesProps) => { + const styles: CSSProperties = useMemo( + () => ({ + height: (bottomRow - topRow) * parentRowSpace, + width: (rightColumn - leftColumn) * parentColumnSpace, + left: + leftColumn * parentColumnSpace + + (noContainerOffset ? 0 : CONTAINER_GRID_PADDING), + top: + topRow * parentRowSpace + + (noContainerOffset ? 0 : CONTAINER_GRID_PADDING), + }), + [ + bottomRow, + leftColumn, + noContainerOffset, + parentColumnSpace, + parentRowSpace, + rightColumn, + topRow, + ], + ); + + return styles; +}; + +export const useMaxMinPropertyPaneFieldsFocused = () => { + const [ + isPropertyPaneMinFieldFocused, + setPropertyPaneMinFieldFocused, + ] = useState(false); + + const [ + isPropertyPaneMaxFieldFocused, + setPropertyPaneMaxFieldFocused, + ] = useState(false); + + function handleOnMaxLimitPropertyPaneFieldFocus() { + setPropertyPaneMaxFieldFocused(true); + } + + function handleOnMaxLimitPropertyPaneFieldBlur() { + setPropertyPaneMaxFieldFocused(false); + } + + function handleOnMinLimitPropertyPaneFieldFocus() { + setPropertyPaneMinFieldFocused(true); + } + + function handleOnMinLimitPropertyPaneFieldBlur() { + setPropertyPaneMinFieldFocused(false); + } + + useEffect(() => { + DynamicHeightCallbackHandler.add( + CallbackHandlerEventType.MAX_HEIGHT_LIMIT_FOCUS, + handleOnMaxLimitPropertyPaneFieldFocus, + ); + + DynamicHeightCallbackHandler.add( + CallbackHandlerEventType.MAX_HEIGHT_LIMIT_BLUR, + handleOnMaxLimitPropertyPaneFieldBlur, + ); + + DynamicHeightCallbackHandler.add( + CallbackHandlerEventType.MIN_HEIGHT_LIMIT_FOCUS, + handleOnMinLimitPropertyPaneFieldFocus, + ); + + DynamicHeightCallbackHandler.add( + CallbackHandlerEventType.MIN_HEIGHT_LIMIT_BLUR, + handleOnMinLimitPropertyPaneFieldBlur, + ); + + return () => { + DynamicHeightCallbackHandler.remove( + CallbackHandlerEventType.MAX_HEIGHT_LIMIT_FOCUS, + handleOnMaxLimitPropertyPaneFieldFocus, + ); + + DynamicHeightCallbackHandler.remove( + CallbackHandlerEventType.MAX_HEIGHT_LIMIT_BLUR, + handleOnMaxLimitPropertyPaneFieldBlur, + ); + + DynamicHeightCallbackHandler.remove( + CallbackHandlerEventType.MIN_HEIGHT_LIMIT_FOCUS, + handleOnMinLimitPropertyPaneFieldFocus, + ); + + DynamicHeightCallbackHandler.remove( + CallbackHandlerEventType.MIN_HEIGHT_LIMIT_BLUR, + handleOnMinLimitPropertyPaneFieldBlur, + ); + }; + }, []); + + return { + isPropertyPaneMaxFieldFocused, + isPropertyPaneMinFieldFocused, + }; +}; diff --git a/app/client/src/components/autoHeightOverlay/index.tsx b/app/client/src/components/autoHeightOverlay/index.tsx new file mode 100644 index 0000000000..f5519b1384 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/index.tsx @@ -0,0 +1,396 @@ +import { focusWidget } from "actions/widgetActions"; +import React, { + CSSProperties, + memo, + useEffect, + useMemo, + useReducer, +} from "react"; +import { useSelector } from "react-redux"; +import { AppState } from "@appsmith/reducers"; +import styled from "styled-components"; +import { + useShowPropertyPane, + useShowTableFilterPane, +} from "utils/hooks/dragResizeHooks"; +import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; +import { WidgetProps } from "widgets/BaseWidget"; +import { GridDefaults, WidgetHeightLimits } from "constants/WidgetConstants"; +import { getParentToOpenSelector } from "selectors/widgetSelectors"; +import AutoHeightLimitHandleGroup from "./AutoHeightLimitHandleGroup"; +import AutoHeightLimitOverlayDisplay from "./ui/AutoHeightLimitOverlayDisplay"; +import { useHoverState, usePositionedStyles } from "./hooks"; +import { getSnappedValues } from "./utils"; +import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks"; +import { LayersContext } from "constants/Layers"; +import { + AutoHeightOverlayUIStateReducer, + createInitialAutoHeightUIState, +} from "./store"; +import { previewModeSelector } from "selectors/editorSelectors"; + +interface StyledAutoHeightOverlayProps { + layerIndex: number; + isHidden: boolean; +} + +const StyledAutoHeightOverlay = styled.div` + width: 100%; + height: 100%; + position: absolute; + z-index: ${(props) => props.layerIndex}; + pointer-events: none; + display: ${(props) => (props.isHidden ? "none" : "block")}; +`; + +interface MinMaxHeightProps { + maxDynamicHeight: number; + minDynamicHeight: number; +} + +interface AutoHeightOverlayContainerProps + extends MinMaxHeightProps, + WidgetProps { + batchUpdate: (height: number) => void; + onMaxHeightSet: (height: number) => void; + onMinHeightSet: (height: number) => void; + style?: CSSProperties; +} + +interface AutoHeightOverlayProps extends AutoHeightOverlayContainerProps { + isHidden: boolean; +} + +const AutoHeightOverlay: React.FC = memo( + ({ + batchUpdate, + isHidden, + maxDynamicHeight, + minDynamicHeight, + onMaxHeightSet, + onMinHeightSet, + style, + ...props + }) => { + const showPropertyPane = useShowPropertyPane(); + const { selectWidget } = useWidgetSelection(); + const selectedWidget = useSelector( + (state: AppState) => state.ui.widgetDragResize.lastSelectedWidget, + ); + + const parentWidgetToSelect = useSelector( + getParentToOpenSelector(props.widgetId), + ); + const showTableFilterPane = useShowTableFilterPane(); + const { + isAutoHeightWithLimitsChanging, + setIsAutoHeightWithLimitsChanging, + } = useAutoHeightUIState(); + + const [autoHeightUIState, autoHeightUIStateDispatch] = useReducer( + AutoHeightOverlayUIStateReducer, + createInitialAutoHeightUIState({ maxDynamicHeight, minDynamicHeight }), + ); + + const { + isMaxDotDragging, + isMinDotDragging, + maxdY, + maxY, + mindY, + minY, + } = autoHeightUIState; + + function setIsMaxDotDragging(isMaxDotDragging: boolean) { + autoHeightUIStateDispatch({ + type: "SET_IS_MAX_DOT_DRAGGING", + payload: { + isMaxDotDragging, + }, + }); + } + + function setIsMinDotDragging(isMinDotDragging: boolean) { + autoHeightUIStateDispatch({ + type: "SET_IS_MIN_DOT_DRAGGING", + payload: { + isMinDotDragging, + }, + }); + } + + function setMaxY(maxY: number) { + autoHeightUIStateDispatch({ + type: "SET_MAX_Y", + payload: { + maxY, + }, + }); + } + + function setMinY(minY: number) { + autoHeightUIStateDispatch({ + type: "SET_MIN_Y", + payload: { + minY, + }, + }); + } + + function setMaxdY(maxdY: number) { + autoHeightUIStateDispatch({ + type: "SET_MAX_D_Y", + payload: { + maxdY, + }, + }); + } + + function setMindY(mindY: number) { + autoHeightUIStateDispatch({ + type: "SET_MIN_D_Y", + payload: { + mindY, + }, + }); + } + + const finalMaxY = maxY + maxdY; + const finalMinY = minY + mindY; + + useEffect(() => { + setMaxY(maxDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT); + }, [maxDynamicHeight]); + + function onAnyDotStop() { + // Tell the Canvas that we've stopped resizing + // Put it later in the stack so that other updates like click, are not propagated to the parent container + setTimeout(() => { + setIsAutoHeightWithLimitsChanging && + setIsAutoHeightWithLimitsChanging(false); + }, 0); + + selectWidget && selectWidget(props.widgetId); + + if (parentWidgetToSelect) { + selectWidget && + selectedWidget !== parentWidgetToSelect.widgetId && + selectWidget(parentWidgetToSelect.widgetId); + focusWidget(parentWidgetToSelect.widgetId); + } else { + selectWidget && + selectedWidget !== props.widgetId && + selectWidget(props.widgetId); + } + // Property pane closes after a resize/drag + showPropertyPane && showPropertyPane(); + } + + function onMaxUpdate(dx: number, dy: number) { + if ( + maxY + dy <= + WidgetHeightLimits.MIN_HEIGHT_IN_ROWS * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT + ) { + return; + } + + const snapped = getSnappedValues(dx, dy, snapGrid); + + if (maxY + snapped.y <= minY) { + setMindY(snapped.y + (maxY - minY)); + } + + setMaxdY(snapped.y); + } + + function updateMaxHeight(height: number) { + setMaxY(height); + onMaxHeightSet(height); + } + + function updateMinHeight(height: number) { + setMinY(height); + onMinHeightSet(height); + } + + function onMaxStop() { + setIsMaxDotDragging(false); + const heightToSet = maxY + maxdY; + + if (heightToSet === minY + mindY) { + batchUpdate(heightToSet); + setMindY(0); + setMaxdY(0); + } else { + updateMaxHeight(heightToSet); + setMaxdY(0); + } + + onAnyDotStop(); + } + + useEffect(() => { + setMinY(minDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT); + }, [minDynamicHeight]); + + function onMinUpdate(dx: number, dy: number) { + if ( + minY + dy <= + WidgetHeightLimits.MIN_HEIGHT_IN_ROWS * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT + ) { + return; + } + + const snapped = getSnappedValues(dx, dy, snapGrid); + + if (minY + snapped.y >= maxY) { + setMaxdY(snapped.y - (maxY - minY)); + } + + setMindY(snapped.y); + } + + function onMinStop() { + setIsMinDotDragging(false); + const heightToSet = minY + mindY; + + if (heightToSet === maxY + maxdY) { + batchUpdate(heightToSet); + setMindY(0); + setMaxdY(0); + } else { + updateMinHeight(heightToSet); + setMindY(0); + } + + onAnyDotStop(); + } + + function onMinDotStart() { + setIsMinDotDragging(true); + onAnyDotStart(); + } + + function onAnyDotStart() { + setIsAutoHeightWithLimitsChanging && + !isAutoHeightWithLimitsChanging && + setIsAutoHeightWithLimitsChanging(true); + selectWidget && + selectedWidget !== props.widgetId && + selectWidget(props.widgetId); + // Make sure that this tableFilterPane should close + showTableFilterPane && showTableFilterPane(); + } + + function onMaxDotStart() { + setIsMaxDotDragging(true); + onAnyDotStart(); + } + + const [isMinDotActive, minHoverFns] = useHoverState(); + const [isMaxDotActive, maxHoverFns] = useHoverState(); + + const snapGrid = useMemo( + () => ({ + x: props.parentColumnSpace, + y: props.parentRowSpace, + }), + [props.parentColumnSpace, props.parentRowSpace], + ); + + const { + bottomRow, + leftColumn, + noContainerOffset, + parentColumnSpace, + parentRowSpace, + rightColumn, + topRow, + } = props; + + const styles = usePositionedStyles({ + bottomRow, + leftColumn, + noContainerOffset, + parentColumnSpace, + parentRowSpace, + rightColumn, + topRow, + }); + + const { autoHeightWithLimitsOverlay } = React.useContext(LayersContext); + + return ( + { + // avoid DropTarget handleFocus + e.stopPropagation(); + }} + style={style ?? styles} + > + + + + + ); + }, +); + +const AutoHeightOverlayContainer: React.FC = memo( + (props) => { + const widgetId = props.widgetId; + const { + isDragging, + isResizing, + lastSelectedWidget: selectedWidget, + selectedWidgets, + } = useSelector((state: AppState) => state.ui.widgetDragResize); + + const isPreviewMode = useSelector(previewModeSelector); + + const isWidgetSelected = selectedWidget === widgetId; + const multipleWidgetsSelected = selectedWidgets.length > 1; + const isHidden = multipleWidgetsSelected || isDragging || isResizing; + + if (isWidgetSelected && !isPreviewMode) { + return ; + } + + return null; + }, +); + +export default AutoHeightOverlayContainer; diff --git a/app/client/src/components/autoHeightOverlay/store.ts b/app/client/src/components/autoHeightOverlay/store.ts new file mode 100644 index 0000000000..d1174da885 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/store.ts @@ -0,0 +1,100 @@ +import { GridDefaults } from "constants/WidgetConstants"; + +interface AutoHeightLimitsUIState { + isMaxDotDragging: boolean; + isMinDotDragging: boolean; + maxY: number; // the actual value + maxdY: number; // the difference during dragging + minY: number; // the actual value + mindY: number; // the difference during dragging +} + +type SET_MAX_Y = { type: "SET_MAX_Y"; payload: { maxY: number } }; +type SET_MIN_Y = { type: "SET_MIN_Y"; payload: { minY: number } }; +type SET_MAX_D_Y = { type: "SET_MAX_D_Y"; payload: { maxdY: number } }; +type SET_MIN_D_Y = { type: "SET_MIN_D_Y"; payload: { mindY: number } }; +type SET_IS_MIN_DOT_DRAGGING = { + type: "SET_IS_MIN_DOT_DRAGGING"; + payload: { isMinDotDragging: boolean }; +}; + +type SET_IS_MAX_DOT_DRAGGING = { + type: "SET_IS_MAX_DOT_DRAGGING"; + payload: { isMaxDotDragging: boolean }; +}; + +type AutoHeightLimitsUIAction = + | SET_MAX_Y + | SET_MIN_Y + | SET_MAX_D_Y + | SET_MIN_D_Y + | SET_IS_MIN_DOT_DRAGGING + | SET_IS_MAX_DOT_DRAGGING; + +export function AutoHeightOverlayUIStateReducer( + state: AutoHeightLimitsUIState, + action: AutoHeightLimitsUIAction, +) { + if (action.type === "SET_IS_MAX_DOT_DRAGGING") { + return { + ...state, + isMaxDotDragging: action.payload.isMaxDotDragging, + }; + } + + if (action.type === "SET_IS_MIN_DOT_DRAGGING") { + return { + ...state, + isMinDotDragging: action.payload.isMinDotDragging, + }; + } + + if (action.type === "SET_MAX_Y") { + return { + ...state, + maxY: action.payload.maxY, + }; + } + + if (action.type === "SET_MIN_Y") { + return { + ...state, + minY: action.payload.minY, + }; + } + + if (action.type === "SET_MAX_D_Y") { + return { + ...state, + maxdY: action.payload.maxdY, + }; + } + + if (action.type === "SET_MIN_D_Y") { + return { + ...state, + mindY: action.payload.mindY, + }; + } + + return state; +} + +interface CreateInitialAutoHeightUIStateProps { + maxDynamicHeight: number; + minDynamicHeight: number; +} + +export function createInitialAutoHeightUIState({ + maxDynamicHeight, + minDynamicHeight, +}: CreateInitialAutoHeightUIStateProps) { + return { + isMinDotDragging: false, + isMaxDotDragging: false, + maxY: maxDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, // the actual value + maxdY: 0, // the difference during dragging + minY: minDynamicHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, // the actual value + mindY: 0, // the difference during dragging + }; +} diff --git a/app/client/src/components/autoHeightOverlay/types.ts b/app/client/src/components/autoHeightOverlay/types.ts new file mode 100644 index 0000000000..ec8fee4f2b --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/types.ts @@ -0,0 +1,10 @@ +export interface onDragCallbacksProps { + onStart: () => void; + onStop: () => void; + onUpdate: (x: number, y: number) => void; +} + +export interface onMouseHoverCallbacksProps { + onMouseEnter: () => void; + onMouseLeave: () => void; +} diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.test.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.test.tsx new file mode 100644 index 0000000000..c94a996d41 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.test.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import "@testing-library/jest-dom"; +import AutoHeightLimitHandleBorder from "./AutoHeightLimitHandleBorder"; +import "jest-styled-components"; +import renderer from "react-test-renderer"; +import { OVERLAY_COLOR } from "../constants"; + +describe("", () => { + it("should have background-color style set to OVERLAY_COLOR when isActive is true", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("background-color", OVERLAY_COLOR); + }); + + it("should have background-color style set to undefined when isActive is false", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("background-color"); + }); +}); diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.tsx new file mode 100644 index 0000000000..74e92e8a22 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleBorder.tsx @@ -0,0 +1,24 @@ +import styled from "styled-components"; +import { OVERLAY_COLOR } from "../constants"; + +interface AutoHeightLimitHandleBorderProps { + isActive: boolean; +} + +const AutoHeightLimitHandleBorder = styled.div< + AutoHeightLimitHandleBorderProps +>` + background-image: linear-gradient( + to right, + ${OVERLAY_COLOR} 50%, + rgba(255, 255, 255, 0) 0% + ); + background-size: 8% 1px; + background-repeat: repeat-x; + height: 1px; + width: 100%; + + ${(props) => (props.isActive ? `background-color: ${OVERLAY_COLOR}` : "")} +`; + +export default AutoHeightLimitHandleBorder; diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.test.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.test.tsx new file mode 100644 index 0000000000..9aba34b0a0 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.test.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import "@testing-library/jest-dom"; +import AutoHeightLimitHandleDot from "./AutoHeightLimitHandleDot"; +import "jest-styled-components"; +import renderer from "react-test-renderer"; +import { OVERLAY_COLOR } from "../constants"; + +describe("", () => { + it("should have scale style set to 1 when isDragging is false", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("transform", "translateX(-50%) scale( 1 )"); + }); + + it("should have scale style set to 1.67 when isDragging is true", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("transform", "translateX(-50%) scale( 1.67 )"); + }); +}); diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.tsx new file mode 100644 index 0000000000..59e41d70b3 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleDot.tsx @@ -0,0 +1,23 @@ +import styled from "styled-components"; +import { OVERLAY_COLOR, OVERLAY_HANDLE_DOT_DRAGGING_SCALE } from "../constants"; + +interface AutoHeightLimitHandleDotProps { + isDragging: boolean; +} + +const AutoHeightLimitHandleDot = styled.div` + position: absolute; + left: 50%; + border-radius: 50%; + width: 7px; + height: 7px; + transform: translateX(-50%) + scale( + ${(props) => (props.isDragging ? OVERLAY_HANDLE_DOT_DRAGGING_SCALE : "1")} + ); + border: 1px solid ${OVERLAY_COLOR}; + background-color: ${OVERLAY_COLOR}; + box-shadow: 0px 0px 0px 2px white; +`; + +export default AutoHeightLimitHandleDot; diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.test.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.test.tsx new file mode 100644 index 0000000000..81459b9dcd --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.test.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import "@testing-library/jest-dom"; +import AutoHeightLimitHandleLabel from "./AutoHeightLimitHandleLabel"; +import "jest-styled-components"; +import renderer from "react-test-renderer"; + +describe("", () => { + it("should have display none when isActive is false", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("display", "none"); + }); + + it("should have display initial when isActive is true", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("display", "initial"); + }); +}); diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.tsx new file mode 100644 index 0000000000..c4143a86f8 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitHandleLabel.tsx @@ -0,0 +1,23 @@ +import styled from "styled-components"; + +interface AutoHeightLimitHandleLabel { + isActive: boolean; +} + +const AutoHeightLimitHandleLabel = styled.div` + position: absolute; + pointer-events: none; + padding: 1px 4px; + background: #191919; + font-weight: 400; + font-size: 10px; + line-height: 16px; + color: #ffffff; + text-align: center; + white-space: nowrap; + left: 0px; + transform: translate(calc(-100% - 4px), -2px); + display: ${(props) => (props.isActive ? "initial" : "none")}; +`; + +export default AutoHeightLimitHandleLabel; diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.test.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.test.tsx new file mode 100644 index 0000000000..e83cb87699 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.test.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import "@testing-library/jest-dom"; +import AutoHeightLimitOverlayDisplay from "./AutoHeightLimitOverlayDisplay"; +import "jest-styled-components"; +import renderer from "react-test-renderer"; + +describe("", () => { + it("should have display none when isActive is false", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("display", "none"); + }); + + it("should have display block when isActive is true", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("display", "block"); + }); + + it("should have height style equal to the height passed in props", () => { + const tree = renderer + .create() + .toJSON(); + expect(tree).toHaveStyleRule("height", "10px"); + }); +}); diff --git a/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.tsx b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.tsx new file mode 100644 index 0000000000..8deee0122d --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/ui/AutoHeightLimitOverlayDisplay.tsx @@ -0,0 +1,20 @@ +import styled from "styled-components"; + +interface AutoHeightLimitOverlayDisplayProps { + isActive: boolean; + height: number; +} + +const AutoHeightLimitOverlayDisplay = styled.div< + AutoHeightLimitOverlayDisplayProps +>` + display: ${(props) => (props.isActive ? "block" : "none")}; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: ${(props) => props.height}px; + background-color: rgba(243, 43, 139, 0.1); +`; + +export default AutoHeightLimitOverlayDisplay; diff --git a/app/client/src/components/autoHeightOverlay/utils.ts b/app/client/src/components/autoHeightOverlay/utils.ts new file mode 100644 index 0000000000..c0214db3a1 --- /dev/null +++ b/app/client/src/components/autoHeightOverlay/utils.ts @@ -0,0 +1,15 @@ +import { GridDefaults } from "constants/WidgetConstants"; + +export const heightToRows = (height: number) => + Math.floor(height / GridDefaults.DEFAULT_GRID_ROW_HEIGHT); + +export const getSnappedValues = ( + x: number, + y: number, + snapGrid: { x: number; y: number }, +) => { + return { + x: Math.round(x / snapGrid.x) * snapGrid.x, + y: Math.round(y / snapGrid.y) * snapGrid.y, + }; +}; diff --git a/app/client/src/components/editorComponents/ActionCreator/viewComponents/TextView/index.tsx b/app/client/src/components/editorComponents/ActionCreator/viewComponents/TextView/index.tsx index 7a861bfeb8..b15e5ad773 100644 --- a/app/client/src/components/editorComponents/ActionCreator/viewComponents/TextView/index.tsx +++ b/app/client/src/components/editorComponents/ActionCreator/viewComponents/TextView/index.tsx @@ -4,7 +4,7 @@ import { FieldWrapper, } from "components/propertyControls/StyledControls"; import { InputText } from "components/propertyControls/InputTextControl"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import React from "react"; export function TextView(props: TextViewProps) { diff --git a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx index 7867d576ce..00dbdd3d5c 100644 --- a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx +++ b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx @@ -1,5 +1,5 @@ import React, { memo } from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import styled from "styled-components"; import { generateReactKey } from "utils/generators"; import { Collapsible } from "."; @@ -14,7 +14,6 @@ import { } from "@appsmith/constants/messages"; import { SuggestedWidget } from "api/ActionAPI"; -import { useSelector } from "store"; import { getDataTree } from "selectors/dataTreeSelectors"; import { getWidgets } from "sagas/selectors"; import { getNextWidgetName } from "sagas/WidgetOperationUtils"; diff --git a/app/client/src/components/editorComponents/CodeEditor/EditorConfig.ts b/app/client/src/components/editorComponents/CodeEditor/EditorConfig.ts index aabbb91236..e7fb2ffd38 100644 --- a/app/client/src/components/editorComponents/CodeEditor/EditorConfig.ts +++ b/app/client/src/components/editorComponents/CodeEditor/EditorConfig.ts @@ -1,7 +1,7 @@ import CodeMirror from "codemirror"; import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import { AdditionalDynamicDataTree } from "utils/autocomplete/customTreeTypeDefCreator"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export enum EditorModes { TEXT = "text/plain", diff --git a/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts b/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts index e9d4478f9e..793c4f24b7 100644 --- a/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts +++ b/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts @@ -3,7 +3,7 @@ import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig" import { AutocompleteDataType, CommandsCompletion, -} from "utils/autocomplete/TernServer"; +} from "utils/autocomplete/CodemirrorTernService"; import { generateQuickCommands } from "./generateQuickCommands"; import { Datasource } from "entities/Datasource"; import AnalyticsUtil from "utils/AnalyticsUtil"; diff --git a/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx b/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx index 1988dbee6e..6d068caf41 100644 --- a/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx @@ -3,7 +3,7 @@ import React from "react"; import { AutocompleteDataType, CommandsCompletion, -} from "utils/autocomplete/TernServer"; +} from "utils/autocomplete/CodemirrorTernService"; import ReactDOM from "react-dom"; import sortBy from "lodash/sortBy"; import { PluginType, SlashCommand, SlashCommandPayload } from "entities/Action"; diff --git a/app/client/src/components/editorComponents/CodeEditor/hintHelpers.ts b/app/client/src/components/editorComponents/CodeEditor/hintHelpers.ts index 4c832cac22..5f1fb01bfb 100644 --- a/app/client/src/components/editorComponents/CodeEditor/hintHelpers.ts +++ b/app/client/src/components/editorComponents/CodeEditor/hintHelpers.ts @@ -1,5 +1,5 @@ import CodeMirror from "codemirror"; -import TernServer from "utils/autocomplete/TernServer"; +import CodemirrorTernService from "utils/autocomplete/CodemirrorTernService"; import KeyboardShortcuts from "constants/KeyboardShortcuts"; import { HintHelper } from "components/editorComponents/CodeEditor/EditorConfig"; import AnalyticsUtil from "utils/AnalyticsUtil"; @@ -11,12 +11,12 @@ export const bindingHint: HintHelper = (editor) => { // @ts-expect-error: Types are not available ...editor.options.extraKeys, [KeyboardShortcuts.CodeEditor.OpenAutocomplete]: (cm: CodeMirror.Editor) => - checkIfCursorInsideBinding(cm) && TernServer.complete(cm), + checkIfCursorInsideBinding(cm) && CodemirrorTernService.complete(cm), [KeyboardShortcuts.CodeEditor.ShowTypeAndInfo]: (cm: CodeMirror.Editor) => { - TernServer.showType(cm); + CodemirrorTernService.showType(cm); }, [KeyboardShortcuts.CodeEditor.OpenDocsLink]: (cm: CodeMirror.Editor) => { - TernServer.showDocs(cm); + CodemirrorTernService.showDocs(cm); }, }); return { @@ -26,12 +26,12 @@ export const bindingHint: HintHelper = (editor) => { additionalData, ): boolean => { if (additionalData && additionalData.blockCompletions) { - TernServer.setEntityInformation({ + CodemirrorTernService.setEntityInformation({ ...entityInformation, blockCompletions: additionalData.blockCompletions, }); } else { - TernServer.setEntityInformation(entityInformation); + CodemirrorTernService.setEntityInformation(entityInformation); } const entityType = entityInformation?.entityType; @@ -43,7 +43,7 @@ export const bindingHint: HintHelper = (editor) => { } if (shouldShow) { AnalyticsUtil.logEvent("AUTO_COMPLETE_SHOW", {}); - TernServer.complete(editor); + CodemirrorTernService.complete(editor); return true; } // @ts-expect-error: Types are not available diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index 35b6abeffd..ceaacec41f 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -80,7 +80,7 @@ import { Button } from "design-system"; import { getPluginIdToImageLocation } from "sagas/selectors"; import { ExpectedValueExample } from "utils/validation/common"; import { getRecentEntityIds } from "selectors/globalSearchSelectors"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { Placement } from "@blueprintjs/popover2"; import { getLintAnnotations, getLintTooltipDirection } from "./lintHelpers"; import { executeCommandAction } from "actions/apiPaneActions"; diff --git a/app/client/src/components/editorComponents/Debugger/index.tsx b/app/client/src/components/editorComponents/Debugger/index.tsx index 9aaa7ff394..2d2b9da9f1 100644 --- a/app/client/src/components/editorComponents/Debugger/index.tsx +++ b/app/client/src/components/editorComponents/Debugger/index.tsx @@ -1,7 +1,6 @@ import { Icon, IconSize } from "design-system"; import React from "react"; -import { useDispatch } from "react-redux"; -import { useSelector } from "store"; +import { useDispatch, useSelector } from "react-redux"; import styled from "styled-components"; import DebuggerTabs from "./DebuggerTabs"; import { AppState } from "@appsmith/reducers"; diff --git a/app/client/src/components/editorComponents/DropTargetComponent.tsx b/app/client/src/components/editorComponents/DropTargetComponent.tsx index 00473755e6..857cec1ba5 100644 --- a/app/client/src/components/editorComponents/DropTargetComponent.tsx +++ b/app/client/src/components/editorComponents/DropTargetComponent.tsx @@ -24,9 +24,13 @@ import { useShowPropertyPane, useCanvasSnapRowsUpdateHook, } from "utils/hooks/dragResizeHooks"; -import { getOccupiedSpacesSelectorForContainer } from "selectors/editorSelectors"; +import { + getOccupiedSpacesSelectorForContainer, + previewModeSelector, +} from "selectors/editorSelectors"; import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import { getDragDetails } from "sagas/selectors"; +import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks"; type DropTargetComponentProps = WidgetProps & { children?: ReactNode; @@ -64,16 +68,52 @@ export const DropTargetContext: Context<{ ) => number | false; }> = createContext({}); -export function DropTargetComponent(props: DropTargetComponentProps) { - const canDropTargetExtend = props.canExtend; - const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend); +/** + * Gets the dropTarget height + * @param canDropTargetExtend boolean: Can we put widgets below the scrollview in this canvas? + * @param isPreviewMode boolean: Are we in the preview mode + * @param currentHeight number: Current height in the ref and what we have set in the dropTarget + * @param snapRowSpace number: This is a static value actually, GridDefaults.DEFAULT_GRID_ROW_HEIGHT + * @param minHeight number: The minHeight we've set to the widget in the reducer + * @returns number: A new height style to set in the dropTarget. + */ +function getDropTargetHeight( + canDropTargetExtend: boolean, + isPreviewMode: boolean, + currentHeight: number, + snapRowSpace: number, + minHeight: number, +) { + let height = canDropTargetExtend + ? `${Math.max(currentHeight * snapRowSpace, minHeight)}px` + : "100%"; + if (isPreviewMode && canDropTargetExtend) + height = `${currentHeight * snapRowSpace}px`; + return height; +} +export function DropTargetComponent(props: DropTargetComponentProps) { + // Get if this is in preview mode. + const isPreviewMode = useSelector(previewModeSelector); + // Pretty much the shouldScrollContents from the parent container like widget + const canDropTargetExtend = props.canExtend; + // If in preview mode, we don't need that extra row + // This gives us the number of rows + const snapRows = getCanvasSnapRows( + props.bottomRow, + props.canExtend && !isPreviewMode, + ); + + // Are we currently resizing? const isResizing = useSelector( (state: AppState) => state.ui.widgetDragResize.isResizing, ); + // Are we currently dragging? const isDragging = useSelector( (state: AppState) => state.ui.widgetDragResize.isDragging, ); + // Are we changing the auto height limits by dragging the signifiers? + const { isAutoHeightWithLimitsChanging } = useAutoHeightUIState(); // dragDetails contains of info needed for a container jump: // which parent the dragging widget belongs, @@ -84,37 +124,59 @@ export function DropTargetComponent(props: DropTargetComponentProps) { const { draggedOn } = dragDetails; + // All the widgets in this canvas const childWidgets: string[] | undefined = useSelector( (state: AppState) => state.entities.canvasWidgets[props.widgetId]?.children, ); + // The occupied spaces in this canvas. It is a data structure which has the rect values of each child. const selectOccupiedSpaces = useCallback( getOccupiedSpacesSelectorForContainer(props.widgetId), [props.widgetId], ); + // Call the selector above. const occupiedSpacesByChildren = useSelector(selectOccupiedSpaces, equal); + // Put the existing snap rows in a ref. const rowRef = useRef(snapRows); + // This shows the property pane const showPropertyPane = useShowPropertyPane(); - const { deselectAll, focusWidget } = useWidgetSelection(); - const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook(); - const showDragLayer = - (isDragging && draggedOn === props.widgetId) || isResizing; + const { deselectAll, focusWidget } = useWidgetSelection(); + + // This updates the bottomRow of this canvas, as simple as that + // This also doesn't cause an eval as it uses the action which is + // not registered to cause an eval + const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook(); + + // Everytime we get a new bottomRow, or we toggle shouldScrollContents + // we call this effect useEffect(() => { - const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend); + const snapRows = getCanvasSnapRows( + props.bottomRow, + props.canExtend && !isPreviewMode, + ); + // If the current ref is not set to the new snaprows we've received (based on bottomRow) if (rowRef.current !== snapRows) { rowRef.current = snapRows; + // This sets the "height" property of the dropTarget div + // This makes the div change heights if new heights are different updateHeight(); - if (canDropTargetExtend) { + // This sets the new rows in the reducer + // Not sure why, as we've just received the values from the props. + // seems like a potential way to cause recursive renders + // See this: https://github.com/appsmithorg/appsmith/pull/18457#issuecomment-1327615572 + if (canDropTargetExtend && !isPreviewMode) { updateCanvasSnapRows(props.widgetId, snapRows); } } - }, [props.bottomRow, props.canExtend]); + }, [props.bottomRow, props.canExtend, isPreviewMode]); + + // If we've stopped dragging, resizing or changing auto height limits useEffect(() => { - if (!isDragging || !isResizing) { + if (!isDragging || !isResizing || !isAutoHeightWithLimitsChanging) { // bottom row of canvas can increase by any number as user moves/resizes any widget towards the bottom of the canvas // but canvas height is not lost when user moves/resizes back top. // it is done that way to have a pleasant building experience. @@ -122,16 +184,40 @@ export function DropTargetComponent(props: DropTargetComponentProps) { rowRef.current = snapRows; updateHeight(); } - }, [isDragging, isResizing]); + }, [isDragging, isResizing, isAutoHeightWithLimitsChanging]); + // Update the drop target height style directly. const updateHeight = () => { if (dropTargetRef.current) { - const height = canDropTargetExtend - ? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px` - : "100%"; + const height = getDropTargetHeight( + canDropTargetExtend, + isPreviewMode, + rowRef.current, + props.snapRowSpace, + props.minHeight, + ); + dropTargetRef.current.style.height = height; } }; + + const handleFocus = (e: any) => { + // Making sure that we don't deselect the widget + // after we are done dragging the limits in auto height with limits + if (!isResizing && !isDragging && !isAutoHeightWithLimitsChanging) { + if (!props.parentId) { + deselectAll(); + focusWidget && focusWidget(props.widgetId); + showPropertyPane && showPropertyPane(); + } + } + e.preventDefault(); + }; + + /** PREPARE CONTEXT */ + + // Function which computes and updates the height of the dropTarget + // This is used in a context and hence in one of the children of this dropTarget const updateDropTargetRows = ( widgetIdsToExclude: string[], widgetBottomRow: number, @@ -153,49 +239,57 @@ export function DropTargetComponent(props: DropTargetComponentProps) { } return false; }; - - const handleFocus = (e: any) => { - if (!isResizing && !isDragging) { - if (!props.parentId) { - deselectAll(); - focusWidget && focusWidget(props.widgetId); - showPropertyPane && showPropertyPane(); - } - } - // commenting this out to allow propagation of click events - // e.stopPropagation(); - e.preventDefault(); - }; - const height = canDropTargetExtend - ? `${Math.max(rowRef.current * props.snapRowSpace, props.minHeight)}px` - : "100%"; - const boxShadow = - (isResizing || isDragging) && props.widgetId === MAIN_CONTAINER_WIDGET_ID - ? "inset 0px 0px 0px 1px #DDDDDD" - : "0px 0px 0px 1px transparent"; - const dropTargetRef = useRef(null); - // memoizing context values const contextValue = useMemo(() => { return { updateDropTargetRows, }; }, [updateDropTargetRows, occupiedSpacesByChildren]); + + /** EO PREPARE CONTEXT */ + + const height = getDropTargetHeight( + canDropTargetExtend, + isPreviewMode, + rowRef.current, + props.snapRowSpace, + props.minHeight, + ); + + const boxShadow = + (isResizing || isDragging || isAutoHeightWithLimitsChanging) && + props.widgetId === MAIN_CONTAINER_WIDGET_ID + ? "inset 0px 0px 0px 1px #DDDDDD" + : "0px 0px 0px 1px transparent"; + + const dropTargetStyles = { + height, + boxShadow, + }; + + const shouldOnboard = + !(childWidgets && childWidgets.length) && !isDragging && !props.parentId; + + // The drag layer is the one with the grid dots. + // They need to show in certain scenarios + const showDragLayer = + ((isDragging && draggedOn === props.widgetId) || + isResizing || + isAutoHeightWithLimitsChanging) && + !isPreviewMode; + + const dropTargetRef = useRef(null); + return ( {props.children} - {!(childWidgets && childWidgets.length) && - !isDragging && - !props.parentId && } + {shouldOnboard && } {showDragLayer && ( ; -} - -export default PreviewModeComponent; diff --git a/app/client/src/components/editorComponents/ResizableComponent.tsx b/app/client/src/components/editorComponents/ResizableComponent.tsx index 47457fac01..fad2e5f6ab 100644 --- a/app/client/src/components/editorComponents/ResizableComponent.tsx +++ b/app/client/src/components/editorComponents/ResizableComponent.tsx @@ -33,14 +33,15 @@ import { } from "./ResizeStyledComponents"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { - previewModeSelector, snipingModeSelector, + previewModeSelector, } from "selectors/editorSelectors"; import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import { focusWidget } from "actions/widgetActions"; import { GridDefaults } from "constants/WidgetConstants"; import { DropTargetContext } from "./DropTargetComponent"; import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import { getParentToOpenSelector } from "selectors/widgetSelectors"; import { isCurrentWidgetFocused, @@ -111,8 +112,8 @@ export const ResizableComponent = memo(function ResizableComponent( width: newDimensions.width - dimensions.width, }; const newRowCols: WidgetRowCols = computeRowCols(delta, position, props); - let canResizeHorizontally = true, - canResizeVertically = true; + let canResizeVertically = true; + let canResizeHorizontally = true; // this is required for list widget so that template have no collision if (props.ignoreCollision) @@ -136,7 +137,6 @@ export const ResizableComponent = memo(function ResizableComponent( ) { canResizeVertically = false; } - const resizedPositions = { id: props.widgetId, left: newRowCols.leftColumn, @@ -145,6 +145,12 @@ export const ResizableComponent = memo(function ResizableComponent( right: newRowCols.rightColumn, }; + if (isAutoHeightEnabledForWidget(props)) { + canResizeVertically = false; + resizedPositions.top = props.topRow; + resizedPositions.bottom = props.bottomRow; + } + // Check if new row cols are occupied by sibling widgets return { canResizeHorizontally, @@ -246,7 +252,6 @@ export const ResizableComponent = memo(function ResizableComponent( !props.resizeDisabled && !isSnipingMode && !isPreviewMode; - const { updateDropTargetRows } = useContext(DropTargetContext); const gridProps = { @@ -269,12 +274,25 @@ export const ResizableComponent = memo(function ResizableComponent( } }; + const snapGrid = useMemo( + () => ({ + x: props.parentColumnSpace, + y: props.parentRowSpace, + }), + [props.parentColumnSpace, props.parentRowSpace], + ); + + const isVerticalResizeEnabled = useMemo(() => { + return !isAutoHeightEnabledForWidget(props) && isEnabled; + }, [props, isAutoHeightEnabledForWidget, isEnabled]); + return ( { + it("Handle resize is allowed if directions are not provided", () => { + const input = { + horizontalEnabled: false, + verticalEnabled: false, + }; + const result = isHandleResizeAllowed( + input.horizontalEnabled, + input.verticalEnabled, + ); + + expect(result).toBe(true); + }); + + it("Handle resize is allowed if directions provided are not vertical or horizontal", () => { + const input = { + horizontalEnabled: false, + verticalEnabled: false, + direction: ReflowDirection.BOTTOMLEFT, + }; + const result = isHandleResizeAllowed( + input.horizontalEnabled, + input.verticalEnabled, + input.direction, + ); + + expect(result).toBe(true); + }); + + it("Handle resize is allowed if horizontal resize is enabled and directs are left and right", () => { + const input = [ + { + horizontalEnabled: true, + verticalEnabled: false, + direction: ReflowDirection.LEFT, + }, + { + horizontalEnabled: true, + verticalEnabled: false, + direction: ReflowDirection.RIGHT, + }, + ]; + input.forEach((_input) => { + const result = isHandleResizeAllowed( + _input.horizontalEnabled, + _input.verticalEnabled, + _input.direction, + ); + + expect(result).toBe(true); + }); + }); + it("Handle resize is disallowed if horizontal resize is disabled and directs are left and right", () => { + const input = [ + { + horizontalEnabled: false, + verticalEnabled: false, + direction: ReflowDirection.LEFT, + }, + { + horizontalEnabled: false, + verticalEnabled: false, + direction: ReflowDirection.RIGHT, + }, + ]; + input.forEach((_input) => { + const result = isHandleResizeAllowed( + _input.horizontalEnabled, + _input.verticalEnabled, + _input.direction, + ); + + expect(result).toBe(false); + }); + }); + it("Handle resize is allowed if vertical resize is enabled and directs are top and bottom", () => { + const input = [ + { + horizontalEnabled: true, + verticalEnabled: true, + direction: ReflowDirection.TOP, + }, + { + horizontalEnabled: true, + verticalEnabled: true, + direction: ReflowDirection.BOTTOM, + }, + ]; + input.forEach((_input) => { + const result = isHandleResizeAllowed( + _input.horizontalEnabled, + _input.verticalEnabled, + _input.direction, + ); + + expect(result).toBe(true); + }); + }); + it("Handle resize is disallowed if vertical resize is disabled and directs are top and bottom", () => { + const input = [ + { + horizontalEnabled: false, + verticalEnabled: false, + direction: ReflowDirection.TOP, + }, + { + horizontalEnabled: false, + verticalEnabled: false, + direction: ReflowDirection.BOTTOM, + }, + ]; + input.forEach((_input) => { + const result = isHandleResizeAllowed( + _input.horizontalEnabled, + _input.verticalEnabled, + _input.direction, + ); + + expect(result).toBe(false); + }); + }); +}); diff --git a/app/client/src/components/editorComponents/ResizableUtils.tsx b/app/client/src/components/editorComponents/ResizableUtils.ts similarity index 68% rename from app/client/src/components/editorComponents/ResizableUtils.tsx rename to app/client/src/components/editorComponents/ResizableUtils.ts index cf37170167..d1395858d4 100644 --- a/app/client/src/components/editorComponents/ResizableUtils.tsx +++ b/app/client/src/components/editorComponents/ResizableUtils.ts @@ -1,6 +1,7 @@ import { WidgetProps, WidgetRowCols } from "widgets/BaseWidget"; import { GridDefaults } from "constants/WidgetConstants"; import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging"; +import { ReflowDirection } from "reflow/reflowTypes"; export type UIElementSize = { height: number; width: number }; @@ -73,3 +74,29 @@ export const computeFinalRowCols = ( return hasRowColsChanged(newRowCols, props) ? newRowCols : false; }; + +/** + * A rudimentary function which based on horizontal and vertical resize enabled + * tells us whether a resize handle in a particular direction works + * Note: This works only if vertical or horizontal directions are provided. + * @param horizontalEnabled : boolean + * @param verticalEnabled : boolean + * @param direction : ReflowDirection + * @returns if resize is allowed in the direction provided + * Works only for vertical and horizontal directions + */ +export function isHandleResizeAllowed( + horizontalEnabled: boolean, + verticalEnabled: boolean, + direction?: ReflowDirection, +): boolean { + if (direction === ReflowDirection.TOP || direction === ReflowDirection.BOTTOM) + return verticalEnabled; + else if ( + direction === ReflowDirection.LEFT || + direction === ReflowDirection.RIGHT + ) { + return horizontalEnabled; + } + return true; +} diff --git a/app/client/src/components/editorComponents/ResizeStyledComponents.tsx b/app/client/src/components/editorComponents/ResizeStyledComponents.tsx index d4d2efce5e..1bd603517b 100644 --- a/app/client/src/components/editorComponents/ResizeStyledComponents.tsx +++ b/app/client/src/components/editorComponents/ResizeStyledComponents.tsx @@ -35,6 +35,7 @@ const ResizeIndicatorStyle = css<{ export const EdgeHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; + disableDot: boolean; }>` position: absolute; width: ${EDGE_RESIZE_HANDLE_WIDTH}px; @@ -50,17 +51,20 @@ export const EdgeHandleStyles = css<{ }}; content: ""; } - ${(props) => (!props.showAsBorder ? ResizeIndicatorStyle : "")} + ${(props) => + props.showAsBorder || props.disableDot ? "" : ResizeIndicatorStyle} `; export const VerticalHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; + disableDot: boolean; }>` ${EdgeHandleStyles} top:${~(WIDGET_PADDING - 1) + 1}px; height: calc(100% + ${2 * WIDGET_PADDING - 1}px); - ${(props) => (!props.showAsBorder ? "cursor: col-resize;" : "")} + ${(props) => + props.showAsBorder || props.disableDot ? "" : "cursor: col-resize;"} &:before { left: 50%; bottom: 0px; @@ -72,11 +76,13 @@ export const VerticalHandleStyles = css<{ export const HorizontalHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; + disableDot: boolean; }>` ${EdgeHandleStyles} left: ${~WIDGET_PADDING + 1}px; width: calc(100% + ${2 * WIDGET_PADDING}px); - ${(props) => (!props.showAsBorder ? "cursor: row-resize;" : "")} + ${(props) => + props.showAsBorder || props.disableDot ? "" : "cursor: row-resize;"} &:before { top: 50%; right: 0px; diff --git a/app/client/src/components/editorComponents/form/ToggleComponentToJson.tsx b/app/client/src/components/editorComponents/form/ToggleComponentToJson.tsx index 1a853b99bf..0d9948ea51 100644 --- a/app/client/src/components/editorComponents/form/ToggleComponentToJson.tsx +++ b/app/client/src/components/editorComponents/form/ToggleComponentToJson.tsx @@ -12,7 +12,9 @@ import { connect, useSelector } from "react-redux"; import { getFormValues } from "redux-form"; import { AnyAction, bindActionCreators, Dispatch } from "redux"; import { change } from "redux-form"; -import { JSToggleButton } from "design-system"; +import { JSToggleButton, TooltipComponent } from "design-system"; +import { get } from "lodash"; +import { JS_TOGGLE_DISABLED_MESSAGE } from "ce/constants/messages"; type Props = { viewType: ViewTypes; @@ -37,22 +39,40 @@ function ToggleComponentToJsonHandler(props: HandlerProps) { ); const viewType = getViewType(formValues, props.configProperty); + // variable to control + let configPropertyPathJsonValue = ""; + + if (viewType === ViewTypes.JSON) { + // if viewType is json mode + // get the value of the json field and store it in configPropertyPathJsonValue. + configPropertyPathJsonValue = get(formValues, props.configProperty); + } const handleViewTypeSwitch = () => { - switchViewType( - formValues, - props.configProperty, - viewType, - props.formName, - props.change, - ); + // only allow switching when the json value is an empty string or undefined. + // capitalizing on falsy nature of empty strings/undefined vals. + if (!configPropertyPathJsonValue) { + switchViewType( + formValues, + props.configProperty, + viewType, + props.formName, + props.change, + ); + } }; + return ( - + + + ); } diff --git a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx index f5cf72c6ef..5f6d95362e 100644 --- a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx +++ b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx @@ -11,7 +11,7 @@ import { EditorTheme, } from "components/editorComponents/CodeEditor/EditorConfig"; import { Case, Classes, Icon, IconSize, Text, TextType } from "design-system"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import DynamicDropdownField from "./DynamicDropdownField"; import { DEFAULT_MULTI_PART_DROPDOWN_PLACEHOLDER, diff --git a/app/client/src/components/editorComponents/form/fields/__tests__/KeyValueFieldArray.test.tsx b/app/client/src/components/editorComponents/form/fields/__tests__/KeyValueFieldArray.test.tsx index f2dc70d6aa..9691b3016b 100644 --- a/app/client/src/components/editorComponents/form/fields/__tests__/KeyValueFieldArray.test.tsx +++ b/app/client/src/components/editorComponents/form/fields/__tests__/KeyValueFieldArray.test.tsx @@ -2,11 +2,11 @@ import React from "react"; import "@testing-library/jest-dom"; import KeyValueFieldArray from "../KeyValueFieldArray"; import { reduxForm } from "redux-form"; -import { render, screen } from "test/testUtils"; +import { render } from "test/testUtils"; const initialProps = { name: "Headers", - actionConfig: [], + actionConfig: [] as Array<{ key: string; value: string }>, dataTreePath: ".config.headers", hideHeader: false, label: "Headers", diff --git a/app/client/src/components/formControls/utils.test.ts b/app/client/src/components/formControls/utils.test.ts index 932d16ef93..ac520da2ba 100644 --- a/app/client/src/components/formControls/utils.test.ts +++ b/app/client/src/components/formControls/utils.test.ts @@ -570,7 +570,7 @@ describe("json/form viewTypes test", () => { node3: { data: "value1" }, node2: { data: "value1", viewType: ViewTypes.JSON }, node4: { - data: "value2", + data: "value1", viewType: ViewTypes.JSON, jsonData: "value2", componentData: "value1", diff --git a/app/client/src/components/formControls/utils.ts b/app/client/src/components/formControls/utils.ts index 88a96e6a51..f464f5f0d0 100644 --- a/app/client/src/components/formControls/utils.ts +++ b/app/client/src/components/formControls/utils.ts @@ -146,24 +146,21 @@ export const switchViewType = ( ".data", ".componentData", ); - const jsonData = get(values, pathForJsonData); const componentData = get(values, pathForComponentData); const currentData = get(values, configProperty, ""); const stringifiedCurrentData = JSON.stringify(currentData, null, "\t"); if (newViewType === ViewTypes.JSON) { changeFormValue(formName, pathForComponentData, currentData); - if (!!jsonData) { - changeFormValue(formName, configProperty, jsonData); - } else { - changeFormValue( - formName, - configProperty, - isString(currentData) - ? currentData - : stringifiedCurrentData.replace(/\\/g, ""), - ); - } + + // when switching to JSON, we always want a form to json conversion of the data. + changeFormValue( + formName, + configProperty, + isString(currentData) + ? currentData + : stringifiedCurrentData.replace(/\\/g, ""), + ); } else { changeFormValue(formName, pathForJsonData, currentData); if (!!componentData) { diff --git a/app/client/src/components/propertyControls/ChartDataControl.tsx b/app/client/src/components/propertyControls/ChartDataControl.tsx index 488b69f159..9a7cb7dade 100644 --- a/app/client/src/components/propertyControls/ChartDataControl.tsx +++ b/app/client/src/components/propertyControls/ChartDataControl.tsx @@ -15,7 +15,7 @@ import { import { Size, Category } from "design-system"; import { AllChartData, ChartData } from "widgets/ChartWidget/constants"; import { generateReactKey } from "utils/generators"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import CodeEditor from "components/editorComponents/LazyCodeEditorWrapper"; const Wrapper = styled.div` diff --git a/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx b/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx index 6000152b8b..64ca982ee0 100644 --- a/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx +++ b/app/client/src/components/propertyControls/ColorPickerComponentV2.tsx @@ -15,7 +15,7 @@ import { import { ReactComponent as ColorPickerIcon } from "assets/icons/control/color-picker.svg"; import { debounce, get } from "lodash"; import { Colors } from "constants/Colors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { getSelectedAppThemeProperties } from "selectors/appThemingSelectors"; import { colorsPropertyName, diff --git a/app/client/src/components/propertyControls/NumericInputControl.tsx b/app/client/src/components/propertyControls/NumericInputControl.tsx index 16eb4e8268..e6526cd7e1 100644 --- a/app/client/src/components/propertyControls/NumericInputControl.tsx +++ b/app/client/src/components/propertyControls/NumericInputControl.tsx @@ -60,6 +60,8 @@ class NumericInputControl extends BaseControl { max, min, minorStepSize, + onBlur, + onFocus, placeholderText, propertyValue, stepSize, @@ -75,6 +77,8 @@ class NumericInputControl extends BaseControl { max={max} min={min} minorStepSize={minorStepSize} + onBlur={onBlur} + onFocus={onFocus} onKeyDown={this.handleKeydown} onValueChange={this.handleValueChange} placeholder={placeholderText} @@ -106,6 +110,8 @@ export interface NumericInputControlProps extends ControlProps { majorStepSize?: number | null; placeholderText?: string; stepSize?: number; + onFocus?: () => void; + onBlur?: () => void; } export default NumericInputControl; diff --git a/app/client/src/config.d.ts b/app/client/src/config.d.ts new file mode 100644 index 0000000000..46fd6980de --- /dev/null +++ b/app/client/src/config.d.ts @@ -0,0 +1,8 @@ +import "react-redux"; +import { AppState } from "@appsmith/reducers"; + +declare module "react-redux" { + // We want the DefaultRootState interface to be the AppState interface + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface DefaultRootState extends AppState {} +} diff --git a/app/client/src/constants/CanvasEditorConstants.tsx b/app/client/src/constants/CanvasEditorConstants.tsx index 90c924e41a..a368c0274c 100644 --- a/app/client/src/constants/CanvasEditorConstants.tsx +++ b/app/client/src/constants/CanvasEditorConstants.tsx @@ -15,6 +15,7 @@ export type WidgetSpace = { id: string; type: string; parentId?: string; + fixedHeight?: number; }; export const zIndexLayers = { diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 9dcb454e7b..970ca47a7d 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -322,16 +322,16 @@ export const BlueprintRadioSwitchGroupTransform = css<{ width: 100%; height: 100%; - ${({ alignment, inline, optionCount }) => ` - display: ${ - inline ? "inline-flex" : alignment === Alignment.RIGHT ? "block" : "flex" - }; + ${({ inline, optionCount }) => ` + display: ${inline ? "inline-flex" : "flex"}; flex-direction: ${inline ? "row" : "column"}; align-items: ${inline ? "center" : "flex-start"}; ${inline && "flex-wrap: wrap"}; justify-content: ${ optionCount > 1 ? `space-between` : inline ? `flex-start` : `center` }; + gap: 10px; + flex-grow: 1; `} ${BlueprintControlTransform}; @@ -342,42 +342,17 @@ export const BlueprintRadioSwitchGroupTransform = css<{ } return "flex"; }}; + width: ${({ alignment, inline }) => { + if (alignment === Alignment.RIGHT) { + return inline ? "auto" : "100%"; + } + return "auto"; + }}; align-items: center; border: 1px solid transparent; color: ${Colors.GREY_10}; line-height: 16px; - min-height: ${({ alignment }) => - alignment === Alignment.RIGHT ? 23 : 30}px; - margin-top: ${({ alignment }) => (alignment === Alignment.RIGHT ? 7 : 0)}px; - margin-bottom: ${({ - alignment, - height, - inline, - labelPosition, - optionCount, - }) => { - if ( - alignment === Alignment.RIGHT && - !inline && - optionCount > 1 && - height - ) { - return Math.max( - (height - - (labelPosition === LabelPosition.Left ? 0 : 35) - - optionCount * 31) / - (optionCount - 1), - 8, - ); - } else { - return 0; - } - }}px; - - &:last-child { - margin-bottom: 0; - } .bp3-control-indicator { margin-top: 0; border: 1px solid ${Colors.GREY_5}; diff --git a/app/client/src/constants/Layers.tsx b/app/client/src/constants/Layers.tsx index 83b4df18a7..8ffbd8540c 100644 --- a/app/client/src/constants/Layers.tsx +++ b/app/client/src/constants/Layers.tsx @@ -57,6 +57,8 @@ export const Layers = { evaluationPopper: Indices.Layer3, concurrentEditorWarning: Indices.Layer2, manualUpgrade: Indices.Layer10, + + autoHeightWithLimitsOverlay: Indices.Layer3, }; export const tailwindLayers = { diff --git a/app/client/src/constants/PropertyControlConstants.tsx b/app/client/src/constants/PropertyControlConstants.tsx index cdef533f5a..498fc5662c 100644 --- a/app/client/src/constants/PropertyControlConstants.tsx +++ b/app/client/src/constants/PropertyControlConstants.tsx @@ -6,8 +6,9 @@ import { import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { CodeEditorExpected } from "components/editorComponents/CodeEditor"; import { UpdateWidgetPropertyPayload } from "actions/controlActions"; -import { AppTheme } from "entities/AppTheming"; +import { Stylesheet } from "entities/AppTheming"; import { WidgetProps } from "widgets/BaseWidget"; +import { ReduxActionType } from "@appsmith/constants/ReduxActionConstants"; const ControlTypes = getPropertyControlTypes(); export type ControlType = typeof ControlTypes[keyof typeof ControlTypes]; @@ -84,11 +85,18 @@ export type PropertyPaneControlConfig = { getStylesheetValue?: ( props: any, propertyPath: string, - stylesheet?: AppTheme["stylesheet"][string], - ) => AppTheme["stylesheet"][string][string]; + stylesheet?: Stylesheet, + ) => Stylesheet[string]; // TODO(abhinav): To fix this, rename the options property of the controls which use this // Alternatively, create a new structure options?: any; + // The following should ideally be used internally + postUpdateAction?: ReduxActionType; + onBlur?: () => void; + onFocus?: () => void; + + // Numeric Input Control + min?: number; }; type ValidationConfigParams = { diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 856c5ff90a..2a6e15ca35 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -70,7 +70,7 @@ export const layoutConfigurations: LayoutConfigurations = { FLUID: { minWidth: -1, maxWidth: -1 }, }; -export const LATEST_PAGE_VERSION = 67; +export const LATEST_PAGE_VERSION = 69; export const GridDefaults = { DEFAULT_CELL_SIZE: 1, @@ -83,6 +83,8 @@ export const GridDefaults = { MAIN_CANVAS_EXTENSION_OFFSET: 8, }; +export const CANVAS_MIN_HEIGHT = 380; + // Note: Widget Padding + Container Padding === DEFAULT_GRID_ROW_HEIGHT to gracefully lose one row when a container is used, // which wud allow the user to place elements centered inside a container(columns are rendered proportionally so it take cares of itself). @@ -138,6 +140,7 @@ export const WIDGET_STATIC_PROPS = { renderMode: true, detachFromLayout: true, noContainerOffset: false, + height: false, }; export const WIDGET_DSL_STRUCTURE_PROPS = { @@ -152,3 +155,15 @@ export const WIDGET_DSL_STRUCTURE_PROPS = { export type TextSize = keyof typeof TextSizes; export const DEFAULT_FONT_SIZE = THEMEING_TEXT_SIZES.base; + +// The max and min height limits for widgets in rows. +// 9000 is an arbitrarily large value for the height of a widget +// In pixels this would be 90000px, which is a fairly large number. + +// 4 is the minimum for any widget, as we donot support zero height widgets today. +// This also makes sure that widgets have sufficient area in which users can interact. +export const WidgetHeightLimits = { + MAX_HEIGHT_IN_ROWS: 9000, + MIN_HEIGHT_IN_ROWS: 4, + MIN_CANVAS_HEIGHT_IN_ROWS: 10, +}; diff --git a/app/client/src/entities/AppTheming/index.ts b/app/client/src/entities/AppTheming/index.ts index 10b477656d..31727a53bd 100644 --- a/app/client/src/entities/AppTheming/index.ts +++ b/app/client/src/entities/AppTheming/index.ts @@ -1,7 +1,28 @@ -type Stylesheet = { - [key: string]: { +type DefaultStylesheet = { + [key: string]: string | DefaultStylesheet; +} & { + childStylesheet?: AppThemeStylesheet; +}; + +export type Stylesheet = T extends void + ? DefaultStylesheet + : T & DefaultStylesheet; + +export type AppThemeStylesheet = { + [key: string]: Stylesheet; +}; + +export type ButtonStyles = { + resetButtonStyles: { [key: string]: string; }; + submitButtonStyles: { + [key: string]: string; + }; +}; + +export type ChildStylesheet = { + childStylesheet: AppThemeStylesheet; }; export type AppTheme = { @@ -35,23 +56,7 @@ export type AppTheme = { }; }; // styles for specific widgets - stylesheet: { - [key: string]: { - [key: string]: - | string - | Stylesheet - | { - [key: string]: string; - }; - childStylesheet: Stylesheet; - resetButtonStyles: { - [key: string]: string; - }; - submitButtonStyles: { - [key: string]: string; - }; - }; - }; + stylesheet: AppThemeStylesheet; // current values for the theme properties: { colors: { diff --git a/app/client/src/entities/AppTheming/utils.test.ts b/app/client/src/entities/AppTheming/utils.test.ts index c552602e84..5321f4b343 100644 --- a/app/client/src/entities/AppTheming/utils.test.ts +++ b/app/client/src/entities/AppTheming/utils.test.ts @@ -1,7 +1,21 @@ import { RenderModes } from "constants/WidgetConstants"; import { getPropertiesToUpdateForReset } from "./utils"; +import { registerWidget } from "utils/WidgetRegisterHelpers"; +import ButtonWidget, { + CONFIG as ButtonWidgetConfig, +} from "widgets/ButtonWidget"; +import TableWidget, { CONFIG as TableWidgetConfig } from "widgets/TableWidget"; +import JSONFormWidget, { + CONFIG as JSONFormWidgetConfig, +} from "widgets/JSONFormWidget"; describe("AppThemingSaga test", () => { + beforeAll(() => { + registerWidget(ButtonWidget, ButtonWidgetConfig); + registerWidget(TableWidget, TableWidgetConfig); + registerWidget(JSONFormWidget, JSONFormWidgetConfig); + }); + it("Checks if button widget resets to correct value", () => { const input = [ { @@ -36,6 +50,7 @@ describe("AppThemingSaga test", () => { widgetId: "widget1", updates: { modify: { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", buttonColor: "{{appsmith.theme.colors.primaryColor}}", }, }, @@ -97,7 +112,9 @@ describe("AppThemingSaga test", () => { widgetId: "widget1", updates: { modify: { - buttonColor: "{{appsmith.theme.colors.primaryColor}}", + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", "primaryColumns.customColumn1.buttonColor": "{{widget1.sanitizedTableData.map((currentRow) => ( appsmith.theme.colors.primaryColor))}}", }, @@ -157,6 +174,12 @@ describe("AppThemingSaga test", () => { boxShadow: "none", }, }, + resetButtonStyles: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + }, + submitButtonStyles: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + }, isLoading: false, parentColumnSpace: 1, parentRowSpace: 1, @@ -190,9 +213,17 @@ describe("AppThemingSaga test", () => { updates: { modify: { borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", - boxShadow: "{{appsmith.theme.borderRadius.appBorderRadius}}", - "schema.__root_schema__.children.name.accentColor": + "submitButtonStyles.borderRadius": + "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + "resetButtonStyles.borderRadius": + "{{appsmith.theme.borderRadius.appBorderRadius}}", + "schema.__root_schema__.borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "schema.__root_schema__.cellBorderRadius": + "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", + "schema.__root_schema__.children.name.accentColor": + "{{((sourceData, formData, fieldState) => (appsmith.theme.colors.primaryColor))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", "schema.__root_schema__.children.name.borderRadius": "{{((sourceData, formData, fieldState) => (appsmith.theme.borderRadius.appBorderRadius))(JSONForm1.sourceData, JSONForm1.formData, JSONForm1.fieldState)}}", }, diff --git a/app/client/src/entities/AppTheming/utils.ts b/app/client/src/entities/AppTheming/utils.ts index aaeb426377..633fad0cbf 100644 --- a/app/client/src/entities/AppTheming/utils.ts +++ b/app/client/src/entities/AppTheming/utils.ts @@ -5,19 +5,18 @@ import { isDynamicValue, THEME_BINDING_REGEX, } from "utils/DynamicBindingUtils"; -import { ROOT_SCHEMA_KEY } from "widgets/JSONFormWidget/constants"; +import WidgetFactory from "utils/WidgetFactory"; import { parseSchemaItem } from "widgets/WidgetUtils"; +import { ROOT_SCHEMA_KEY } from "widgets/JSONFormWidget/constants"; import { getFieldStylesheet } from "widgets/JSONFormWidget/helper"; -import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; -import { AppTheme } from "entities/AppTheming"; import { UpdateWidgetPropertyPayload } from "actions/controlActions"; +import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; /** * get properties to update for reset */ export const getPropertiesToUpdateForReset = ( canvasWidgets: CanvasWidgetsReduxState, - themeStylesheet: AppTheme["stylesheet"], ) => { const propertiesToUpdate: UpdateWidgetPropertyPayload[] = []; @@ -36,7 +35,9 @@ export const getPropertiesToUpdateForReset = ( // in stylesheet Object.keys(canvasWidgets).map((widgetId) => { const widget = canvasWidgets[widgetId]; - const stylesheetValue = themeStylesheet[widget.type]; + const stylesheetValue = WidgetFactory.getWidgetStylesheetConfigMap( + widget.type, + ); const modifications: any = {}; if (stylesheetValue) { @@ -82,18 +83,19 @@ export const getPropertiesToUpdateForReset = ( Object.keys(widget.groupButtons).map((groupButtonName: string) => { const groupButton = widget.groupButtons[groupButtonName]; - const childStylesheetValue = stylesheetValue.childStylesheet.button; + const childStylesheetValue = stylesheetValue?.childStylesheet?.button; - Object.keys(childStylesheetValue).map((childPropertyKey) => { - if ( - childStylesheetValue[childPropertyKey] !== - groupButton[childPropertyKey] - ) { - modifications[ - `groupButtons.${groupButtonName}.${childPropertyKey}` - ] = childStylesheetValue[childPropertyKey]; - } - }); + childStylesheetValue && + Object.keys(childStylesheetValue).map((childPropertyKey) => { + if ( + get(childStylesheetValue, childPropertyKey) !== + groupButton[childPropertyKey] + ) { + modifications[ + `groupButtons.${groupButtonName}.${childPropertyKey}` + ] = get(childStylesheetValue, childPropertyKey); + } + }); }); } @@ -106,7 +108,8 @@ export const getPropertiesToUpdateForReset = ( const fieldStylesheet = getFieldStylesheet( widget.widgetName, schemaItem.fieldType, - themeStylesheet[widget.type].childStylesheet as any, + (WidgetFactory.getWidgetStylesheetConfigMap(widget.type) || {}) + .childStylesheet as any, ); Object.keys(fieldStylesheet).map((fieldPropertyKey) => { @@ -126,26 +129,31 @@ export const getPropertiesToUpdateForReset = ( } // reset submit button - ["submitButtonStyles", "resetButtonStyles"].map((buttonStyleKey) => { - Object.keys(stylesheetValue[buttonStyleKey]).map((propertyKey) => { - const buttonStylesheetValue = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - stylesheetValue[buttonStyleKey][propertyKey]; + (["submitButtonStyles", "resetButtonStyles"] as const).map( + (buttonStyleKey) => { + Object.keys(get(stylesheetValue, buttonStyleKey, {})).map( + (propertyKey) => { + const buttonStylesheetValue = get( + stylesheetValue, + `${buttonStyleKey}.${propertyKey}`, + ) as string; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if ( - THEME_BINDING_REGEX.test(buttonStylesheetValue) && - buttonStylesheetValue !== widget[buttonStyleKey][propertyKey] && - buttonStylesheetValue !== widget[buttonStyleKey][propertyKey] - ) { - modifications[ - `${buttonStyleKey}.${propertyKey}` - ] = buttonStylesheetValue; - } - }); - }); + if ( + buttonStylesheetValue && + typeof buttonStylesheetValue === "string" && + THEME_BINDING_REGEX.test(buttonStylesheetValue) && + buttonStylesheetValue !== + widget[buttonStyleKey][propertyKey] && + buttonStylesheetValue !== widget[buttonStyleKey][propertyKey] + ) { + modifications[ + `${buttonStyleKey}.${propertyKey}` + ] = buttonStylesheetValue; + } + }, + ); + }, + ); } if (Object.keys(modifications).length > 0) { diff --git a/app/client/src/entities/Replay/ReplayEntity/ReplayCanvas.ts b/app/client/src/entities/Replay/ReplayEntity/ReplayCanvas.ts index fcac1a6062..0dec5f7a5b 100644 --- a/app/client/src/entities/Replay/ReplayEntity/ReplayCanvas.ts +++ b/app/client/src/entities/Replay/ReplayEntity/ReplayCanvas.ts @@ -34,6 +34,7 @@ const positionProps = [ "detachFromLayout", "noContainerOffset", "isCanvas", + "height", ]; /** diff --git a/app/client/src/entities/URLRedirect/URLAssembly.ts b/app/client/src/entities/URLRedirect/URLAssembly.ts index 0f96db5ec0..1b4a0242d1 100644 --- a/app/client/src/entities/URLRedirect/URLAssembly.ts +++ b/app/client/src/entities/URLRedirect/URLAssembly.ts @@ -47,11 +47,16 @@ export type PageURLParams = { customSlug?: string; }; -const fetchQueryParamsToPersist = () => { +const fetchQueryParamsToPersist = (persistExistingParams: boolean) => { const existingParams = getQueryParamsObject() || {}; // not persisting the entire query currently, since that's the current behavior const { branch, embed } = existingParams; - let params = { branch, embed } as any; + let params; + if (persistExistingParams) { + params = { ...existingParams }; + } else { + params = { branch, embed } as any; + } // test param to make sure a query param is present in the URL during dev and tests if ((window as any).Cypress) { params = { a: "b", ...params }; @@ -166,7 +171,13 @@ export class URLBuilder { * @returns URL string */ build(builderParams: URLBuilderParams, mode: APP_MODE = APP_MODE.EDIT) { - const { hash = "", params = {}, suffix, pageId } = builderParams; + const { + hash = "", + params = {}, + persistExistingParams = false, + suffix, + pageId, + } = builderParams; if (!pageId) { throw new URIError( @@ -176,7 +187,9 @@ export class URLBuilder { const basePath = this.generateBasePath(pageId, mode); - const queryParamsToPersist = fetchQueryParamsToPersist(); + const queryParamsToPersist = fetchQueryParamsToPersist( + persistExistingParams, + ); const modifiedQueryParams = { ...queryParamsToPersist, ...params }; diff --git a/app/client/src/navigation/FocusElements.ts b/app/client/src/navigation/FocusElements.ts index 8901798439..763e65daef 100644 --- a/app/client/src/navigation/FocusElements.ts +++ b/app/client/src/navigation/FocusElements.ts @@ -2,11 +2,13 @@ import { getApiPaneConfigSelectedTabIndex, getApiPaneResponsePaneHeight, getApiPaneResponseSelectedTab, + getApiRightPaneSelectedTab, } from "selectors/apiPaneSelectors"; import { setApiPaneResponseSelectedTab, setApiPaneConfigSelectedTabIndex, setApiPaneResponsePaneHeight, + setApiRightPaneSelectedTab, } from "actions/apiPaneActions"; import { AppState } from "@appsmith/reducers"; import { ReduxAction } from "@appsmith/constants/ReduxActionConstants"; @@ -55,6 +57,7 @@ export enum FocusElement { ApiPaneConfigTabs = "ApiPaneConfigTabs", ApiPaneResponseTabs = "ApiPaneResponseTabs", ApiPaneResponseHeight = "ApiPaneResponseHeight", + ApiRightPaneTabs = "ApiRightPaneTabs", QueryPaneConfigTabs = "QueryPaneConfigTabs", QueryPaneResponseTabs = "QueryPaneResponseTabs", QueryPaneResponseHeight = "QueryPaneResponseHeight", @@ -191,5 +194,10 @@ export const FocusElementsConfig: Record = { setter: setApiPaneResponsePaneHeight, defaultValue: ActionExecutionResizerHeight, }, + { + name: FocusElement.ApiRightPaneTabs, + selector: getApiRightPaneSelectedTab, + setter: setApiRightPaneSelectedTab, + }, ], }; diff --git a/app/client/src/pages/AppViewer/AppViewerHeader.tsx b/app/client/src/pages/AppViewer/AppViewerHeader.tsx index 758793717c..9f1ead7d59 100644 --- a/app/client/src/pages/AppViewer/AppViewerHeader.tsx +++ b/app/client/src/pages/AppViewer/AppViewerHeader.tsx @@ -148,6 +148,7 @@ export function AppViewerHeader(props: AppViewerHeaderProps) { } buttonVariant="SECONDARY" className="h-8" + data-cy="viewmode-share" text="Share" /> } diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index a770a6b90f..3ccf9c8b35 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -239,6 +239,11 @@ function AppViewer(props: Props) { [updateWidgetAutoHeightAction, dispatch], ); + const checkContainersForAutoHeightCallback = useCallback( + () => dispatch(checkContainersForAutoHeightAction()), + [checkContainersForAutoHeightAction], + ); + return ( { if (authType.length) info.push(authType); return info.join(" | "); }; - function ApiRightPane(props: any) { - const [selectedIndex, setSelectedIndex] = useState(0); + const dispatch = useDispatch(); const { entityDependencies, hasDependencies } = useEntityDependencies( props.actionName, ); + const selectedTab = useSelector(getApiRightPaneSelectedTab); + + const setSelectedTab = useCallback((selectedIndex: number) => { + dispatch(setApiRightPaneSelectedTab(selectedIndex)); + }, []); + useEffect(() => { - if (!!props.hasResponse) setSelectedIndex(1); + // Switch to connections tab only initially after successfully run get stored value + // otherwise + if (!!props.hasResponse && isUndefined(selectedTab)) setSelectedTab(1); }, [props.hasResponse]); // array of datasources with the current action's datasource first, followed by the rest. @@ -232,8 +243,8 @@ function ApiRightPane(props: any) { 0 ? ( {(sortedDatasources || []).map((d: any, idx: number) => { const dataSourceInfo: string = getDatasourceInfo(d); @@ -313,7 +324,7 @@ function ApiRightPane(props: any) { ), }, { - key: "Connections", + key: "connections", title: "Connections", panelComponent: ( diff --git a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx index 7e939e5d40..0bb9bcbf36 100644 --- a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx @@ -93,6 +93,7 @@ const Form = styled.form` `; const MainConfiguration = styled.div` + z-index: 7; padding: ${(props) => props.theme.spaces[4]}px ${(props) => props.theme.spaces[10]}px 0px ${(props) => props.theme.spaces[10]}px; diff --git a/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx b/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx index 804b3276e1..b64f148de6 100644 --- a/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx +++ b/app/client/src/pages/Editor/APIEditor/GraphQL/VariableEditor.tsx @@ -10,7 +10,7 @@ import styled from "styled-components"; import { Colors } from "constants/Colors"; import { Text, TextType } from "design-system"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; const VariableWrapper = styled.div` display: flex; diff --git a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx index 22b5b8d336..b8a8a3d214 100644 --- a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx +++ b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx @@ -21,7 +21,7 @@ import { import { Classes, MultiSwitch } from "design-system"; import { updateBodyContentType } from "actions/apiPaneActions"; import { CodeEditorExpected } from "components/editorComponents/CodeEditor"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { createMessage, API_PANE_NO_BODY } from "@appsmith/constants/messages"; const PostBodyContainer = styled.div` diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 25c6c87407..89a24fb0db 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -1,7 +1,7 @@ import log from "loglevel"; import * as Sentry from "@sentry/react"; import styled from "styled-components"; -import store, { useSelector } from "store"; +import store from "store"; import { CanvasWidgetStructure } from "widgets/constants"; import WidgetFactory from "utils/WidgetFactory"; import React, { memo, useCallback, useEffect } from "react"; @@ -12,7 +12,7 @@ import CanvasMultiPointerArena, { import { throttle } from "lodash"; import { RenderModes } from "constants/WidgetConstants"; import { isMultiplayerEnabledForUser as isMultiplayerEnabledForUserSelector } from "selectors/appCollabSelectors"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { initPageLevelSocketConnection } from "actions/websocketActions"; import { collabShareUserPointerEvent } from "actions/appCollabActions"; import { getIsPageLevelSocketConnected } from "selectors/websocketSelectors"; diff --git a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx index be202fa019..daf020eb41 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx @@ -547,6 +547,7 @@ class DatasourceRestAPIEditor extends React.Component< GrantType.AuthorizationCode && ( { if (!datasourceStructure && isOpen) { - dispatch(fetchDatasourceStructure(props.datasource.id)); + dispatch(fetchDatasourceStructure(props.datasource.id, true)); } dispatch(expandDatasourceEntity(isOpen ? props.datasource.id : "")); diff --git a/app/client/src/pages/Editor/Explorer/Widgets/utils.ts b/app/client/src/pages/Editor/Explorer/Widgets/utils.ts index be3e36e7c3..b74f8454ac 100644 --- a/app/client/src/pages/Editor/Explorer/Widgets/utils.ts +++ b/app/client/src/pages/Editor/Explorer/Widgets/utils.ts @@ -6,6 +6,7 @@ export const navigateToCanvas = (pageId: string, widgetId?: string) => { const canvasEditorURL = `${builderURL({ pageId, hash: widgetId, + persistExistingParams: true, })}`; if (currentPath !== canvasEditorURL) { history.push(canvasEditorURL); diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx index 126e2feffb..6378f3be0d 100644 --- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx +++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx @@ -2,8 +2,7 @@ import React from "react"; import { Icon } from "@blueprintjs/core"; import { Button, Category, Text, TextType } from "design-system"; import styled from "styled-components"; -import { useDispatch } from "react-redux"; -import { useSelector } from "store"; +import { useDispatch, useSelector } from "react-redux"; import { getCanvasWidgets, getDatasources, diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx index 80f820a000..54c52e2430 100644 --- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx +++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx @@ -22,7 +22,7 @@ import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { INTEGRATION_TABS } from "constants/routes"; import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants"; import React from "react"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { getCurrentApplicationId, @@ -34,7 +34,6 @@ import { getPageActions, } from "selectors/entitiesSelector"; import { getFirstTimeUserOnboardingModal } from "selectors/onboardingSelectors"; -import { useSelector } from "store"; import styled from "styled-components"; import AnalyticsUtil from "utils/AnalyticsUtil"; import history from "utils/history"; diff --git a/app/client/src/pages/Editor/GuidedTour/Boxed.tsx b/app/client/src/pages/Editor/GuidedTour/Boxed.tsx index 74974601c2..b5284381df 100644 --- a/app/client/src/pages/Editor/GuidedTour/Boxed.tsx +++ b/app/client/src/pages/Editor/GuidedTour/Boxed.tsx @@ -1,11 +1,11 @@ import React from "react"; import { ReactNode } from "react"; +import { useSelector } from "react-redux"; import { forceShowContentSelector, getCurrentStep, inGuidedTour, } from "selectors/onboardingSelectors"; -import { useSelector } from "store"; type BoxedProps = { alternative?: JSX.Element; diff --git a/app/client/src/pages/Editor/GuidedTour/DeviationModal.tsx b/app/client/src/pages/Editor/GuidedTour/DeviationModal.tsx index 6e7b4526e4..d32a4403fb 100644 --- a/app/client/src/pages/Editor/GuidedTour/DeviationModal.tsx +++ b/app/client/src/pages/Editor/GuidedTour/DeviationModal.tsx @@ -1,3 +1,4 @@ +import { useSelector } from "react-redux"; import { enableGuidedTour, toggleShowDeviationDialog, @@ -16,7 +17,6 @@ import { showDeviatingDialogSelector, showEndTourDialogSelector, } from "selectors/onboardingSelectors"; -import { useSelector } from "store"; import styled from "styled-components"; import AnalyticsUtil from "utils/AnalyticsUtil"; diff --git a/app/client/src/pages/Editor/MainContainerLayoutControl.tsx b/app/client/src/pages/Editor/MainContainerLayoutControl.tsx index 2da594286f..30b6df2f17 100644 --- a/app/client/src/pages/Editor/MainContainerLayoutControl.tsx +++ b/app/client/src/pages/Editor/MainContainerLayoutControl.tsx @@ -1,12 +1,11 @@ import classNames from "classnames"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import React, { useMemo, useCallback } from "react"; import { getCurrentApplicationId, getCurrentApplicationLayout, } from "selectors/editorSelectors"; -import { useSelector } from "store"; import { Colors } from "constants/Colors"; import { AppLayoutConfig, diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx index 25c13e963b..22566f0880 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx @@ -45,19 +45,18 @@ import { ENTITY_TYPE } from "entities/AppsmithConsole"; import LOG_TYPE from "entities/AppsmithConsole/logtype"; import { getExpectedValue } from "utils/validation/common"; import { ControlData } from "components/propertyControls/BaseControl"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; -import { getSelectedAppTheme } from "selectors/appThemingSelectors"; +import { AppState } from "@appsmith/reducers"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { TooltipComponent } from "design-system"; import { ReactComponent as ResetIcon } from "assets/icons/control/undo_2.svg"; -import { AppTheme } from "entities/AppTheming"; import { JS_TOGGLE_DISABLED_MESSAGE } from "@appsmith/constants/messages"; -import { AppState } from "@appsmith/reducers"; import { getPropertyControlFocusElement, shouldFocusOnPropertyControl, } from "utils/editorContextUtils"; import PropertyPaneHelperText from "./PropertyPaneHelperText"; import { generateKeyAndSetFocusablePropertyPaneField } from "actions/propertyPaneActions"; +import WidgetFactory from "utils/WidgetFactory"; type Props = PropertyPaneControlConfig & { panel: IPanelProps; @@ -88,16 +87,16 @@ const PropertyControl = memo((props: Props) => { // using hasDispatchedPropertyFocus to make sure // the component does not select the state after dispatching the action, // which might lead to another rerender and reset the component - let hasDispatchedPropertyFocus = false; + const hasDispatchedPropertyFocus = useRef(false); const shouldFocusPropertyPath: boolean = useSelector( (state: AppState) => getShouldFocusPropertyPath( state, dataTreePath, - hasDispatchedPropertyFocus, + hasDispatchedPropertyFocus.current, ), (before: boolean, after: boolean) => { - return hasDispatchedPropertyFocus || before === after; + return hasDispatchedPropertyFocus.current || before === after; }, ); @@ -110,8 +109,6 @@ const PropertyControl = memo((props: Props) => { equal, ); - const selectedTheme = useSelector(getSelectedAppTheme); - useEffect(() => { if (shouldFocusPropertyPath) { // We can get a code editor element as well, which will take time to load @@ -138,9 +135,8 @@ const PropertyControl = memo((props: Props) => { * theme config and thus it is fetched from there. */ const propertyStylesheetValue = (() => { - const widgetStylesheet: AppTheme["stylesheet"][string] = get( - selectedTheme, - `stylesheet.${widgetProperties.type}`, + const widgetStylesheet = WidgetFactory.getWidgetStylesheetConfigMap( + widgetProperties.type, ); if (props.getStylesheetValue) { @@ -256,10 +252,18 @@ const PropertyControl = memo((props: Props) => { propertyValue, ); } + if (propertiesToUpdate) { const allUpdates: Record = {}; const allDeletions: string[] = []; const allDynamicPropertyPathUpdate: DynamicPath[] = []; + // TODO(abhinav): DEBUG: Ask Rahul and Ashok, if this causes issues anywhere else. + + // We add the current updated first, so that the updatehooks can override the value + // This is needed for transformations in some cases. For example, + // the INPUT_TEXT control uses string as default, we can convert this into a number + // by calling an updateHook which runs the parseInt over this value. + allUpdates[propertyName] = propertyValue; propertiesToUpdate.forEach( ({ isDynamicPropertyPath, @@ -280,7 +284,6 @@ const PropertyControl = memo((props: Props) => { } }, ); - allUpdates[propertyName] = propertyValue; AppsmithConsole.info({ logType: LOG_TYPE.WIDGET_UPDATE, text: "Widget properties were updated", @@ -299,6 +302,7 @@ const PropertyControl = memo((props: Props) => { updates: { modify: allUpdates, remove: allDeletions, + postUpdateAction: props.postUpdateAction, }, dynamicUpdates: { dynamicPropertyPathList: allDynamicPropertyPathUpdate, @@ -322,10 +326,12 @@ const PropertyControl = memo((props: Props) => { [propertyName]: propertyValue, }, }); + return { widgetId: widgetProperties.widgetId, updates: { modify, + postUpdateAction: props.postUpdateAction, }, }; } @@ -341,8 +347,8 @@ const PropertyControl = memo((props: Props) => { // would recommend NOT TO FOLLOW this path for upcoming widgets. // if there are enhancements related to the widget, calling them here - // enhancements are basically group of functions that are called before widget propety - // is changed on propertypane. For e.g - set/update parent property + // enhancements are basically group of functions that are called before widget property + // is changed on propertyPane. For e.g - set/update parent property if (childWidgetPropertyUpdateEnhancementFn) { const hookPropertiesUpdates = childWidgetPropertyUpdateEnhancementFn( widgetProperties.widgetName, @@ -414,6 +420,7 @@ const PropertyControl = memo((props: Props) => { propertyName, propertyValue, ); + const enhancementsToOtherWidgets: UpdateWidgetPropertyPayload[] = getOtherWidgetPropertyChanges( propertyName, propertyValue, @@ -428,6 +435,7 @@ const PropertyControl = memo((props: Props) => { ); } } + if (allPropertiesToUpdates && allPropertiesToUpdates.length) { // updating properties of a widget(s) should be done only once when property value changes. // to make sure dsl updates are atomic which is a necessity for undo/redo. @@ -561,7 +569,7 @@ const PropertyControl = memo((props: Props) => { const handleOnFocus = () => { if (!shouldFocusPropertyPath) { - hasDispatchedPropertyFocus = true; + hasDispatchedPropertyFocus.current = true; setTimeout(() => { dispatch(generateKeyAndSetFocusablePropertyPaneField(dataTreePath)); }, 0); diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx index c1d8d627c1..272663d451 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyPaneView.tsx @@ -38,6 +38,7 @@ import WidgetFactory from "utils/WidgetFactory"; import styled from "styled-components"; import { PropertyPaneTab } from "./PropertyPaneTab"; import { useSearchText } from "./helpers"; +import { disableWidgetFeatures } from "utils/WidgetFeatures"; export const StyledSearchInput = React.memo(styled(SearchInput)` position: sticky; @@ -262,8 +263,9 @@ function PropertyPaneView( contentComponent={ isContentConfigAvailable ? ( { + dispatch(setQueryPaneResponsePaneHeight(height)); + }, []); + const responseBodyTabs = responseDataTypes && responseDataTypes.map((dataType, index) => { @@ -752,7 +757,7 @@ export function EditorJSONtoForm(props: Props) { panelComponent: responseTabComponent( dataType.key, output, - // tableBodyHeight, + responsePaneHeight, ), }; }); @@ -912,11 +917,6 @@ export function EditorJSONtoForm(props: Props) { dispatch(setQueryPaneResponseSelectedTab(tabKey)); }, []); - const responsePaneHeight = useSelector(getQueryPaneResponsePaneHeight); - const setResponsePaneHeight = useCallback((height: number) => { - dispatch(setQueryPaneResponsePaneHeight(height)); - }, []); - // when switching between different redux forms, make sure this redux form has been initialized before rendering anything. // the initialized prop below comes from redux-form. if (!props.initialized) { diff --git a/app/client/src/pages/Editor/QueryEditor/Table.tsx b/app/client/src/pages/Editor/QueryEditor/Table.tsx index aaec33b3c1..bd7da97fbb 100644 --- a/app/client/src/pages/Editor/QueryEditor/Table.tsx +++ b/app/client/src/pages/Editor/QueryEditor/Table.tsx @@ -241,12 +241,15 @@ function Table(props: TableProps) { return []; }, [data]); + const responseTypePanelHeight = 24; + const tableBodyHeightComputed = (props.tableBodyHeight || window.innerHeight) - TABLE_SIZES.COLUMN_HEADER_HEIGHT - props.theme.tabPanelHeight - TABLE_SIZES.SCROLL_SIZE - - 2 * props.theme.spaces[5]; //top and bottom padding + responseTypePanelHeight - + 2 * props.theme.spaces[4]; //top and bottom padding const defaultColumn = React.useMemo( () => ({ diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx index 90420cc86f..e02a1beebe 100644 --- a/app/client/src/pages/Editor/QueryEditor/index.tsx +++ b/app/client/src/pages/Editor/QueryEditor/index.tsx @@ -33,7 +33,10 @@ import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants"; import { QueryAction, SaaSAction } from "entities/Action"; import Spinner from "components/editorComponents/Spinner"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; -import { changeQuery } from "actions/queryPaneActions"; +import { + changeQuery, + setQueryPaneResponsePaneHeight, +} from "actions/queryPaneActions"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; @@ -46,6 +49,7 @@ import { integrationEditorURL } from "RouteBuilder"; import { getConfigInitialValues } from "components/formControls/utils"; import { merge } from "lodash"; import { getPathAndValueFromActionDiffObject } from "../../../utils/getPathAndValueFromActionDiffObject"; +import { ActionExecutionResizerHeight } from "../APIEditor/constants"; const EmptyStateContainer = styled.div` display: flex; @@ -76,6 +80,7 @@ type ReduxDispatchProps = { propertyName: string, value: string, ) => void; + setQueryPaneResponsePaneHeight: (height: number) => void; }; type ReduxStateProps = { @@ -154,6 +159,9 @@ class QueryEditor extends React.Component { dataSourceSize: dataSources.length, }); this.props.runAction(this.props.actionId); + + // reset response pane height back to original + this.props.setQueryPaneResponsePaneHeight(ActionExecutionResizerHeight); }; componentDidUpdate(prevProps: Props) { @@ -356,6 +364,9 @@ const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({ ) => { dispatch(setActionProperty({ actionId, propertyName, value })); }, + setQueryPaneResponsePaneHeight: (height) => { + dispatch(setQueryPaneResponsePaneHeight(height)); + }, }); export default connect(mapStateToProps, mapDispatchToProps)(QueryEditor); diff --git a/app/client/src/pages/Editor/WidgetsEditor/index.tsx b/app/client/src/pages/Editor/WidgetsEditor/index.tsx index ed6308bcd8..fb91b8c1d6 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/index.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/index.tsx @@ -29,6 +29,7 @@ import EditorContextProvider from "components/editorComponents/EditorContextProv import Guide from "../GuidedTour/Guide"; import PropertyPaneContainer from "./PropertyPaneContainer"; import CanvasTopSection from "./EmptyCanvasSection"; +import { useAutoHeightUIState } from "utils/hooks/autoHeightUIHooks"; /* eslint-disable react/display-name */ function WidgetsEditor() { @@ -71,16 +72,24 @@ function WidgetsEditor() { }, [isFetchingPage, selectWidget, guidedTourEnabled]); const allowDragToSelect = useAllowEditorDragToSelect(); + const { isAutoHeightWithLimitsChanging } = useAutoHeightUIState(); const handleWrapperClick = useCallback(() => { - if (allowDragToSelect) { + // Making sure that we don't deselect the widget + // after we are done dragging the limits in auto height with limits + if (allowDragToSelect && !isAutoHeightWithLimitsChanging) { focusWidget && focusWidget(); deselectAll && deselectAll(); dispatch(closePropertyPane()); dispatch(closeTableFilterPane()); dispatch(setCanvasSelectionFromEditor(false)); } - }, [allowDragToSelect, focusWidget, deselectAll]); + }, [ + allowDragToSelect, + focusWidget, + deselectAll, + isAutoHeightWithLimitsChanging, + ]); /** * drag event handler for selection drawing diff --git a/app/client/src/pages/Editor/__tests__/QueryEditorTable.test.tsx b/app/client/src/pages/Editor/__tests__/QueryEditorTable.test.tsx index fb3671f277..fe41147213 100644 --- a/app/client/src/pages/Editor/__tests__/QueryEditorTable.test.tsx +++ b/app/client/src/pages/Editor/__tests__/QueryEditorTable.test.tsx @@ -12,6 +12,20 @@ function createEle() { } const scrollW = 6; +const reponsePaneHeight = 307; + +const tableData = [ + { "": "Jan 1 1970 10:15AM" }, + { "": "Jan 2 1970 10:15AM" }, + { "": "Jan 3 1970 10:15AM" }, + { "": "Jan 4 1970 10:15AM" }, + { "": "Jan 5 1970 10:15AM" }, + { "": "Jan 1 1970 10:15AM" }, + { "": "Jan 2 1970 10:15AM" }, + { "": "Jan 3 1970 10:15AM" }, + { "": "Jan 4 1970 10:15AM" }, + { "": "Jan 5 1970 10:15AM" }, +]; describe("Query Editor Table", () => { it("it should render table with missing key", () => { @@ -43,4 +57,10 @@ describe("Query Editor Table", () => { const scrollWidth = getScrollBarWidth(ele, scrollW); expect(scrollWidth).toBeGreaterThan(0); }); + + it("17653: Scroll bar in table doesnt appear", () => { + render(); + const tbodyEle = document.querySelectorAll(".tbody > div"); + expect(tbodyEle[0]).toHaveStyle("height: 171px"); + }); }); diff --git a/app/client/src/pages/Editor/gitSync/components/DeployPreview.tsx b/app/client/src/pages/Editor/gitSync/components/DeployPreview.tsx index 5443bcd6c8..4340100cd7 100644 --- a/app/client/src/pages/Editor/gitSync/components/DeployPreview.tsx +++ b/app/client/src/pages/Editor/gitSync/components/DeployPreview.tsx @@ -3,7 +3,7 @@ import React from "react"; import styled from "styled-components"; import { ReactComponent as CloudyIcon } from "assets/icons/ads/cloudy-line.svg"; import { ReactComponent as RightArrow } from "assets/icons/ads/arrow-right-line.svg"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { getCurrentPageId, getApplicationLastDeployedAt, diff --git a/app/client/src/pages/Editor/gitSync/components/GitConnectError.tsx b/app/client/src/pages/Editor/gitSync/components/GitConnectError.tsx index 7b507ea5b1..35001ecb2f 100644 --- a/app/client/src/pages/Editor/gitSync/components/GitConnectError.tsx +++ b/app/client/src/pages/Editor/gitSync/components/GitConnectError.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; +import { useSelector } from "react-redux"; import styled from "constants/DefaultTheme"; -import { useSelector } from "store"; import { getConnectingErrorDocUrl, getGitConnectError, diff --git a/app/client/src/pages/Settings/FormGroup/Accordion.test.tsx b/app/client/src/pages/Settings/FormGroup/Accordion.test.tsx index b6841c4131..a8831781cb 100644 --- a/app/client/src/pages/Settings/FormGroup/Accordion.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Accordion.test.tsx @@ -3,13 +3,14 @@ import React from "react"; import { SettingTypes, SettingSubtype, + Setting, } from "@appsmith/pages/AdminSettings/config/types"; import Accordion from "./Accordion"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import { reduxForm } from "redux-form"; let container: any = null; -const setting = { +const setting: Setting = { id: "SETTING_TOGGLE_ID", name: "SETTING_TOGGLE_ID", category: "test category", @@ -53,7 +54,8 @@ function renderComponent() { form: { [SETTINGS_FORM_NAME]: { values: { - [setting.advanced[0].id]: false, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + [setting.advanced![0].id]: false, }, }, }, diff --git a/app/client/src/pages/Settings/FormGroup/Button.test.tsx b/app/client/src/pages/Settings/FormGroup/Button.test.tsx index fc4048b78a..4d186ca579 100644 --- a/app/client/src/pages/Settings/FormGroup/Button.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Button.test.tsx @@ -1,12 +1,15 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import ButtonComponent from "./Button"; let container: any = null; const buttonClickHandler = jest.fn(); const buttonIsDisabled = jest.fn(); -const setting = { +const setting: Setting = { id: "SETTING_ID", text: "download", action: buttonClickHandler, @@ -15,19 +18,12 @@ const setting = { isDisabled: buttonIsDisabled, }; const dispatch = jest.fn(); +const settings = {}; jest.mock("react-redux", () => { const originalModule = jest.requireActual("react-redux"); return { ...originalModule, useDispatch: () => dispatch, - }; -}); - -const settings = {}; -jest.mock("store", () => { - const store = jest.requireActual("store").default; - return { - ...store, useSelector: () => settings, }; }); diff --git a/app/client/src/pages/Settings/FormGroup/Button.tsx b/app/client/src/pages/Settings/FormGroup/Button.tsx index 971fa97d86..f06656ffa9 100644 --- a/app/client/src/pages/Settings/FormGroup/Button.tsx +++ b/app/client/src/pages/Settings/FormGroup/Button.tsx @@ -1,9 +1,8 @@ import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import React from "react"; import { Button, Category } from "design-system"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { getFormValues } from "redux-form"; -import { useSelector } from "store"; import styled from "styled-components"; import { FormGroup, SettingComponentProps } from "./Common"; diff --git a/app/client/src/pages/Settings/FormGroup/Group.test.tsx b/app/client/src/pages/Settings/FormGroup/Group.test.tsx index de322ca2f1..4db64f5736 100644 --- a/app/client/src/pages/Settings/FormGroup/Group.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Group.test.tsx @@ -1,12 +1,15 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import Group from "./group"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import { reduxForm } from "redux-form"; let container: any = null; -const settings = [ +const settings: Setting[] = [ { id: "test", name: "test", diff --git a/app/client/src/pages/Settings/FormGroup/Link.test.tsx b/app/client/src/pages/Settings/FormGroup/Link.test.tsx index d63b2b2018..c1cf740dc8 100644 --- a/app/client/src/pages/Settings/FormGroup/Link.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Link.test.tsx @@ -1,11 +1,14 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import Link from "./Link"; let container: any = null; const linkClickHandler = jest.fn(); -const setting = { +const setting: Setting = { id: "SETTING_ID", isHidden: false, label: "setting label", diff --git a/app/client/src/pages/Settings/FormGroup/Radio.test.tsx b/app/client/src/pages/Settings/FormGroup/Radio.test.tsx new file mode 100644 index 0000000000..08eee1dba7 --- /dev/null +++ b/app/client/src/pages/Settings/FormGroup/Radio.test.tsx @@ -0,0 +1,80 @@ +import { render } from "test/testUtils"; +import React from "react"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; +import Radio from "./Radio"; +import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; +import { reduxForm } from "redux-form"; + +let container: any = null; +const setting: Setting = { + id: "SETTING_RADIO", + name: "SETTING_RADIO", + category: "test category", + controlType: SettingTypes.RADIO, + label: "test label", + controlTypeProps: { + options: [ + { + label: "Label one", + value: "ONE", + }, + { + label: "Label two", + value: "TWO", + }, + ], + }, + format: (value) => { + return { value }; + }, + parse: (value) => { + return value.value; + }, +}; + +function renderComponent() { + function RadioFieldComponent() { + return ; + } + const Parent = reduxForm({ + validate: () => { + return {}; + }, + form: SETTINGS_FORM_NAME, + touchOnBlur: true, + })(RadioFieldComponent); + + render(, { + initialState: { + form: { + [SETTINGS_FORM_NAME]: { + values: { + [setting.id]: "TWO", + }, + }, + }, + }, + }); +} + +describe("Radio", () => { + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + }); + + it("is rendered", () => { + renderComponent(); + const radioOptions: NodeListOf = document.querySelectorAll( + "input[type=radio]", + ); + const numberOfCheckboxes = radioOptions.length; + expect(numberOfCheckboxes).toEqual( + setting.controlTypeProps?.options.length, + ); + expect(radioOptions[1].checked).toBeTruthy(); + }); +}); diff --git a/app/client/src/pages/Settings/FormGroup/Radio.tsx b/app/client/src/pages/Settings/FormGroup/Radio.tsx new file mode 100644 index 0000000000..ebfc31f8ec --- /dev/null +++ b/app/client/src/pages/Settings/FormGroup/Radio.tsx @@ -0,0 +1,216 @@ +import React, { ReactElement } from "react"; +import { + IconWrapper, + OptionProps, + Radio, + Text, + TextType, + Button, + Size, + IconSize, +} from "design-system"; +import { Popover2 } from "@blueprintjs/popover2"; +import { FormGroup, SettingComponentProps } from "./Common"; +import { + Field, + WrappedFieldInputProps, + WrappedFieldMetaProps, +} from "redux-form"; +import { FieldError } from "design-system"; +import { Colors } from "constants/Colors"; +import styled from "styled-components"; +import { Position } from "@blueprintjs/core"; + +type RadioOption = { + node?: ReactElement; + nodeLabel?: string; + nodeInputPath?: string; + nodeParentClass?: string; + badge?: string; + tooltip?: { + icon: any; + text: string; + linkText: string; + link: string; + }; +} & OptionProps; +export type RadioProps = { + options: RadioOption[]; +}; + +const Badge = styled(Text)<{ selected?: boolean }>` + background-color: ${(props) => + props.selected ? Colors.WARNING_ORANGE : Colors.SEA_SHELL}; + padding: 1.5px 5px; + margin-left: 4px; +`; + +const TooltipContent = styled.div` + width: 300px; + padding: 12px; + + a { + justify-content: flex-start; + padding: 0; + margin-top: 4px; + text-decoration: underline; + } + + .tooltip-text { + line-height: 1.17; + } +`; + +const RadioWrapper = styled.div<{ index: number }>` + ${(props) => + props.index > 0 && + ` + margin-top: 12.5px; + `} + + .icon { + margin-left: 4px; + } +`; + +const SuffixWrapper = styled.div` + display: inline-flex; + align-items: center; +`; + +const NodeWrapper = styled.div` + margin-left: 27px; + margin-top: 8px; +`; + +type RadioGroupProps = SettingComponentProps; + +function RadioFieldWrapper( + componentProps: { + meta: Partial; + input: Partial; + } & RadioProps, +) { + function onChangeHandler(e?: any) { + componentProps.input.onChange && + componentProps.input.onChange({ + value: e.target.value, + additionalData: componentProps.input.value.additionalData, + }); + } + + function onInputNodeChangeHandler(value?: any) { + componentProps.input.onChange && + componentProps.input.onChange({ + value: componentProps.input.value.value, + additionalData: value, + }); + componentProps.input.onBlur && + componentProps.input.onBlur({ + value: componentProps.input.value.value, + additionalData: value, + }); + } + + return ( +
+ {componentProps.options.map((item, index) => { + const isSelected = componentProps.input.value.value === item.value; + + return ( + + + {item.label} + + + + {item.badge && ( + + {item.badge} + + )} + {item.tooltip && ( + + + {item.tooltip.text} + +
+ ); +} + +export default function RadioField({ setting }: RadioGroupProps) { + const controlTypeProps = setting.controlTypeProps as RadioProps; + + return ( + + + + ); +} diff --git a/app/client/src/pages/Settings/FormGroup/TagInputField.test.tsx b/app/client/src/pages/Settings/FormGroup/TagInputField.test.tsx index 37daf2d70b..b8a134bcce 100644 --- a/app/client/src/pages/Settings/FormGroup/TagInputField.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/TagInputField.test.tsx @@ -1,12 +1,15 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import TagInputField from "./TagInputField"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import { reduxForm } from "redux-form"; let container: any = null; -const setting = { +const setting: Setting = { id: "SETTING_TAG_INPUT_ID", name: "SETTING_TAG_INPUT_ID", category: "test category", diff --git a/app/client/src/pages/Settings/FormGroup/TagInputField.tsx b/app/client/src/pages/Settings/FormGroup/TagInputField.tsx index 4e4f61a916..5e29c04b96 100644 --- a/app/client/src/pages/Settings/FormGroup/TagInputField.tsx +++ b/app/client/src/pages/Settings/FormGroup/TagInputField.tsx @@ -18,7 +18,7 @@ const renderComponent = ( const setting = componentProps.setting; return ( diff --git a/app/client/src/pages/Settings/FormGroup/Text.test.tsx b/app/client/src/pages/Settings/FormGroup/Text.test.tsx index c747cb1b0c..bf30469bd1 100644 --- a/app/client/src/pages/Settings/FormGroup/Text.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Text.test.tsx @@ -1,11 +1,14 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import TextComponent from "./Text"; let container: any = null; const buttonClickHandler = jest.fn(); -const setting = { +const setting: Setting = { id: "SETTING_ID", name: "textType", text: "download", diff --git a/app/client/src/pages/Settings/FormGroup/Text.tsx b/app/client/src/pages/Settings/FormGroup/Text.tsx index 2bad64f91f..019c2a3756 100644 --- a/app/client/src/pages/Settings/FormGroup/Text.tsx +++ b/app/client/src/pages/Settings/FormGroup/Text.tsx @@ -1,7 +1,7 @@ import { Text, TextType } from "design-system"; import React from "react"; import { getSettings } from "selectors/settingsSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import styled from "styled-components"; import { FormGroup, SettingComponentProps } from "./Common"; diff --git a/app/client/src/pages/Settings/FormGroup/TextInput.test.tsx b/app/client/src/pages/Settings/FormGroup/TextInput.test.tsx index bb5bd5e5b8..df5730692a 100644 --- a/app/client/src/pages/Settings/FormGroup/TextInput.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/TextInput.test.tsx @@ -3,13 +3,14 @@ import React from "react"; import { SettingTypes, SettingSubtype, + Setting, } from "@appsmith/pages/AdminSettings/config/types"; import TextInput from "./TextInput"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import { reduxForm } from "redux-form"; let container: any = null; -const setting = { +const setting: Setting = { id: "SETTING_TEXT_INPUT_ID", name: "SETTING_TEXT_INPUT_ID", category: "test category", diff --git a/app/client/src/pages/Settings/FormGroup/Toggle.test.tsx b/app/client/src/pages/Settings/FormGroup/Toggle.test.tsx index 78df982e6f..e096c845ac 100644 --- a/app/client/src/pages/Settings/FormGroup/Toggle.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/Toggle.test.tsx @@ -1,12 +1,15 @@ import { render } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import Toggle from "./Toggle"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; import { reduxForm } from "redux-form"; let container: any = null; -const setting = { +const setting: Setting = { id: "SETTING_TOGGLE_ID", name: "SETTING_TOGGLE_ID", category: "test category", diff --git a/app/client/src/pages/Settings/FormGroup/common.test.tsx b/app/client/src/pages/Settings/FormGroup/common.test.tsx index 424a99f0c8..bddfe2c162 100644 --- a/app/client/src/pages/Settings/FormGroup/common.test.tsx +++ b/app/client/src/pages/Settings/FormGroup/common.test.tsx @@ -1,10 +1,13 @@ import { render, screen } from "test/testUtils"; import React from "react"; -import { SettingTypes } from "@appsmith/pages/AdminSettings/config/types"; +import { + Setting, + SettingTypes, +} from "@appsmith/pages/AdminSettings/config/types"; import { FormGroup } from "./Common"; let container: any = null; -const setting = { +const setting: Setting = { id: "SETTING_ID", label: "formGroup", helpText: "", diff --git a/app/client/src/pages/Settings/FormGroup/group.tsx b/app/client/src/pages/Settings/FormGroup/group.tsx index 2a8549e432..90bf26448b 100644 --- a/app/client/src/pages/Settings/FormGroup/group.tsx +++ b/app/client/src/pages/Settings/FormGroup/group.tsx @@ -11,7 +11,7 @@ import Text from "./Text"; import Button from "./Button"; import { getFormValues } from "redux-form"; import { SETTINGS_FORM_NAME } from "@appsmith/constants/forms"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { createMessage, REDIRECT_URL_TOOLTIP, @@ -24,7 +24,9 @@ import Dropdown from "./Dropdown"; import { Classes } from "@blueprintjs/core"; import { Colors } from "constants/Colors"; import Checkbox from "./Checkbox"; +import Radio from "./Radio"; import { useDispatch } from "react-redux"; +import { getTypographyByKey } from "constants/DefaultTheme"; type GroupProps = { name?: string; @@ -58,29 +60,29 @@ const GroupBody = styled.div` margin-top: 0px; } } - & .tag-input { - .t--admin-settings-tag-input { - > div { - margin: 0; - .${Classes.TAG_INPUT}, .${Classes.TAG_INPUT}.${Classes.ACTIVE} { - border: 1.2px solid var(--appsmith-color-black-250); - box-shadow: none; - .bp3-tag { - background: var(--appsmith-color-black-50); - color: ${Colors.BLACK}; - svg:hover { - cursor: pointer; - path { - fill: currentColor; - } - } - } - } - .${Classes.TAG_INPUT}.${Classes.ACTIVE} { - border: 1.2px solid var(--appsmith-input-focus-border-color); - } + &&&& { + // TagInput in design system has a right margin + .tag-input > div { + margin: 0; + } + + .tag-input .${Classes.TAG_INPUT} { + box-shadow: none; + } + + .tag-input .${Classes.TAG} { + color: ${Colors.GRAY_700}; + background-color: ${Colors.GRAY_200}; + ${(props) => getTypographyByKey(props, "h5")} + // Cursor on close icon need to be a pointer + svg:hover { + cursor: pointer; } } + + .tag-input .${Classes.TAG_INPUT}.${Classes.ACTIVE} { + border: 1.2px solid var(--appsmith-color-black-900); + } } `; @@ -110,6 +112,16 @@ export default function Group({ return null; } switch (setting.controlType) { + case SettingTypes.RADIO: + return ( +
+ +
+ ); case SettingTypes.TEXTINPUT: return (
diff --git a/app/client/src/reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer.ts b/app/client/src/reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer.ts index 12ad50bbd1..443c30bab8 100644 --- a/app/client/src/reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer.ts +++ b/app/client/src/reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer.ts @@ -22,7 +22,7 @@ const autoHeightLayoutTreeReducer = createImmerReducer(initialState, { action: ReduxAction, ) => { const { tree } = action.payload; - const diff = xor(Object.keys(state), ...Object.keys(tree)); + const diff = xor(Object.keys(state), [...Object.keys(tree)]); for (const widgetId in diff) { delete state[widgetId]; } @@ -46,6 +46,8 @@ const autoHeightLayoutTreeReducer = createImmerReducer(initialState, { state[widgetId].topRow = tree[widgetId].topRow; state[widgetId].bottomRow = tree[widgetId].bottomRow; + state[widgetId].originalTopRow = tree[widgetId].originalTopRow; + state[widgetId].originalBottomRow = tree[widgetId].originalBottomRow; } else { state[widgetId] = tree[widgetId]; } diff --git a/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.test.ts b/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.test.ts index 8fa02fed19..ceb8524515 100644 --- a/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.test.ts +++ b/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.test.ts @@ -7,7 +7,7 @@ describe("Canvas Levels Reducer", () => { }); it("should set the new canvas mappings to the state", () => { - const type = ReduxActionTypes.SET_CANVAS_LEVELS_MAP; + const type = ReduxActionTypes.SET_AUTO_HEIGHT_LAYOUT_TREE; const payload = { canvasLevelMap: { 0: 0, @@ -21,7 +21,7 @@ describe("Canvas Levels Reducer", () => { }); it("should augment the further canvas mappings to the state", () => { - const type = ReduxActionTypes.SET_CANVAS_LEVELS_MAP; + const type = ReduxActionTypes.SET_AUTO_HEIGHT_LAYOUT_TREE; const payload = { canvasLevelMap: { 0: 0, diff --git a/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.ts b/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.ts index 529a11760e..ff9f7755e7 100644 --- a/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.ts +++ b/app/client/src/reducers/entityReducers/autoHeightReducers/canvasLevelsReducer.ts @@ -14,7 +14,7 @@ export type CanvasLevelsReduxState = { const initialState: CanvasLevelsReduxState = {}; const canvasLevelsReducer = createImmerReducer(initialState, { - [ReduxActionTypes.SET_CANVAS_LEVELS_MAP]: ( + [ReduxActionTypes.SET_AUTO_HEIGHT_LAYOUT_TREE]: ( state: CanvasLevelsReduxState, action: ReduxAction, ) => { diff --git a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts similarity index 99% rename from app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx rename to app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts index ec5313c26e..b23d823c35 100644 --- a/app/client/src/reducers/entityReducers/canvasWidgetsReducer.tsx +++ b/app/client/src/reducers/entityReducers/canvasWidgetsReducer.ts @@ -5,10 +5,8 @@ import { ReduxAction, } from "@appsmith/constants/ReduxActionConstants"; import { WidgetProps } from "widgets/BaseWidget"; -import { Diff, diff } from "deep-diff"; import { uniq, get, set } from "lodash"; - -const initialState: CanvasWidgetsReduxState = {}; +import { Diff, diff } from "deep-diff"; /* This type is an object whose keys are widgetIds and values are arrays with property paths and property values @@ -23,6 +21,8 @@ export type UpdateWidgetsPayload = Record< }> >; +const initialState: CanvasWidgetsReduxState = {}; + export type FlattenedWidgetProps = | (WidgetProps & { children?: string[]; @@ -103,6 +103,7 @@ const canvasWidgetsReducer = createImmerReducer(initialState, { } }, }); + export interface CanvasWidgetsReduxState { [widgetId: string]: FlattenedWidgetProps; } diff --git a/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx b/app/client/src/reducers/entityReducers/widgetConfigReducer.ts similarity index 90% rename from app/client/src/reducers/entityReducers/widgetConfigReducer.tsx rename to app/client/src/reducers/entityReducers/widgetConfigReducer.ts index 4835967aa9..0dabf52118 100644 --- a/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx +++ b/app/client/src/reducers/entityReducers/widgetConfigReducer.ts @@ -4,6 +4,7 @@ import { ReduxAction, } from "@appsmith/constants/ReduxActionConstants"; import { WidgetProps } from "widgets/BaseWidget"; +import { WidgetFeatures } from "utils/WidgetFeatures"; const initialState: WidgetConfigReducerState = { config: {} }; @@ -34,6 +35,8 @@ export interface WidgetConfig key: string; isCanvas?: boolean; needsMeta?: boolean; + canvasHeightOffset?: (props: WidgetProps) => number; + features?: WidgetFeatures; } export interface WidgetConfigReducerState { diff --git a/app/client/src/reducers/uiReducers/apiPaneReducer.ts b/app/client/src/reducers/uiReducers/apiPaneReducer.ts index a6adc12e95..dd9d10a9e0 100644 --- a/app/client/src/reducers/uiReducers/apiPaneReducer.ts +++ b/app/client/src/reducers/uiReducers/apiPaneReducer.ts @@ -34,6 +34,7 @@ export interface ApiPaneReduxState { selectedConfigTabIndex: number; selectedResponseTab: string; responseTabHeight: number; + selectedRightPaneTab?: number; } const apiPaneReducer = createReducer(initialState, { @@ -245,6 +246,16 @@ const apiPaneReducer = createReducer(initialState, { responseTabHeight: height, }; }, + [ReduxActionTypes.SET_API_RIGHT_PANE_SELECTED_TAB]: ( + state: ApiPaneReduxState, + action: ReduxAction<{ selectedTab: number }>, + ) => { + const { selectedTab } = action.payload; + return { + ...state, + selectedRightPaneTab: selectedTab, + }; + }, }); export default apiPaneReducer; diff --git a/app/client/src/reducers/uiReducers/autoHeightReducer.ts b/app/client/src/reducers/uiReducers/autoHeightReducer.ts new file mode 100644 index 0000000000..75593a0e71 --- /dev/null +++ b/app/client/src/reducers/uiReducers/autoHeightReducer.ts @@ -0,0 +1,29 @@ +import { createImmerReducer } from "utils/ReducerUtils"; +import { + ReduxAction, + ReduxActionTypes, +} from "@appsmith/constants/ReduxActionConstants"; + +export type AutoHeightUIStatePayload = { + isAutoHeightWithLimitsChanging: boolean; +}; + +export type AutoHeightUIState = { + isAutoHeightWithLimitsChanging: boolean; +}; + +const initialState: AutoHeightUIState = { + isAutoHeightWithLimitsChanging: false, +}; + +const autoHeightUIReducer = createImmerReducer(initialState, { + [ReduxActionTypes.SET_AUTO_HEIGHT_WITH_LIMITS_CHANGING]: ( + state: AutoHeightUIState, + action: ReduxAction, + ) => { + state.isAutoHeightWithLimitsChanging = + action.payload.isAutoHeightWithLimitsChanging; + }, +}); + +export default autoHeightUIReducer; diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx index 4586956689..ba16838eaa 100644 --- a/app/client/src/reducers/uiReducers/index.tsx +++ b/app/client/src/reducers/uiReducers/index.tsx @@ -42,6 +42,7 @@ import mainCanvasReducer from "./mainCanvasReducer"; import focusHistoryReducer from "./focusHistoryReducer"; import { editorContextReducer } from "./editorContextReducer"; import guidedTourReducer from "./guidedTourReducer"; +import autoHeightUIReducer from "./autoHeightReducer"; const uiReducer = combineReducers({ editor: editorReducer, @@ -87,6 +88,7 @@ const uiReducer = combineReducers({ mainCanvas: mainCanvasReducer, focusHistory: focusHistoryReducer, editorContext: editorContextReducer, + autoHeightUI: autoHeightUIReducer, }); export default uiReducer; diff --git a/app/client/src/reducers/uiReducers/mainCanvasReducer.ts b/app/client/src/reducers/uiReducers/mainCanvasReducer.ts index 73e45944ca..475575e121 100644 --- a/app/client/src/reducers/uiReducers/mainCanvasReducer.ts +++ b/app/client/src/reducers/uiReducers/mainCanvasReducer.ts @@ -30,7 +30,6 @@ const mainCanvasReducer = createImmerReducer(initialState, { action: ReduxAction, ) => { state.width = action.payload.width || state.width; - state.height = action.payload.height || state.height; state.initialized = true; }, }); diff --git a/app/client/src/reflow/reflowHelpers.ts b/app/client/src/reflow/reflowHelpers.ts index 728c3d83ba..07a27d2987 100644 --- a/app/client/src/reflow/reflowHelpers.ts +++ b/app/client/src/reflow/reflowHelpers.ts @@ -114,7 +114,7 @@ export function getMovementMap( childNode[directionalAccessors.oppositeDirection] - childNode.collidingValue; - const { depth, occupiedSpace } = getMovementMapHelper( + const { occupiedLength, occupiedSpace } = getMovementMapHelper( childNode, movementMap, delta, @@ -130,27 +130,27 @@ export function getMovementMap( globalProcessedNodes, ); - let staticDepth = 0, + let staticOccupiedLength = 0, maxOccupiedSpace = 0; if (!directionalVariables[childNode.collidingId]) { directionalVariables[childNode.collidingId] = {}; } if (directionalVariables[childNode.collidingId][childDirection]) { - [staticDepth, maxOccupiedSpace] = directionalVariables[ + [staticOccupiedLength, maxOccupiedSpace] = directionalVariables[ childNode.collidingId ][childDirection]; } - staticDepth = Math.max(staticDepth, depth); + staticOccupiedLength = Math.max(staticOccupiedLength, occupiedLength); maxOccupiedSpace = Math.max(maxOccupiedSpace, occupiedSpace); directionalVariables[childNode.collidingId][childDirection] = [ - staticDepth, + staticOccupiedLength, maxOccupiedSpace, directionalAccessors, childDirection, ]; } - //based on the movement values and the depth of the dragging space from the borders, movement limits are calculated + //based on the movement values and the occupiedLength of the dragging space from the borders, movement limits are calculated const movementVariablesMap = getMovementVariables( newSpacePositionsMap, directionalVariables, @@ -236,7 +236,7 @@ export function getCollisionTree( // this method recursively builds the tree structure const { collisionTree: currentCollisionTree, - depth, + occupiedLength, } = getCollisionTreeHelper( newSpacePositions, currentOccSpaces, @@ -263,7 +263,7 @@ export function getCollisionTree( currentCollidingSpace.collidingValue, currentDirection, gridProps, - depth, + occupiedLength, ); if (currentCollisionTree) { collisionTrees.push({ @@ -342,7 +342,7 @@ function getCollisionTreeHelper( secondOrderCollisionMap?: SecondOrderCollisionMap, ) { if (!collidingSpace) return {}; - let depth = 0; + let occupiedLength = 0; const collisionTree: CollisionTree = { ...collidingSpace, children: {} }; // we resize the space to either increase the width or height based on movement @@ -432,7 +432,7 @@ function getCollisionTreeHelper( //Recursively call to build the tree const { collisionTree: currentCollisionTree, - depth: currentDepth, + occupiedLength: currentOccupiedLength, } = getCollisionTreeHelper( filteredNewSpacePositions, currentOccSpaces, @@ -466,7 +466,7 @@ function getCollisionTreeHelper( modifiedCollidingSpace.collidingValue, currentDirection, gridProps, - currentDepth, + currentOccupiedLength, ); //add value to cache globalProcessedNodes[modifiedCollidingSpace.id][ @@ -484,15 +484,25 @@ function getCollisionTreeHelper( }; } } - //store overall maximum depth - if (currentDepth) depth = Math.max(depth, currentDepth); + //store overall maximum travel + if (currentOccupiedLength) + occupiedLength = Math.max(occupiedLength, currentOccupiedLength); } else if (currentChildNode && collisionTree.children) { collisionTree.children[currentChildNode.id] = { ...currentChildNode, }; } } - return { collisionTree, depth: depth + 1 }; + return { + collisionTree, + occupiedLength: + occupiedLength + + (accessors.isHorizontal + ? HORIZONTAL_RESIZE_LIMIT + : collidingSpace.fixedHeight && accessors.directionIndicator < 0 + ? collidingSpace.fixedHeight + : VERTICAL_RESIZE_LIMIT), + }; } /** @@ -611,7 +621,7 @@ function getMovementMapHelper( globalProcessedNodes: CollisionTreeCache, ) { let maxOccupiedSpace = 0, - depth = 0, + occupiedLength = 0, currentEmptySpaces = emptySpaces; if (collisionTree.children && !isEmpty(collisionTree.children)) { @@ -628,14 +638,14 @@ function getMovementMapHelper( let { currentEmptySpaces: childEmptySpaces, - depth: currentDepth, + occupiedLength: currentOccupiedLength, occupiedSpace, shouldProcessNode, } = checkProcessNodeForTree(childNode, globalProcessedNodes); //process the nodes if either one is undefined if ( shouldProcessNode || - currentDepth === undefined || + currentOccupiedLength === undefined || occupiedSpace === undefined || childEmptySpaces === undefined ) { @@ -661,14 +671,14 @@ function getMovementMapHelper( //add value to cache globalProcessedNodes[childNode.id][childNode.direction] = { value: childNode.collidingValue, - depth: movementVariables.depth, + occupiedLength: movementVariables.occupiedLength, occupiedSpace: movementVariables.occupiedSpace, currentEmptySpaces: movementVariables.currentEmptySpaces, }; //set current values shouldProcessNode = false; - currentDepth = movementVariables.depth; + currentOccupiedLength = movementVariables.occupiedLength; occupiedSpace = movementVariables.occupiedSpace; childEmptySpaces = movementVariables.currentEmptySpaces; } @@ -679,9 +689,9 @@ function getMovementMapHelper( //maxOccupiedSpace is the maximum dimension that is occupied by all the spaces above it in the tree maxOccupiedSpace = Math.max(maxOccupiedSpace, occupiedSpace || 0); - // depth is the number of spaces it has below it in the tree, + // occupiedLength is the sum of minimum occupied lengths of all spaces between collidingSpace and the edge of canvas, //useful to calculate resized dimensions for spaces colliding with boundaries - depth = Math.max(depth, currentDepth); + occupiedLength = Math.max(occupiedLength, currentOccupiedLength); } } else { if (direction === ReflowDirection.RIGHT) { @@ -701,7 +711,7 @@ function getMovementMapHelper( gridProps, direction, maxOccupiedSpace, - depth, + occupiedLength, distanceBeforeCollision, emptySpaces, currentEmptySpaces, @@ -725,7 +735,9 @@ function getMovementMapHelper( (movementMap[collisionTree.id].horizontalMaxOccupiedSpace || 0) + collisionTree[accessors.parallelMax] - collisionTree[accessors.parallelMin], - depth: (movementMap[collisionTree.id].horizontalDepth || 0) + 1, + occupiedLength: + (movementMap[collisionTree.id].horizontalOccupiedLength || 0) + + HORIZONTAL_RESIZE_LIMIT, currentEmptySpaces: (movementMap[collisionTree.id].horizontalEmptySpaces as number) || 0, @@ -735,7 +747,11 @@ function getMovementMapHelper( (movementMap[collisionTree.id].verticalMaxOccupiedSpace || 0) + collisionTree[accessors.parallelMax] - collisionTree[accessors.parallelMin], - depth: (movementMap[collisionTree.id].verticalDepth || 0) + 1, + occupiedLength: + (movementMap[collisionTree.id].verticalOccupiedLength || 0) + + (collisionTree.fixedHeight && accessors.directionIndicator < 0 + ? collisionTree.fixedHeight + : VERTICAL_RESIZE_LIMIT), currentEmptySpaces: (movementMap[collisionTree.id].verticalEmptySpaces as number) || 0, }; @@ -751,7 +767,13 @@ function getMovementMapHelper( maxOccupiedSpace + collisionTree[accessors.parallelMax] - collisionTree[accessors.parallelMin], - depth: depth + 1, + occupiedLength: + occupiedLength + + (accessors.isHorizontal + ? HORIZONTAL_RESIZE_LIMIT + : collisionTree.fixedHeight && accessors.directionIndicator < 0 + ? collisionTree.fixedHeight + : VERTICAL_RESIZE_LIMIT), currentEmptySpaces, }; } @@ -762,7 +784,7 @@ function getMovementMapHelper( * @param gridProps properties of the canvas's grid * @param direction ReflowDirection, direction of reflow of the colliding space * @param maxOccupiedSpace dimension of all the spaces that were occupied - * @param depth index of the widget from the end branches of the tree + * @param occupiedLength is the sum of minimum occupied lengths of all spaces between collidingSpace and the edge of canvas * @param distanceBeforeCollision point of collision from the previous widget * @param emptySpaces total number of emptySpaces it's parent ancestors encountered while reflowed * @param currentEmptySpaces current number of emptySpaces this node encountered @@ -776,7 +798,7 @@ export function getHorizontalSpaceMovement( gridProps: GridProps, direction: ReflowDirection, maxOccupiedSpace: number, - depth: number, + occupiedLength: number, distanceBeforeCollision: number, emptySpaces: number, currentEmptySpaces: number, @@ -788,7 +810,7 @@ export function getHorizontalSpaceMovement( collisionTree, gridProps, direction, - depth, + occupiedLength, maxOccupiedSpace, shouldResize, ); @@ -818,7 +840,7 @@ export function getHorizontalSpaceMovement( maxX, width, horizontalEmptySpaces: currentEmptySpaces, - horizontalDepth: depth, + horizontalOccupiedLength: occupiedLength, horizontalMaxOccupiedSpace: maxOccupiedSpace, }; @@ -831,7 +853,7 @@ export function getHorizontalSpaceMovement( * @param gridProps properties of the canvas's grid * @param direction ReflowDirection, direction of reflow of the colliding space * @param maxOccupiedSpace dimension of all the spaces that were occupied - * @param depth index of the widget from the end branches of the tree + * @param occupiedLength is the sum of minimum occupied lengths of all spaces between collidingSpace and the edge of canvas * @param distanceBeforeCollision point of collision from the previous widget * @param emptySpaces total number of emptySpaces it's parent ancestors encountered while reflowed * @param currentEmptySpaces current number of emptySpaces this node encountered @@ -845,7 +867,7 @@ export function getVerticalSpaceMovement( gridProps: GridProps, direction: ReflowDirection, maxOccupiedSpace: number, - depth: number, + occupiedLength: number, distanceBeforeCollision: number, emptySpaces: number, currentEmptySpaces: number, @@ -857,7 +879,7 @@ export function getVerticalSpaceMovement( collisionTree, gridProps, direction, - depth, + occupiedLength, maxOccupiedSpace, shouldResize, ); @@ -888,7 +910,7 @@ export function getVerticalSpaceMovement( maxY, height, verticalEmptySpaces: currentEmptySpaces, - verticalDepth: depth, + verticalOccupiedLength: occupiedLength, verticalMaxOccupiedSpace: maxOccupiedSpace, }; @@ -900,7 +922,7 @@ export function getVerticalSpaceMovement( * MovementVariables are intermediatory variables to calculate the actual movement Limits of each dragging/resizing space * * @param newSpacePositionsMap new/current positions map of the space/block - * @param directionalVariables information required to calculate limits such ass depth, emptySpaces of new space positions + * @param directionalVariables information required to calculate limits such as occupiedLength, emptySpaces of new space positions * @param delta X and Y distance from original positions * @param gridProps properties of the canvas's grid * @param shouldResize boolean to indicate if colliding spaces should resize @@ -925,7 +947,7 @@ function getMovementVariables( for (const directionKey of directionalKeys) { const [ - staticDepth, + staticOccupiedLength, maxOccupiedSpace, accessors, reflowDirection, @@ -941,7 +963,7 @@ function getMovementVariables( newSpacePositionsMap[newSpacePositionId] as CollisionTree, gridProps, reflowDirection, - staticDepth, + staticOccupiedLength, maxOccupiedSpace, shouldResize, ) + diff --git a/app/client/src/reflow/reflowTypes.ts b/app/client/src/reflow/reflowTypes.ts index a69efcd9a3..48d86c7cb8 100644 --- a/app/client/src/reflow/reflowTypes.ts +++ b/app/client/src/reflow/reflowTypes.ts @@ -58,6 +58,7 @@ export type CollidingSpace = OccupiedSpace & { collidingId: string; isHorizontal: boolean; order: number; + fixedHeight?: number; }; export type SecondOrderCollision = OccupiedSpace & { @@ -95,6 +96,7 @@ export type CollisionTree = OccupiedSpace & { collidingId: string; isHorizontal: boolean; order: number; + fixedHeight?: number; }; export type SpaceMovementMap = { @@ -112,7 +114,7 @@ export type CollisionTreeCache = { [spaceId: string]: { [direction: string]: { value: number; - depth?: number; + occupiedLength?: number; occupiedSpace?: number; currentEmptySpaces?: number; childNode?: CollisionTree; @@ -125,8 +127,8 @@ export type ReflowedSpace = { Y?: number; width?: number; height?: number; - horizontalDepth?: number; - verticalDepth?: number; + horizontalOccupiedLength?: number; + verticalOccupiedLength?: number; x?: number; y?: number; maxX?: number; diff --git a/app/client/src/reflow/reflowUtils.ts b/app/client/src/reflow/reflowUtils.ts index 65adf52aba..6a8d8f57fd 100644 --- a/app/client/src/reflow/reflowUtils.ts +++ b/app/client/src/reflow/reflowUtils.ts @@ -9,7 +9,6 @@ import { CollisionTree, CollisionTreeCache, GridProps, - HORIZONTAL_RESIZE_LIMIT, MathComparators, MovementLimitMap, OrientationAccessors, @@ -21,7 +20,6 @@ import { SpaceAttributes, SpaceMap, SpaceMovementMap, - VERTICAL_RESIZE_LIMIT, } from "./reflowTypes"; /** @@ -998,7 +996,7 @@ export function getAccessor(direction: ReflowDirection): CollisionAccessors { * @param collisionTree * @param gridProps * @param direction - * @param depth + * @param occupiedLength * @param maxOccupiedSpace * @param shouldResize * @returns number @@ -1007,14 +1005,12 @@ export function getMaxX( collisionTree: CollisionTree, gridProps: GridProps, direction: ReflowDirection, - depth: number, + occupiedLength: number, maxOccupiedSpace: number, shouldResize: boolean, ) { const accessors = getAccessor(direction); - const movementLimit = shouldResize - ? depth * HORIZONTAL_RESIZE_LIMIT - : maxOccupiedSpace; + const movementLimit = shouldResize ? occupiedLength : maxOccupiedSpace; let maxX = collisionTree[accessors.direction] - movementLimit; @@ -1035,7 +1031,7 @@ export function getMaxX( * @param collisionTree * @param gridProps * @param direction - * @param depth + * @param occupiedLength * @param maxOccupiedSpace * @param shouldResize * @returns number @@ -1044,14 +1040,12 @@ export function getMaxY( collisionTree: CollisionTree, gridProps: GridProps, direction: ReflowDirection, - depth: number, + occupiedLength: number, maxOccupiedSpace: number, shouldResize: boolean, ) { const accessors = getAccessor(direction); - const movementLimit = shouldResize - ? depth * VERTICAL_RESIZE_LIMIT - : maxOccupiedSpace; + const movementLimit = shouldResize ? occupiedLength : maxOccupiedSpace; let maxY = (collisionTree[accessors.direction] - movementLimit) * @@ -1138,6 +1132,9 @@ export function getReflowedDimension( ) { const accessors = getAccessor(direction); + if (direction === ReflowDirection.TOP && collisionTree.fixedHeight) + return collisionTree.fixedHeight * snapGridSpace; + const currentDistanceBeforeCollision = travelDistance + (distanceBeforeCollision + emptySpaces * accessors.directionIndicator) * @@ -1850,7 +1847,7 @@ export function checkProcessNodeForTree( const { childNode, currentEmptySpaces, - depth, + occupiedLength, occupiedSpace, value, } = globalProcessedNodes[collidingSpace.id][direction]; @@ -1858,7 +1855,7 @@ export function checkProcessNodeForTree( return { shouldProcessNode: false, currentChildNode: childNode, - depth, + occupiedLength, occupiedSpace, currentEmptySpaces, }; @@ -1873,11 +1870,11 @@ export function checkProcessNodeForTree( * eg, If a widget is colliding with another, near the ege of the canvas. * After the point where the widget resizes full and can'yt move or resize, then the colliding value also should not increase * - * @param depth * @param accessors * @param collidingValue * @param direction * @param gridProps + * @param occupiedLength * @returns number, colliding value to the edge of canvas */ export function getRelativeCollidingValue( @@ -1885,13 +1882,12 @@ export function getRelativeCollidingValue( collidingValue: number, direction: ReflowDirection, { maxGridColumns }: GridProps, - depth?: number, + occupiedLength?: number, ): number { - if (direction === ReflowDirection.BOTTOM || !depth) return collidingValue; + if (direction === ReflowDirection.BOTTOM || !occupiedLength) + return collidingValue; - let calculatedCollidingValue = - (accessors.isHorizontal ? HORIZONTAL_RESIZE_LIMIT : VERTICAL_RESIZE_LIMIT) * - depth; + let calculatedCollidingValue = occupiedLength; if (direction === ReflowDirection.RIGHT) calculatedCollidingValue = maxGridColumns - calculatedCollidingValue; diff --git a/app/client/src/reflow/tests/reflowHelpers.test.js b/app/client/src/reflow/tests/reflowHelpers.test.js index 2862c20228..ca99b5355f 100644 --- a/app/client/src/reflow/tests/reflowHelpers.test.js +++ b/app/client/src/reflow/tests/reflowHelpers.test.js @@ -1,4 +1,8 @@ -import { ReflowDirection } from "reflow/reflowTypes"; +import { + HORIZONTAL_RESIZE_LIMIT, + ReflowDirection, + VERTICAL_RESIZE_LIMIT, +} from "reflow/reflowTypes"; import { getAccessor } from "reflow/reflowUtils"; import { getCollisionTree, @@ -364,7 +368,7 @@ describe("Test reflow helper methods", () => { directionY: "BOTTOM", height: 200, maxY: Infinity, - verticalDepth: 1, + verticalOccupiedLength: VERTICAL_RESIZE_LIMIT, verticalEmptySpaces: 0, verticalMaxOccupiedSpace: 20, }, @@ -374,7 +378,7 @@ describe("Test reflow helper methods", () => { directionY: "BOTTOM", height: 200, maxY: Infinity, - verticalDepth: 0, + verticalOccupiedLength: 0, verticalEmptySpaces: 0, verticalMaxOccupiedSpace: 0, }, @@ -384,7 +388,7 @@ describe("Test reflow helper methods", () => { directionY: "BOTTOM", height: 200, maxY: Infinity, - verticalDepth: 0, + verticalOccupiedLength: 0, verticalEmptySpaces: 0, verticalMaxOccupiedSpace: 0, }, @@ -392,7 +396,7 @@ describe("Test reflow helper methods", () => { X: 100, dimensionXBeforeCollision: -10, directionX: "RIGHT", - horizontalDepth: 0, + horizontalOccupiedLength: 0, horizontalEmptySpaces: -46, horizontalMaxOccupiedSpace: 0, maxX: 900, @@ -570,7 +574,7 @@ describe("Test reflow helper methods", () => { gridProps, ReflowDirection.RIGHT, 20, - 3, + 3 * HORIZONTAL_RESIZE_LIMIT, -10, 7, 7, @@ -581,7 +585,7 @@ describe("Test reflow helper methods", () => { X: 30, dimensionXBeforeCollision: -10, directionX: "RIGHT", - horizontalDepth: 3, + horizontalOccupiedLength: 3 * HORIZONTAL_RESIZE_LIMIT, horizontalEmptySpaces: 7, horizontalMaxOccupiedSpace: 20, maxX: 80, @@ -595,7 +599,7 @@ describe("Test reflow helper methods", () => { gridProps, ReflowDirection.BOTTOM, 20, - 3, + 3 * VERTICAL_RESIZE_LIMIT, -10, 7, 7, @@ -608,7 +612,7 @@ describe("Test reflow helper methods", () => { directionY: "BOTTOM", height: 200, maxY: Infinity, - verticalDepth: 3, + verticalOccupiedLength: 3 * VERTICAL_RESIZE_LIMIT, verticalEmptySpaces: 7, verticalMaxOccupiedSpace: 20, }); diff --git a/app/client/src/reflow/tests/reflowUtils.test.js b/app/client/src/reflow/tests/reflowUtils.test.js index 21dfdf02c3..2280c7254f 100644 --- a/app/client/src/reflow/tests/reflowUtils.test.js +++ b/app/client/src/reflow/tests/reflowUtils.test.js @@ -532,7 +532,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.LEFT, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, 30, false, ), @@ -545,7 +545,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.LEFT, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, 30, true, ), @@ -562,7 +562,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.RIGHT, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, 30, false, ), @@ -578,7 +578,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.RIGHT, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, 30, true, ), @@ -608,7 +608,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.TOP, - depth, + depth * VERTICAL_RESIZE_LIMIT, 20, false, ), @@ -617,7 +617,14 @@ describe("Test reflow util methods", () => { it("should return max number for TOP Direction when Resizing", () => { const depth = 2; expect( - getMaxY(collisionTree, gridProps, ReflowDirection.TOP, depth, 20, true), + getMaxY( + collisionTree, + gridProps, + ReflowDirection.TOP, + depth * VERTICAL_RESIZE_LIMIT, + 20, + true, + ), ).toBe( -1 * (collisionTree.top - depth * VERTICAL_RESIZE_LIMIT) * @@ -631,7 +638,7 @@ describe("Test reflow util methods", () => { collisionTree, gridProps, ReflowDirection.BOTTOM, - depth, + depth * VERTICAL_RESIZE_LIMIT, 230, false, ), @@ -1906,7 +1913,7 @@ describe("Test reflow util methods", () => { "1234": { BOTTOM: { value: 10, - depth: 5, + occupiedLength: 5 * VERTICAL_RESIZE_LIMIT, occupiedSpace: 10, currentEmptySpaces: 10, }, @@ -1914,7 +1921,7 @@ describe("Test reflow util methods", () => { }; expect(checkProcessNodeForTree(collidingSpace, processedNodes)).toEqual({ shouldProcessNode: false, - depth: 5, + occupiedLength: 5 * VERTICAL_RESIZE_LIMIT, occupiedSpace: 10, currentEmptySpaces: 10, }); @@ -1936,7 +1943,7 @@ describe("Test reflow util methods", () => { collidingValue, direction, gridProps, - depth, + depth * VERTICAL_RESIZE_LIMIT, ), ).toBe(collidingValue); }); @@ -1951,7 +1958,7 @@ describe("Test reflow util methods", () => { collidingValue, direction, gridProps, - depth, + depth * VERTICAL_RESIZE_LIMIT, ), ).toBe(collidingValue); }); @@ -1966,7 +1973,7 @@ describe("Test reflow util methods", () => { collidingValue, direction, gridProps, - depth, + depth * VERTICAL_RESIZE_LIMIT, ), ).toBe(depth * VERTICAL_RESIZE_LIMIT); }); @@ -1981,7 +1988,7 @@ describe("Test reflow util methods", () => { collidingValue, direction, gridProps, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, ), ).toBe(depth * HORIZONTAL_RESIZE_LIMIT); }); @@ -1996,7 +2003,7 @@ describe("Test reflow util methods", () => { collidingValue, direction, gridProps, - depth, + depth * HORIZONTAL_RESIZE_LIMIT, ), ).toBe(gridProps.maxGridColumns - depth * HORIZONTAL_RESIZE_LIMIT); }); diff --git a/app/client/src/resizable/resize/index.tsx b/app/client/src/resizable/resize/index.tsx index 8f7e55827b..6e3f547661 100644 --- a/app/client/src/resizable/resize/index.tsx +++ b/app/client/src/resizable/resize/index.tsx @@ -5,12 +5,14 @@ import { Spring, animated } from "react-spring"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; +import { ReflowDirection } from "reflow/reflowTypes"; +import { isHandleResizeAllowed } from "components/editorComponents/ResizableUtils"; -const ResizeWrapper = styled(animated.div)<{ prevents: boolean }>` +const ResizeWrapper = styled(animated.div)<{ $prevents: boolean }>` display: block; & { * { - pointer-events: ${(props) => !props.prevents && "none"}; + pointer-events: ${(props) => !props.$prevents && "none"}; } } `; @@ -29,6 +31,7 @@ const getSnappedValues = ( type ResizableHandleProps = { allowResize: boolean; showLightBorder?: boolean; + disableDot: boolean; dragCallback: (x: number, y: number) => void; component: StyledComponent<"div", Record>; onStart: () => void; @@ -62,6 +65,7 @@ function ResizableHandle(props: ResizableHandleProps) { ...bind(), showAsBorder: !props.allowResize, showLightBorder: props.showLightBorder, + disableDot: props.disableDot, }; return ; @@ -88,7 +92,8 @@ type ResizableProps = { position: { x: number; y: number }, ) => void; snapGrid: { x: number; y: number }; - enable: boolean; + enableVerticalResize: boolean; + enableHorizontalResize: boolean; isColliding: ( size: { width: number; height: number }, position: { x: number; y: number }, @@ -140,6 +145,14 @@ export const Resizable = forwardRef(function Resizable( y: number; }) => { const { height, width, x, y } = rect; + const shouldUpdateHeight = + props.componentHeight !== height && props.enableVerticalResize; + if (!shouldUpdateHeight) rect.height = props.componentHeight; + + const shouldUpdateWidth = + props.componentWidth !== width && props.enableHorizontalResize; + if (!shouldUpdateWidth) rect.width = props.componentWidth; + const isColliding = props.isColliding({ width, height }, { x, y }); if (!isColliding) { set({ ...rect, reset: false }); @@ -169,6 +182,7 @@ export const Resizable = forwardRef(function Resizable( }); }, component: props.handles.left, + handleDirection: ReflowDirection.LEFT, }); } @@ -183,6 +197,7 @@ export const Resizable = forwardRef(function Resizable( }); }, component: props.handles.top, + handleDirection: ReflowDirection.TOP, }); } @@ -197,6 +212,7 @@ export const Resizable = forwardRef(function Resizable( }); }, component: props.handles.right, + handleDirection: ReflowDirection.RIGHT, }); } @@ -211,6 +227,7 @@ export const Resizable = forwardRef(function Resizable( }); }, component: props.handles.bottom, + handleDirection: ReflowDirection.BOTTOM, }); } @@ -283,20 +300,28 @@ export const Resizable = forwardRef(function Resizable( ); }; - const renderHandles = handles.map((handle, index) => ( - { - togglePointerEvents(false); - props.onStart(); - }} - onStop={onResizeStop} - showLightBorder={props.showLightBorder} - snapGrid={props.snapGrid} - /> - )); + const renderHandles = handles.map((handle, index) => { + const disableDot = !isHandleResizeAllowed( + props.enableHorizontalResize, + props.enableVerticalResize, + handle.handleDirection, + ); + return ( + { + togglePointerEvents(false); + props.onStart(); + }} + onStop={onResizeStop} + showLightBorder={props.showLightBorder} + snapGrid={props.snapGrid} + /> + ); + }); return ( {(_props) => ( {props.children} - {props.enable && renderHandles} + {props.enableHorizontalResize && renderHandles} )} diff --git a/app/client/src/resizable/resizenreflow/index.tsx b/app/client/src/resizable/resizenreflow/index.tsx index d93ee21168..f9097e0aca 100644 --- a/app/client/src/resizable/resizenreflow/index.tsx +++ b/app/client/src/resizable/resizenreflow/index.tsx @@ -19,12 +19,13 @@ import { import { getNearestParentCanvas } from "utils/generators"; import { getContainerOccupiedSpacesSelectorWhileResizing } from "selectors/editorSelectors"; import { isDropZoneOccupied } from "utils/WidgetPropsUtils"; +import { isHandleResizeAllowed } from "components/editorComponents/ResizableUtils"; -const ResizeWrapper = styled(animated.div)<{ prevents: boolean }>` +const ResizeWrapper = styled(animated.div)<{ $prevents: boolean }>` display: block; & { * { - pointer-events: ${(props) => !props.prevents && "none"}; + pointer-events: ${(props) => !props.$prevents && "none"}; } } `; @@ -54,6 +55,7 @@ export type DimensionProps = { type ResizableHandleProps = { allowResize: boolean; scrollParent: HTMLDivElement | null; + disableDot: boolean; checkForCollision: (widgetNewSize: { left: number; top: number; @@ -68,6 +70,7 @@ type ResizableHandleProps = { x: number; y: number; }; + direction?: ReflowDirection; }; function ResizableHandle(props: ResizableHandleProps) { @@ -112,9 +115,15 @@ function ResizableHandle(props: ResizableHandleProps) { const propsToPass = { ...bind(), showAsBorder: !props.allowResize, + disableDot: props.disableDot, }; - return ; + return ( + + ); } type ResizableProps = { @@ -148,7 +157,8 @@ type ResizableProps = { position: { x: number; y: number }, ) => void; snapGrid: { x: number; y: number }; - enable: boolean; + enableVerticalResize: boolean; + enableHorizontalResize: boolean; className?: string; parentId?: string; widgetId: string; @@ -321,6 +331,7 @@ export function ReflowResizable(props: ResizableProps) { }); }, component: props.handles.left, + handleDirection: ReflowDirection.LEFT, }); } @@ -337,6 +348,7 @@ export function ReflowResizable(props: ResizableProps) { }); }, component: props.handles.top, + handleDirection: ReflowDirection.TOP, }); } @@ -353,6 +365,7 @@ export function ReflowResizable(props: ResizableProps) { }); }, component: props.handles.right, + handleDirection: ReflowDirection.RIGHT, }); } @@ -369,6 +382,7 @@ export function ReflowResizable(props: ResizableProps) { }); }, component: props.handles.bottom, + handleDirection: ReflowDirection.BOTTOM, }); } @@ -454,22 +468,31 @@ export function ReflowResizable(props: ResizableProps) { setResizing(false); }; - const renderHandles = handles.map((handle, index) => ( - { - togglePointerEvents(false); - props.onStart(); - setResizing(true); - }} - onStop={onResizeStop} - scrollParent={resizableRef.current} - snapGrid={props.snapGrid} - /> - )); + const renderHandles = handles.map((handle, index) => { + const disableDot = !isHandleResizeAllowed( + props.enableHorizontalResize, + props.enableVerticalResize, + handle.handleDirection, + ); + return ( + { + togglePointerEvents(false); + props.onStart(); + setResizing(true); + }} + onStop={onResizeStop} + scrollParent={resizableRef.current} + snapGrid={props.snapGrid} + /> + ); + }); const widgetWidth = reflowedPosition?.width === undefined @@ -499,13 +522,13 @@ export function ReflowResizable(props: ResizableProps) { > {(_props) => ( {props.children} - {props.enable && renderHandles} + {props.enableHorizontalResize && renderHandles} )} diff --git a/app/client/src/sagas/AppThemingSaga.tsx b/app/client/src/sagas/AppThemingSaga.tsx index b670d6d9c9..a3fee20274 100644 --- a/app/client/src/sagas/AppThemingSaga.tsx +++ b/app/client/src/sagas/AppThemingSaga.tsx @@ -32,7 +32,6 @@ import { APP_MODE } from "entities/App"; import { getCurrentUser } from "selectors/usersSelectors"; import { User } from "constants/userConstants"; import { getBetaFlag, setBetaFlag, STORAGE_KEYS } from "utils/storage"; -import { getSelectedAppThemeStylesheet } from "selectors/appThemingSelectors"; import { batchUpdateMultipleWidgetProperties, UpdateWidgetPropertyPayload, @@ -308,12 +307,8 @@ function* resetTheme() { const canvasWidgets: CanvasWidgetsReduxState = yield select( getCanvasWidgets, ); - // @ts-expect-error: Type the StyleSheet - const themeStylesheet = yield select(getSelectedAppThemeStylesheet); - const propertiesToUpdate: UpdateWidgetPropertyPayload[] = getPropertiesToUpdateForReset( canvasWidgets, - themeStylesheet, ); if (propertiesToUpdate.length) { diff --git a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts index 5669e973d4..6b1caf6749 100644 --- a/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts +++ b/app/client/src/sagas/CanvasSagas/DraggingCanvasSagas.ts @@ -1,35 +1,36 @@ -import { Toaster } from "design-system"; import { ReduxAction, ReduxActionErrorTypes, ReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; -import { - CanvasWidgetsReduxState, - FlattenedWidgetProps, -} from "reducers/entityReducers/canvasWidgetsReducer"; -import { all, call, put, select, takeLatest } from "redux-saga/effects"; -import log from "loglevel"; -import { cloneDeep } from "lodash"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import { updateAndSaveLayout, WidgetAddChild } from "actions/pageActions"; import { calculateDropTargetRows } from "components/editorComponents/DropTargetUtils"; +import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants"; +import { OccupiedSpace } from "constants/CanvasEditorConstants"; import { GridDefaults, MAIN_CONTAINER_WIDGET_ID, } from "constants/WidgetConstants"; -import { WidgetProps } from "widgets/BaseWidget"; +import { Toaster } from "design-system"; +import { cloneDeep } from "lodash"; +import log from "loglevel"; +import { WidgetDraggingUpdateParams } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas"; +import { + CanvasWidgetsReduxState, + FlattenedWidgetProps, +} from "reducers/entityReducers/canvasWidgetsReducer"; +import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; +import { getWidget, getWidgets } from "sagas/selectors"; +import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas"; import { getMainCanvasProps, getOccupiedSpacesSelectorForContainer, } from "selectors/editorSelectors"; -import { OccupiedSpace } from "constants/CanvasEditorConstants"; -import { collisionCheckPostReflow } from "utils/reflowHookUtils"; -import { WidgetDraggingUpdateParams } from "pages/common/CanvasArenas/hooks/useBlocksToBeDraggedOnCanvas"; -import { getWidget, getWidgets } from "sagas/selectors"; -import { getUpdateDslAfterCreatingChild } from "sagas/WidgetAdditionSagas"; -import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; -import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants"; import AnalyticsUtil from "utils/AnalyticsUtil"; +import { collisionCheckPostReflow } from "utils/reflowHookUtils"; +import { WidgetProps } from "widgets/BaseWidget"; export type WidgetMoveParams = { widgetId: string; @@ -56,21 +57,33 @@ export function* getCanvasSizeAfterWidgetMove( //get mainCanvas's minHeight if the canvasWidget is mianCanvas let mainCanvasMinHeight; + let canvasParentMinHeight = canvasWidget.minHeight; if (canvasWidgetId === MAIN_CONTAINER_WIDGET_ID) { const mainCanvasProps: MainCanvasReduxState = yield select( getMainCanvasProps, ); mainCanvasMinHeight = mainCanvasProps?.height; + } else if (canvasWidget.parentId) { + const parent: FlattenedWidgetProps = yield select( + getWidget, + canvasWidget.parentId, + ); + if (!parent.detachFromLayout) { + canvasParentMinHeight = + (parent.bottomRow - parent.topRow) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + } } - if (canvasWidget) { const occupiedSpacesByChildren: OccupiedSpace[] | undefined = yield select( getOccupiedSpacesSelectorForContainer(canvasWidgetId), ); + const canvasMinHeight = mainCanvasMinHeight || - canvasWidget.minHeight || + canvasParentMinHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX; + const newRows = calculateDropTargetRows( movedWidgetIds, movedWidgetsBottomRow, @@ -78,6 +91,7 @@ export function* getCanvasSizeAfterWidgetMove( occupiedSpacesByChildren, canvasWidgetId, ); + const rowsToPersist = Math.max( canvasMinHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1, newRows, @@ -88,6 +102,7 @@ export function* getCanvasSizeAfterWidgetMove( const newBottomRow = Math.round( rowsToPersist * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, ); + /* Update the canvas's rows, ONLY if it has changed since the last render */ if (originalSnapRows !== newBottomRow) { // TODO(abhinav): This considers that the topRow will always be zero @@ -140,6 +155,7 @@ function* addWidgetAndMoveWidgetsSaga( throw Error; } yield put(updateAndSaveLayout(updatedWidgetsOnAddAndMove)); + yield put(generateAutoHeightLayoutTreeAction(true, true)); yield put({ type: ReduxActionTypes.RECORD_RECENTLY_ADDED_WIDGET, payload: [newWidget.newWidgetId], @@ -293,6 +309,7 @@ function* moveWidgetsSaga( throw Error; } yield put(updateAndSaveLayout(updatedWidgetsOnMove)); + yield put(generateAutoHeightLayoutTreeAction(true, true)); const block = draggedBlocksToUpdate[0]; const oldParentId = block.updateWidgetParams.payload.parentId; diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index fc530433d1..4eac75ed28 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -335,8 +335,6 @@ function* updateDatasourceSaga( const state: AppState = yield select(); const expandDatasourceId = state.ui.datasourcePane.expandDatasourceId; - const datasourceStructure = - state.entities.datasources.structure[response.data.id]; // Dont redirect if action payload has an onSuccess yield put( @@ -358,8 +356,9 @@ function* updateDatasourceSaga( if (actionPayload.onSuccess) { yield put(actionPayload.onSuccess); } - if (expandDatasourceId === response.data.id && !datasourceStructure) { - yield put(fetchDatasourceStructure(response.data.id)); + //Refresh datasource structure on save + if (expandDatasourceId === response.data.id) { + yield put(fetchDatasourceStructure(response.data.id, true)); } AppsmithConsole.info({ diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 8727f58bd6..d1b6494b99 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -134,7 +134,6 @@ function* evaluateTreeSaga( PerformanceTracker.startAsyncTracking( PerformanceTransactionName.DATA_TREE_EVALUATION, ); - const evalTreeRequestData: EvalTreeRequestData = { unevalTree, widgetTypeConfigMap, @@ -217,9 +216,13 @@ function* evaluateTreeSaga( isCreateFirstTree, ); - yield fork(updateTernDefinitions, updatedDataTree, unEvalUpdates); + yield fork( + updateTernDefinitions, + updatedDataTree, + unEvalUpdates, + isCreateFirstTree, + ); } - yield put(setDependencyMap(dependencies)); if (postEvalActions && postEvalActions.length) { yield call(postEvalActionDispatcher, postEvalActions); diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index a12b920d1f..fc11ee6c85 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -38,6 +38,7 @@ import AppEngine, { import AppEngineFactory from "entities/Engine/factory"; import { ApplicationPagePayload } from "api/ApplicationApi"; import { updateSlugNamesInURL } from "utils/helpers"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; export const URL_CHANGE_ACTIONS = [ ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE, @@ -92,6 +93,7 @@ export function* startAppEngine(action: ReduxAction) { yield call(engine.loadAppEntities, toLoadPageId, applicationId); yield call(engine.loadGit, applicationId); yield call(engine.completeChore); + yield put(generateAutoHeightLayoutTreeAction(true, false)); engine.stopPerformanceTracking(); } catch (e) { log.error(e); diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index 93eabd0afc..65dcd7cfe1 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -126,6 +126,7 @@ import { getCanvasWidgetsWithParentId } from "selectors/entitiesSelector"; import { showModal } from "actions/widgetActions"; import { checkAndLogErrorsIfCyclicDependency } from "./helper"; import { LOCAL_STORAGE_KEYS } from "utils/localStorage"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; const WidgetTypes = WidgetFactory.widgetTypes; @@ -260,6 +261,10 @@ export function* handleFetchedPage({ payload: extractedDSL, }); + // Since new page has new layout, we need to generate a data structure + // to compute dynamic height based on the new layout. + yield put(generateAutoHeightLayoutTreeAction(true, true)); + if (willPageBeMigrated) { yield put(saveLayout()); } @@ -349,6 +354,10 @@ export function* fetchPublishedPageSaga( // dispatch fetch page success yield put(fetchPublishedPageSuccess()); + // Since new page has new layout, we need to generate a data structure + // to compute dynamic height based on the new layout. + yield put(generateAutoHeightLayoutTreeAction(true, true)); + /* Currently, All Actions are fetched in initSagas and on pageSwitch we only fetch page */ // Hence, if is not isFirstLoad then trigger evaluation with execute pageLoad action diff --git a/app/client/src/sagas/PostEvaluationSagas.ts b/app/client/src/sagas/PostEvaluationSagas.ts index 58dfe1913d..7d26638303 100644 --- a/app/client/src/sagas/PostEvaluationSagas.ts +++ b/app/client/src/sagas/PostEvaluationSagas.ts @@ -41,7 +41,7 @@ import { AppState } from "@appsmith/reducers"; import { getAppMode } from "selectors/applicationSelectors"; import { APP_MODE } from "entities/App"; import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator"; -import TernServer from "utils/autocomplete/TernServer"; +import CodemirrorTernService from "utils/autocomplete/CodemirrorTernService"; import { selectFeatureFlags } from "selectors/usersSelectors"; import FeatureFlags from "entities/FeatureFlags"; import { JSAction } from "entities/JSCollection"; @@ -337,55 +337,36 @@ export function* postEvalActionDispatcher(actions: Array) { // is accurate export function* updateTernDefinitions( dataTree: DataTree, - updates?: DataTreeDiff[], + updates: DataTreeDiff[], + isCreateFirstTree: boolean, ) { - let shouldUpdate: boolean; - // No updates, means it was a first Eval - if (!updates) { - shouldUpdate = true; - } else if (updates.length === 0) { - // update length is 0 means no significant updates - shouldUpdate = false; - } else { - // Only when new field is added or deleted, we want to re-create the def - shouldUpdate = some(updates, (update) => { - if ( - update.event === DataTreeDiffEvent.NEW || - update.event === DataTreeDiffEvent.DELETE - ) { - return true; - } - - if (update.event === DataTreeDiffEvent.NOOP) { - const { entityName } = getEntityNameAndPropertyPath( - update.payload.propertyPath, - ); - const entity = dataTree[entityName]; - if (entity && isWidget(entity)) { - // if widget property name is modified then update tern def - return isWidgetPropertyNamePath(entity, update.payload.propertyPath); - } - } - - return false; + const shouldUpdate: boolean = + isCreateFirstTree || + some(updates, (update) => { + if (update.event === DataTreeDiffEvent.NEW) return true; + if (update.event === DataTreeDiffEvent.DELETE) return true; + if (update.event === DataTreeDiffEvent.EDIT) return false; + const { entityName } = getEntityNameAndPropertyPath( + update.payload.propertyPath, + ); + const entity = dataTree[entityName]; + if (!entity || !isWidget(entity)) return false; + return isWidgetPropertyNamePath(entity, update.payload.propertyPath); }); - } - if (shouldUpdate) { - const start = performance.now(); - // remove private widgets from dataTree used for autocompletion - const treeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets( - dataTree, - ); - const featureFlags: FeatureFlags = yield select(selectFeatureFlags); - const { def, entityInfo } = dataTreeTypeDefCreator( - treeWithoutPrivateWidgets, - !!featureFlags.JS_EDITOR, - ); - TernServer.updateDef("DATA_TREE", def, entityInfo); - const end = performance.now(); - log.debug("Tern", { updates }); - log.debug("Tern definitions updated took ", (end - start).toFixed(2)); - } + + if (!shouldUpdate) return; + const start = performance.now(); + // remove private widgets from dataTree used for autocompletion + const treeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets(dataTree); + const featureFlags: FeatureFlags = yield select(selectFeatureFlags); + const { def, entityInfo } = dataTreeTypeDefCreator( + treeWithoutPrivateWidgets, + !!featureFlags.JS_EDITOR, + ); + CodemirrorTernService.updateDef("DATA_TREE", def, entityInfo); + const end = performance.now(); + log.debug("Tern", { updates }); + log.debug("Tern definitions updated took ", (end - start).toFixed(2)); } export function* handleJSFunctionExecutionErrorLog( diff --git a/app/client/src/sagas/ReplaySaga.ts b/app/client/src/sagas/ReplaySaga.ts index 4a274e554f..7fdae58f4e 100644 --- a/app/client/src/sagas/ReplaySaga.ts +++ b/app/client/src/sagas/ReplaySaga.ts @@ -78,6 +78,7 @@ import { updateSelectedAppThemeAction, } from "actions/appThemingActions"; import { AppThemingMode } from "selectors/appThemingSelectors"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; export type UndoRedoPayload = { operation: ReplayReduxActionTypes; @@ -213,15 +214,21 @@ export function* undoRedoSaga(action: ReduxAction) { case ENTITY_TYPE.WIDGET: { const isPropertyUpdate = replay.widgets && replay.propertyUpdates; AnalyticsUtil.logEvent(event, { paths, timeTaken }); - if (isPropertyUpdate) yield call(openPropertyPaneSaga, replay); - //TODO Identify the updated widgets and pass the values + yield put( updateAndSaveLayout(replayEntity.widgets, { isRetry: false, shouldReplay: false, }), ); - if (!isPropertyUpdate) yield call(postUndoRedoSaga, replay); + + if (isPropertyUpdate) { + yield call(openPropertyPaneSaga, replay); + } + if (!isPropertyUpdate) { + yield call(postUndoRedoSaga, replay); + } + yield put(generateAutoHeightLayoutTreeAction(true, false)); break; } case ENTITY_TYPE.ACTION: diff --git a/app/client/src/sagas/WidgetAdditionSagas.ts b/app/client/src/sagas/WidgetAdditionSagas.ts index 74a4902c00..2527258041 100644 --- a/app/client/src/sagas/WidgetAdditionSagas.ts +++ b/app/client/src/sagas/WidgetAdditionSagas.ts @@ -38,21 +38,15 @@ import WidgetFactory from "utils/WidgetFactory"; import omit from "lodash/omit"; import produce from "immer"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; -import { getSelectedAppThemeStylesheet } from "selectors/appThemingSelectors"; import { getPropertiesToUpdate } from "./WidgetOperationSagas"; import { klona as clone } from "klona/full"; import { DataTree } from "entities/DataTree/dataTreeFactory"; import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; import { getMainCanvasProps } from "selectors/editorSelectors"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; const WidgetTypes = WidgetFactory.widgetTypes; -const themePropertiesDefaults = { - boxShadow: "none", - borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", - accentColor: "{{appsmith.theme.colors.primaryColor}}", -}; - type GeneratedWidgetPayload = { widgetId: string; widgets: { [widgetId: string]: FlattenedWidgetProps }; @@ -68,32 +62,6 @@ function* getEntityNames() { return Object.keys(evalTree); } -/** - * return stylesheet of widget - * NOTE: a stylesheet is an object that contains - * which property of widget will use which property of the theme - * - * @param type - * @returns - */ -function* getThemeDefaultConfig(type: string) { - const fallbackStylesheet: Record = { - TABLE_WIDGET_V2: "TABLE_WIDGET", - }; - - const stylesheet: Record = yield select( - getSelectedAppThemeStylesheet, - ); - - if (stylesheet[type]) { - return stylesheet[type]; - } else if (fallbackStylesheet[type] && stylesheet[fallbackStylesheet[type]]) { - return stylesheet[fallbackStylesheet[type]]; - } else { - return themePropertiesDefaults; - } -} - function* getChildWidgetProps( parent: FlattenedWidgetProps, params: WidgetAddChild, @@ -112,10 +80,9 @@ function* getChildWidgetProps( const restDefaultConfig = omit(WidgetFactory.widgetConfigMap.get(type), [ "blueprint", ]); - const themeDefaultConfig: Record = yield call( - getThemeDefaultConfig, - type, - ); + const themeDefaultConfig = + WidgetFactory.getWidgetStylesheetConfigMap(type) || {}; + if (!widgetName) { const widgetNames = Object.keys(widgets).map((w) => widgets[w].widgetName); const entityNames: string[] = yield call(getEntityNames); @@ -359,11 +326,14 @@ export function* addChildSaga(addChildAction: ReduxAction) { const updatedWidgets: { [widgetId: string]: FlattenedWidgetProps; } = yield call(getUpdateDslAfterCreatingChild, addChildAction.payload); + yield put(updateAndSaveLayout(updatedWidgets)); yield put({ type: ReduxActionTypes.RECORD_RECENTLY_ADDED_WIDGET, payload: [addChildAction.payload.newWidgetId], }); + yield put(generateAutoHeightLayoutTreeAction(true, true)); + log.debug("add child computations took", performance.now() - start, "ms"); // go up till MAIN_CONTAINER, if there is a operation CHILD_OPERATIONS IN ANY PARENT, // call execute diff --git a/app/client/src/sagas/WidgetDeletionSagas.ts b/app/client/src/sagas/WidgetDeletionSagas.ts index 3df86a1a56..d3e733de3c 100644 --- a/app/client/src/sagas/WidgetDeletionSagas.ts +++ b/app/client/src/sagas/WidgetDeletionSagas.ts @@ -40,6 +40,7 @@ import { import { toggleShowDeviationDialog } from "actions/onboardingActions"; import { getMainCanvasProps } from "selectors/editorSelectors"; import { MainCanvasReduxState } from "reducers/uiReducers/mainCanvasReducer"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; const WidgetTypes = WidgetFactory.widgetTypes; type WidgetDeleteTabChild = { @@ -229,6 +230,7 @@ function* deleteSaga(deleteAction: ReduxAction) { if (updatedObj) { const { finalWidgets, otherWidgetsToDelete, widgetName } = updatedObj; yield put(updateAndSaveLayout(finalWidgets)); + yield put(generateAutoHeightLayoutTreeAction(true, true)); const analyticsEvent = isShortcut ? "WIDGET_DELETE_VIA_SHORTCUT" : "WIDGET_DELETE"; @@ -313,6 +315,8 @@ function* deleteAllSelectedWidgetsSaga( } yield put(updateAndSaveLayout(finalWidgets)); + yield put(generateAutoHeightLayoutTreeAction(true, true)); + yield put(selectWidgetInitAction("")); const bulkDeleteKey = selectedWidgets.join(","); if (!disallowUndo) { diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 43ad5ae796..2811adc91d 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1,6 +1,7 @@ import { ReduxAction, ReduxActionErrorTypes, + ReduxActionType, ReduxActionTypes, WidgetReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; @@ -57,6 +58,7 @@ import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/utils"; import { getCurrentPageId, getContainerWidgetSpacesSelector, + getCanvasHeightOffset, } from "selectors/editorSelectors"; import { selectMultipleWidgetsInitAction } from "actions/widgetSelectionActions"; @@ -140,6 +142,55 @@ import { flashElementsById } from "utils/helpers"; import { getSlidingCanvasName } from "constants/componentClassNameConstants"; import { builderURL } from "RouteBuilder"; import history from "utils/history"; +import { updateMultipleWidgetProperties } from "actions/widgetActions"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; + +export function* updateAllChildCanvasHeights( + currentContainerLikeWidgetId: string, + topRow: number, + bottomRow: number, + allWidgets?: CanvasWidgetsReduxState, +) { + const containerLikeWidget: FlattenedWidgetProps = yield select( + getWidget, + currentContainerLikeWidgetId, + ); + let stateWidgets: CanvasWidgetsReduxState | undefined = allWidgets; + if (!stateWidgets) stateWidgets = yield select(getWidgets); + const canvasHeightOffset: number = getCanvasHeightOffset( + containerLikeWidget.type, + containerLikeWidget, + ); + const containerLikeWidgetHeightInPx: number = + (bottomRow - topRow - canvasHeightOffset) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + const widgets = { ...stateWidgets }; + if (Array.isArray(containerLikeWidget.children)) { + containerLikeWidget.children.forEach((childWidgetId: string) => { + const childWidget = { ...widgets[childWidgetId] }; + if (Array.isArray(childWidget.children)) { + const maxChildBottomRow = childWidget.children.reduce((prev, next) => { + return widgets[next].bottomRow > prev + ? widgets[next].bottomRow + : prev; + }, 0); + const maxHeightBasedOnChildrenInPx = + maxChildBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + const finalHeight = Math.max( + containerLikeWidgetHeightInPx, + maxHeightBasedOnChildrenInPx, + ); + widgets[childWidgetId] = { + ...childWidget, + bottomRow: finalHeight, + minHeight: finalHeight, + }; + } + }); + } + return widgets; +} export function* resizeSaga(resizeAction: ReduxAction) { try { @@ -162,7 +213,7 @@ export function* resizeSaga(resizeAction: ReduxAction) { const widgets = { ...stateWidgets }; widget = { ...widget, leftColumn, rightColumn, topRow, bottomRow }; - const movedWidgets: { + let movedWidgets: { [widgetId: string]: FlattenedWidgetProps; } = yield call( reflowWidgets, @@ -172,6 +223,13 @@ export function* resizeSaga(resizeAction: ReduxAction) { snapRowSpace, ); + movedWidgets = yield updateAllChildCanvasHeights( + widgetId, + topRow, + bottomRow, + movedWidgets, + ); + const updatedCanvasBottomRow: number = yield call( getCanvasSizeAfterWidgetMove, parentId, @@ -188,6 +246,7 @@ export function* resizeSaga(resizeAction: ReduxAction) { log.debug("resize computations took", performance.now() - start, "ms"); yield put(stopReflowAction()); yield put(updateAndSaveLayout(movedWidgets)); + yield put(generateAutoHeightLayoutTreeAction(true, true)); } catch (error) { yield put({ type: ReduxActionErrorTypes.WIDGET_OPERATION_ERROR, @@ -520,12 +579,23 @@ export function getPropertiesToUpdate( }; } +export function* getIsContainerLikeWidget(widget: FlattenedWidgetProps) { + const children = widget.children; + if (Array.isArray(children) && children.length > 0) { + const firstChild: FlattenedWidgetProps = yield select( + getWidget, + children[0], + ); + if (firstChild.type === "CANVAS_WIDGET") return true; + } + return false; +} export function* getPropertiesUpdatedWidget( updatesObj: UpdateWidgetPropertyPayload, ) { const { dynamicUpdates, updates, widgetId } = updatesObj; - const { modify = {}, remove = [], triggerPaths } = updates; + const { modify = {}, remove = [], postUpdateAction, triggerPaths } = updates; const stateWidget: WidgetProps = yield select(getWidget, widgetId); @@ -570,7 +640,10 @@ export function* getPropertiesUpdatedWidget( // If there exists another spot in this workflow, where we are iterating over the dynamicTriggerPathList and dynamicBindingPathList, after // performing all updates to the widget, we can piggy back on that iteration to purge orphaned paths // I couldn't find it, so here it is. - return purgeOrphanedDynamicPaths(widget); + return { + updatedWidget: purgeOrphanedDynamicPaths(widget), + actionToDispatch: postUpdateAction, + }; } function* batchUpdateWidgetPropertySaga( @@ -582,12 +655,15 @@ function* batchUpdateWidgetPropertySaga( // Handling the case where sometimes widget id is not passed through here return; } - const updatedWidget: WidgetProps = yield call( - getPropertiesUpdatedWidget, - action.payload, - ); + const updatedWidgetAndActionsToDispatch: { + updatedWidget: WidgetProps; + actionToDispatch?: ReduxActionType; + } = yield call(getPropertiesUpdatedWidget, action.payload); const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets); - const widgets = { ...stateWidgets, [widgetId]: updatedWidget }; + const widgets = { + ...stateWidgets, + [widgetId]: updatedWidgetAndActionsToDispatch.updatedWidget, + }; log.debug( "Batch widget property update calculations took: ", performance.now() - start, @@ -595,6 +671,12 @@ function* batchUpdateWidgetPropertySaga( ); // Save the layout yield put(updateAndSaveLayout(widgets, { shouldReplay })); + if (updatedWidgetAndActionsToDispatch.actionToDispatch) { + yield put({ + type: updatedWidgetAndActionsToDispatch.actionToDispatch, + payload: { widgetId }, + }); + } } function* batchUpdateMultipleWidgetsPropertiesSaga( @@ -603,22 +685,31 @@ function* batchUpdateMultipleWidgetsPropertiesSaga( const start = performance.now(); const { updatesArray } = action.payload; const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets); - const updatedWidgets: WidgetProps[] = yield all( + const updatedWidgetsAndActionsToDispatch: Array<{ + updatedWidget: WidgetProps; + actionToDispatch?: ReduxActionType; + }> = yield all( updatesArray.map((eachUpdate) => { return call(getPropertiesUpdatedWidget, eachUpdate); }), ); - const updatedStateWidgets = updatedWidgets.reduce( - (allWidgets, eachUpdatedWidget) => { + + const updatedStateWidgets = updatedWidgetsAndActionsToDispatch.reduce( + (allWidgets, eachUpdatedWidgetAndActionsToDispatch) => { return { ...allWidgets, - [eachUpdatedWidget.widgetId]: eachUpdatedWidget, + [eachUpdatedWidgetAndActionsToDispatch.updatedWidget.widgetId]: + eachUpdatedWidgetAndActionsToDispatch.updatedWidget, }; }, stateWidgets, ); - const updatedWidgetIds = uniq(updatedWidgets.map((each) => each.widgetId)); + const updatedWidgetIds = uniq( + updatedWidgetsAndActionsToDispatch.map( + (each) => each.updatedWidget.widgetId, + ), + ); log.debug( "Batch multi-widget properties update calculations took: ", @@ -632,6 +723,14 @@ function* batchUpdateMultipleWidgetsPropertiesSaga( updatedWidgetIds, }), ); + for (const updatedWidgetAndActions of updatedWidgetsAndActionsToDispatch) { + if (updatedWidgetAndActions.actionToDispatch) { + yield put({ + type: updatedWidgetAndActions.actionToDispatch, + payload: { widgetId: updatedWidgetAndActions.updatedWidget.widgetId }, + }); + } + } } function* removeWidgetProperties(widget: WidgetProps, paths: string[]) { @@ -733,7 +832,10 @@ function* updateCanvasSize( action: ReduxAction<{ canvasWidgetId: string; snapRows: number }>, ) { const { canvasWidgetId, snapRows } = action.payload; - const canvasWidget: WidgetProps = yield select(getWidget, canvasWidgetId); + const canvasWidget: FlattenedWidgetProps = yield select( + getWidget, + canvasWidgetId, + ); const originalSnapRows = canvasWidget.bottomRow - canvasWidget.topRow; @@ -746,13 +848,14 @@ function* updateCanvasSize( // Check this out when non canvas widgets are updating snapRows // erstwhile: Math.round((rows * props.snapRowSpace) / props.parentRowSpace), yield put( - batchUpdateWidgetProperty( - canvasWidgetId, - { - modify: { bottomRow: newBottomRow }, - }, - false, - ), + updateMultipleWidgetProperties({ + [canvasWidgetId]: [ + { + propertyPath: "bottomRow", + propertyValue: newBottomRow, + }, + ], + }), ); } } @@ -1600,6 +1703,7 @@ function* pasteWidgetSaga( type: ReduxActionTypes.RECORD_RECENTLY_ADDED_WIDGET, payload: newlyCreatedWidgetIds, }); + yield put(generateAutoHeightLayoutTreeAction(true, true)); //if pasting at the bottom of the canvas, then flash it. if (shouldGroup || !newPastingPositionMap) { diff --git a/app/client/src/sagas/WidgetOperationUtils.ts b/app/client/src/sagas/WidgetOperationUtils.ts index cf2f154777..91a21c4b0e 100644 --- a/app/client/src/sagas/WidgetOperationUtils.ts +++ b/app/client/src/sagas/WidgetOperationUtils.ts @@ -1457,16 +1457,13 @@ export const getParentBottomRowAfterAddingWidget = ( ) => { const parentRowSpace = newWidget.parentRowSpace || GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + const newBottomRow = + (newWidget.bottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) * + parentRowSpace; const updateBottomRow = stateParent.type === "CANVAS_WIDGET" && - newWidget.bottomRow * parentRowSpace > stateParent.bottomRow; - return updateBottomRow - ? Math.max( - (newWidget.bottomRow + GridDefaults.CANVAS_EXTENSION_OFFSET) * - parentRowSpace, - stateParent.bottomRow, - ) - : stateParent.bottomRow; + newBottomRow > stateParent.bottomRow; + return updateBottomRow ? newBottomRow : stateParent.bottomRow; }; /** @@ -1674,29 +1671,29 @@ export function mergeDynamicPropertyPaths( /** * returns the BottomRow for CANVAS_WIDGET * @param finalWidgets - * @param parentId + * @param canvasWidgetId */ export function resizeCanvasToLowestWidget( finalWidgets: CanvasWidgetsReduxState, - parentId: string | undefined, + canvasWidgetId: string | undefined, currentBottomRow: number, - mainCanvasMinHeight?: number, //defined only if parentId is MAIN_CONTAINER_ID + mainCanvasMinHeight?: number, //defined only if canvasWidgetId is MAIN_CONTAINER_ID ) { - if (!parentId) return currentBottomRow; + if (!canvasWidgetId) return currentBottomRow; if ( - !finalWidgets[parentId] || - finalWidgets[parentId].type !== "CANVAS_WIDGET" + !finalWidgets[canvasWidgetId] || + finalWidgets[canvasWidgetId].type !== "CANVAS_WIDGET" ) { return currentBottomRow; } const defaultLowestBottomRow = mainCanvasMinHeight || - finalWidgets[parentId].minHeight || + finalWidgets[canvasWidgetId].minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX; - const childIds = finalWidgets[parentId].children || []; + const childIds = finalWidgets[canvasWidgetId].children || []; let lowestBottomRow = 0; // find the lowest row @@ -1709,7 +1706,7 @@ export function resizeCanvasToLowestWidget( }); const canvasOffset = - parentId === MAIN_CONTAINER_WIDGET_ID + canvasWidgetId === MAIN_CONTAINER_WIDGET_ID ? GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET : GridDefaults.CANVAS_EXTENSION_OFFSET; diff --git a/app/client/src/sagas/WidgetSelectionSagas.ts b/app/client/src/sagas/WidgetSelectionSagas.ts index 89e09c473e..cf137c5374 100644 --- a/app/client/src/sagas/WidgetSelectionSagas.ts +++ b/app/client/src/sagas/WidgetSelectionSagas.ts @@ -349,15 +349,17 @@ function* appendSelectedWidgetToUrlSaga( canvasEditorURL = `${builderURL({ pageId: currentPageId, hash: selectedWidgets[0], + persistExistingParams: true, })}`; } else { canvasEditorURL = `${builderURL({ pageId: currentPageId, + persistExistingParams: true, })}`; } if (currentURL !== canvasEditorURL) { - history.push(canvasEditorURL); + history.replace(canvasEditorURL); } } diff --git a/app/client/src/sagas/autoHeightSagas/batcher.ts b/app/client/src/sagas/autoHeightSagas/batcher.ts new file mode 100644 index 0000000000..bb1c518d84 --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/batcher.ts @@ -0,0 +1,41 @@ +import { + ReduxAction, + ReduxActionTypes, +} from "@appsmith/constants/ReduxActionConstants"; +import { UpdateWidgetAutoHeightPayload } from "actions/autoHeightActions"; +import log from "loglevel"; +import { put, select } from "redux-saga/effects"; +import { getIsDraggingOrResizing } from "selectors/widgetSelectors"; + +// eslint-disable-next-line no-var +var autoHeightUpdateWidgetsQueue: Record = {}; + +export function addWidgetToAutoHeightUpdateQueue( + widgetId: string, + height: number, +) { + if (autoHeightUpdateWidgetsQueue[widgetId] !== height) { + autoHeightUpdateWidgetsQueue[widgetId] = height; + } +} + +export function resetAutoHeightUpdateQueue() { + autoHeightUpdateWidgetsQueue = {}; +} + +export function getAutoHeightUpdateQueue() { + return autoHeightUpdateWidgetsQueue; +} + +export function* batchCallsToUpdateWidgetAutoHeightSaga( + action: ReduxAction, +) { + const isLayoutUpdating: boolean = yield select(getIsDraggingOrResizing); + const { height, widgetId } = action.payload; + log.debug("Dynamic height: batching update:", { widgetId, height }); + addWidgetToAutoHeightUpdateQueue(widgetId, height); + if (isLayoutUpdating) return; + yield put({ + type: ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES, + }); +} diff --git a/app/client/src/sagas/autoHeightSagas/containers.ts b/app/client/src/sagas/autoHeightSagas/containers.ts new file mode 100644 index 0000000000..f919cc631b --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/containers.ts @@ -0,0 +1,199 @@ +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { GridDefaults } from "constants/WidgetConstants"; +import log from "loglevel"; +import { AutoHeightLayoutTreeReduxState } from "reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer"; +import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer"; +import { call, put, select } from "redux-saga/effects"; +import { getMinHeightBasedOnChildren, shouldWidgetsCollapse } from "./helpers"; +import { getWidgets } from "sagas/selectors"; +import { getCanvasHeightOffset } from "selectors/editorSelectors"; +import { getAutoHeightLayoutTree } from "selectors/autoHeightSelectors"; +import { FlattenedWidgetProps } from "widgets/constants"; +import { + getWidgetMaxAutoHeight, + getWidgetMinAutoHeight, + isAutoHeightEnabledForWidget, +} from "widgets/WidgetUtils"; +import { getChildOfContainerLikeWidget } from "./helpers"; +import { getDataTree } from "selectors/dataTreeSelectors"; +import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory"; + +export function* dynamicallyUpdateContainersSaga() { + const start = performance.now(); + + const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets); + const canvasWidgets: FlattenedWidgetProps[] | undefined = Object.values( + stateWidgets, + ).filter((widget: FlattenedWidgetProps) => { + const isCanvasWidget = widget.type === "CANVAS_WIDGET"; + const parent = widget.parentId ? stateWidgets[widget.parentId] : undefined; + if (parent?.type === "LIST_WIDGET") return false; + if (parent === undefined) return false; + return isCanvasWidget; + }); + + const dynamicHeightLayoutTree: AutoHeightLayoutTreeReduxState = yield select( + getAutoHeightLayoutTree, + ); + + const updates: Record = {}; + const shouldCollapse: boolean = yield call(shouldWidgetsCollapse); + + for (const canvasWidget of canvasWidgets) { + if (canvasWidget.parentId) { + // The parent widget of this canvas widget + const parentContainerWidget = stateWidgets[canvasWidget.parentId]; + + // Skip this whole process if the parent is collapsed: Process: + // Get the DataTree + const dataTree: DataTree = yield select(getDataTree); + // Get this parentContainerWidget from the DataTree + const dataTreeWidget = dataTree[parentContainerWidget.widgetName]; + // If the widget exists, is not visible and we can collapse widgets + if ( + dataTreeWidget && + (dataTreeWidget as DataTreeWidget).isVisible !== true && + shouldCollapse + ) + continue; + + let bottomRow, topRow; + // If the parent exists in the layout tree + if (dynamicHeightLayoutTree[parentContainerWidget.widgetId]) { + // Get the tree node for the parent + const layoutNode = + dynamicHeightLayoutTree[parentContainerWidget.widgetId]; + // Get all the dimensions from the tree node + bottomRow = layoutNode.bottomRow; + topRow = layoutNode.topRow; + } else { + // If it doesn't exist in the layout tree + // It is most likely a Modal Widget + // Use the dimensions as they exist in the widget. + bottomRow = parentContainerWidget.bottomRow; + topRow = parentContainerWidget.topRow; + } + + // If this is a Modal widget or some other widget + // which is detached from layout + // use the value 0, as the starting point. + if ( + parentContainerWidget.detachFromLayout && + parentContainerWidget.height + ) { + topRow = 0; + } + + if (isAutoHeightEnabledForWidget(parentContainerWidget)) { + // Get the child we need to consider + // For a container widget, it will be the child canvas + // For a tabs widget, it will be the currently open tab's canvas + const childWidgetId: + | string + | undefined = yield getChildOfContainerLikeWidget( + parentContainerWidget, + ); + + // This can be different from the canvas widget in consideration + // For example, if this canvas widget in consideration + // is not the selected tab's canvas in a tabs widget + // we don't have to consider it at all + if (childWidgetId !== canvasWidget.widgetId) continue; + + // Get the boundaries for possible min and max dynamic height. + const minDynamicHeightInRows = getWidgetMinAutoHeight( + parentContainerWidget, + ); + const maxDynamicHeightInRows = getWidgetMaxAutoHeight( + parentContainerWidget, + ); + + // Default to the min height expected. + let maxBottomRow = minDynamicHeightInRows; + + // For the child Canvas, use the value in pixels. + let canvasBottomRow = maxBottomRow + 0; + + // For widgets like Tabs Widget, some of the height is occupied by the + // tabs themselves, the child canvas as a result has less number of rows available + // To accommodate for this, we need to increase the new height by the offset amount. + const canvasHeightOffset: number = getCanvasHeightOffset( + parentContainerWidget.type, + parentContainerWidget, + ); + + // If this canvas has children + // we need to consider the bottom most child for the height + if ( + Array.isArray(canvasWidget.children) && + canvasWidget.children.length > 0 + ) { + let maxBottomRowBasedOnChildren: number = yield getMinHeightBasedOnChildren( + canvasWidget.widgetId, + {}, + true, + dynamicHeightLayoutTree, + ); + // Add a canvas extension offset + maxBottomRowBasedOnChildren += GridDefaults.CANVAS_EXTENSION_OFFSET; + // Set the canvas bottom row as a new variable with a new reference + canvasBottomRow = maxBottomRowBasedOnChildren + 0; + + // Add the offset to the total height of the parent widget + maxBottomRowBasedOnChildren += canvasHeightOffset; + + // Get the larger value between the minDynamicHeightInRows and bottomMostRowForChild + maxBottomRow = Math.max(maxBottomRowBasedOnChildren, maxBottomRow); + } else { + canvasBottomRow = maxBottomRow - canvasHeightOffset; + } + + // The following makes sure we stay within bounds + // If the new height is below the min threshold + if (maxBottomRow < minDynamicHeightInRows) { + maxBottomRow = minDynamicHeightInRows; + } + // If the new height is above the max threshold + if (maxBottomRow > maxDynamicHeightInRows) { + maxBottomRow = maxDynamicHeightInRows; + } + + canvasBottomRow = + Math.max(maxBottomRow - canvasHeightOffset, canvasBottomRow) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + // If we have a new height to set and + // If the canvas for some reason doesn't have the correct bottomRow + if ( + maxBottomRow !== bottomRow - topRow || + canvasBottomRow !== canvasWidget.bottomRow + ) { + if (!updates.hasOwnProperty(parentContainerWidget.widgetId)) { + updates[parentContainerWidget.widgetId] = + maxBottomRow * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + } + } + } + } + } + + log.debug("Dynamic Height: Container Updates", { updates }); + + if (Object.keys(updates).length > 0) { + // TODO(abhinav): Make sure there are no race conditions or scenarios where these updates are not considered. + for (const widgetId in updates) { + yield put({ + type: ReduxActionTypes.UPDATE_WIDGET_AUTO_HEIGHT, + payload: { + widgetId, + height: updates[widgetId], + }, + }); + } + } + log.debug( + "Dynamic height: Container computations time taken:", + performance.now() - start, + "ms", + ); +} diff --git a/app/client/src/sagas/autoHeightSagas/helpers.ts b/app/client/src/sagas/autoHeightSagas/helpers.ts new file mode 100644 index 0000000000..4ba1473217 --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/helpers.ts @@ -0,0 +1,145 @@ +import { GridDefaults } from "constants/WidgetConstants"; +import { APP_MODE } from "entities/App"; +import { AutoHeightLayoutTreeReduxState } from "reducers/entityReducers/autoHeightReducers/autoHeightLayoutTreeReducer"; +import { + CanvasWidgetsReduxState, + FlattenedWidgetProps, +} from "reducers/entityReducers/canvasWidgetsReducer"; +import { select } from "redux-saga/effects"; +import { getWidgetMetaProps, getWidgets } from "sagas/selectors"; +import { + getCanvasHeightOffset, + previewModeSelector, +} from "selectors/editorSelectors"; +import { getAppMode } from "selectors/entitiesSelector"; +import { TreeNode } from "utils/autoHeight/constants"; + +export function* shouldWidgetsCollapse() { + const isPreviewMode: boolean = yield select(previewModeSelector); + const appMode: APP_MODE = yield select(getAppMode); + + return isPreviewMode || appMode === APP_MODE.PUBLISHED; +} + +export function* getChildOfContainerLikeWidget( + containerLikeWidget: FlattenedWidgetProps, +) { + // Todo: Abstraction leak (abhinav): This is an abstraction leak + // I don't have a better solution right now. + // What we're trying to acheive is to skip the canvas which + // is not currently visible in the tabs widget. + if (containerLikeWidget.type === "TABS_WIDGET") { + // Get the current tabs widget meta + const tabsMeta: { selectedTabWidgetId: string } | undefined = yield select( + getWidgetMetaProps, + containerLikeWidget.widgetId, + ); + // If we have a meta for the tabs widget + if (tabsMeta) return tabsMeta.selectedTabWidgetId; + + // If there are not meta values for the tabs widget + // we get the first tab using the `index` + const firstTab = Object.values( + containerLikeWidget.tabsObj as Record< + string, + { widgetId: string; index: number } + >, + ).find((entry: { widgetId: string; index: number }) => entry.index === 0); + + return firstTab?.widgetId; + } else if (Array.isArray(containerLikeWidget.children)) { + // First child of a container like widget will be the canvas widget within in + // Note: If we have this feature for List Widget, we will need to consider it. + return containerLikeWidget.children[0]; + } +} + +export function getParentCurrentHeightInRows( + tree: Record, + parentId: string, + changesSoFar: Record, +) { + // Get the parentHeight in rows + let parentHeightInRows = tree[parentId].bottomRow - tree[parentId].topRow; + + // If the parent has changed so far. + if (changesSoFar.hasOwnProperty(parentId)) { + parentHeightInRows = + changesSoFar[parentId].bottomRow - changesSoFar[parentId].topRow; + } + return parentHeightInRows; +} + +export function* getMinHeightBasedOnChildren( + widgetId: string, + changesSoFar: Record, + ignoreParent = false, + tree: AutoHeightLayoutTreeReduxState, +) { + // Starting with no height + let minHeightInRows = 0; + + // Should we be able to collapse widgets + const shouldCollapse: boolean = yield shouldWidgetsCollapse(); + // Get all widgets in the DSL + const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets); + + const { children = [], parentId } = stateWidgets[widgetId]; + // If we need to consider the parent height + if (parentId && !ignoreParent) { + const parent = stateWidgets[parentId]; + const parentHeightInRows = getParentCurrentHeightInRows( + tree, + parentId, + changesSoFar, + ); + // The canvas will be an extension smaller than the parent? + minHeightInRows = parentHeightInRows - GridDefaults.CANVAS_EXTENSION_OFFSET; + + // We will also remove any extra offsets the parent has + // As we're dealing with the child canvas widget here. + const canvasHeightOffset: number = getCanvasHeightOffset( + parent.type, + parent, + ); + minHeightInRows = minHeightInRows - canvasHeightOffset; + // If the canvas is empty return the parent's height in rows, without + // the canvas extension offset + if (!children.length) { + return minHeightInRows; + } + } + + // For each child widget id. + for (const childWidgetId of children) { + // If we've changed the widget's bottomRow via computations + const { detachFromLayout } = stateWidgets[childWidgetId]; + // We ignore widgets like ModalWidget which don't occupy parent's space. + // detachFromLayout helps us identify such widgets + if (detachFromLayout) continue; + + // Get the child widget's dimenstions from the tree + const { bottomRow, topRow } = tree[childWidgetId]; + + // If this child has changed so far during computations + if (changesSoFar.hasOwnProperty(childWidgetId)) { + const collapsing = + changesSoFar[childWidgetId].bottomRow === + changesSoFar[childWidgetId].topRow; + + // If this child is collapsing, don't consider it + if (!(shouldCollapse && collapsing)) + minHeightInRows = Math.max( + minHeightInRows, + changesSoFar[childWidgetId].bottomRow, + ); + // If we need to get the existing bottomRow from the state + } else { + // If this child is to collapse, don't consider it. + if (!(shouldCollapse && bottomRow === topRow)) + minHeightInRows = Math.max(minHeightInRows, bottomRow); + } + } + + return minHeightInRows; +} diff --git a/app/client/src/sagas/autoHeightSagas/index.ts b/app/client/src/sagas/autoHeightSagas/index.ts new file mode 100644 index 0000000000..003817cff0 --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/index.ts @@ -0,0 +1,33 @@ +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { all, debounce, takeEvery, takeLatest } from "redux-saga/effects"; +import { batchCallsToUpdateWidgetAutoHeightSaga } from "./batcher"; +import { dynamicallyUpdateContainersSaga } from "./containers"; +import { generateTreeForAutoHeightComputations } from "./layoutTree"; +import { updateWidgetAutoHeightSaga } from "./widgets"; + +export default function* autoHeightSagas() { + yield all([ + takeLatest( + [ + ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT, + ReduxActionTypes.SET_PREVIEW_MODE, + ], + dynamicallyUpdateContainersSaga, + ), + takeEvery( + ReduxActionTypes.UPDATE_WIDGET_AUTO_HEIGHT, + batchCallsToUpdateWidgetAutoHeightSaga, + ), + debounce( + 50, + ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES, + updateWidgetAutoHeightSaga, + ), + takeLatest( + [ + ReduxActionTypes.GENERATE_AUTO_HEIGHT_LAYOUT_TREE, // add, move, paste, cut, delete, undo/redo + ], + generateTreeForAutoHeightComputations, + ), + ]); +} diff --git a/app/client/src/sagas/autoHeightSagas/layoutTree.ts b/app/client/src/sagas/autoHeightSagas/layoutTree.ts new file mode 100644 index 0000000000..9bfc651668 --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/layoutTree.ts @@ -0,0 +1,69 @@ +import { + ReduxAction, + ReduxActionTypes, +} from "@appsmith/constants/ReduxActionConstants"; +import { + checkContainersForAutoHeightAction, + setAutoHeightLayoutTreeAction, +} from "actions/autoHeightActions"; +import log from "loglevel"; +import { put, select } from "redux-saga/effects"; +import { getAutoHeightLayoutTree } from "selectors/autoHeightSelectors"; +import { getOccupiedSpacesGroupedByParentCanvas } from "selectors/editorSelectors"; +import { TreeNode } from "utils/autoHeight/constants"; +import { generateTree } from "utils/autoHeight/generateTree"; +import { shouldWidgetsCollapse } from "./helpers"; + +export function* getLayoutTree(layoutUpdated: boolean) { + const start = performance.now(); + + const shouldCollapse: boolean = yield shouldWidgetsCollapse(); + const { canvasLevelMap, occupiedSpaces } = yield select( + getOccupiedSpacesGroupedByParentCanvas, + ); + + // TODO PERF:(abhinav): Memoize this or something, in case the `UPDATE_LAYOUT` did not cause a change in + // widget positions and sizes + let tree: Record = {}; + const previousTree: Record = yield select( + getAutoHeightLayoutTree, + ); + for (const canvasWidgetId in occupiedSpaces) { + if (occupiedSpaces[canvasWidgetId].length > 0) { + const treeForThisCanvas = generateTree( + occupiedSpaces[canvasWidgetId], + !shouldCollapse && layoutUpdated, + previousTree, + ); + tree = Object.assign({}, tree, treeForThisCanvas); + } + } + log.debug( + "Dynamic Height: Tree generation time taken:", + performance.now() - start, + "ms", + ); + return { canvasLevelMap, tree }; +} + +export function* generateTreeForAutoHeightComputations( + action: ReduxAction<{ + shouldCheckContainersForAutoHeightUpdates: boolean; + layoutUpdated: boolean; + }>, +) { + const { canvasLevelMap, tree } = yield getLayoutTree( + action.payload.layoutUpdated, + ); + yield put(setAutoHeightLayoutTreeAction(tree, canvasLevelMap)); + const { shouldCheckContainersForAutoHeightUpdates } = action.payload; + + if (shouldCheckContainersForAutoHeightUpdates) { + yield put({ + type: ReduxActionTypes.PROCESS_AUTO_HEIGHT_UPDATES, + }); + yield put(checkContainersForAutoHeightAction()); + } + + return tree; +} diff --git a/app/client/src/sagas/autoHeightSagas/widgets.ts b/app/client/src/sagas/autoHeightSagas/widgets.ts new file mode 100644 index 0000000000..abaab1f7ca --- /dev/null +++ b/app/client/src/sagas/autoHeightSagas/widgets.ts @@ -0,0 +1,598 @@ +import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants"; +import { + GridDefaults, + MAIN_CONTAINER_WIDGET_ID, +} from "constants/WidgetConstants"; +import { groupBy, uniq } from "lodash"; +import log from "loglevel"; +import { + CanvasWidgetsReduxState, + UpdateWidgetsPayload, +} from "reducers/entityReducers/canvasWidgetsReducer"; +import { put, select } from "redux-saga/effects"; +import { getWidgets } from "sagas/selectors"; +import { getCanvasHeightOffset } from "selectors/editorSelectors"; +import { FlattenedWidgetProps } from "widgets/constants"; +import { + getWidgetMaxAutoHeight, + getWidgetMinAutoHeight, + isAutoHeightEnabledForWidget, +} from "widgets/WidgetUtils"; +import { + getAutoHeightUpdateQueue, + resetAutoHeightUpdateQueue, +} from "./batcher"; +import { + getChildOfContainerLikeWidget, + getMinHeightBasedOnChildren, + getParentCurrentHeightInRows, + shouldWidgetsCollapse, +} from "./helpers"; +import { updateMultipleWidgetPropertiesAction } from "actions/controlActions"; +import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; +import { computeChangeInPositionBasedOnDelta } from "utils/autoHeight/reflow"; +import { CanvasLevelsReduxState } from "reducers/entityReducers/autoHeightReducers/canvasLevelsReducer"; +import { getCanvasLevelMap } from "selectors/autoHeightSelectors"; +import { getLayoutTree } from "./layoutTree"; + +/* TODO(abhinav) + hasScroll is no longer needed, as the only way we will be computing for hasScroll, is when we get the updates + from the Container computations saga. In container computations, we also compute the inner canvas height. So, + this becomes a duplicate run of pretty much the same code. + + In most cases, when we run the getMinHeightBasedOnChildren, we add the CANVAS_EXTENSION_OFFSET and the offset + from the widget configuration. This means that we can DRY this by moving them into the getMinHeightBasedOnChildren function + + The computations we do when a widget changes for its parent, is pretty much the same as the ones we do in container + computations saga, so we can potentially re-use that code. + + Adding to widgetsToUpdate can be done using one function and shrink this saga by a large amount + + + +/** + * Saga to update a widget's auto height + * When a widget changes in height, it must do the following + * - Make sure any parent that should also change height accordingly, does so + * - Make sure any widget that needs to reposition due to the above changes, does so + * + * + * TODO: PERF_TRACK(abhinav): Make sure to benchmark the computations. + * We need to propagate changes within 10ms + */ +export function* updateWidgetAutoHeightSaga() { + const updates = getAutoHeightUpdateQueue(); + log.debug("Dynamic Height: updates to process", { updates }); + const start = performance.now(); + let shouldRecomputeContainers = false; + + const shouldCollapse: boolean = yield shouldWidgetsCollapse(); + + const { tree: dynamicHeightLayoutTree } = yield getLayoutTree(false); + + // Get all widgets from canvasWidgetsReducer + const stateWidgets: CanvasWidgetsReduxState = yield select(getWidgets); + + // Initialise all the widgets we will be updating + const widgetsToUpdate: UpdateWidgetsPayload = {}; + + // Initialise all expected updates + const expectedUpdates: Array<{ + widgetId: string; + expectedHeightinPx: number; + expectedChangeInHeightInRows: number; + currentTopRow: number; + currentBottomRow: number; + expectedBottomRow: number; + parentId?: string; + hasScroll?: boolean; + }> = []; + + // For each widget which have new heights to update. + for (const widgetId in updates) { + // Get the widget from the reducer. + const widget: FlattenedWidgetProps = stateWidgets[widgetId]; + // If this widget exists (not sure why this needs to be here) + if (widget && !widget.detachFromLayout) { + // Get the boundaries for possible min and max dynamic height. + let minDynamicHeightInPixels = + getWidgetMinAutoHeight(widget) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + if (widget.type === "TABS_WIDGET") shouldRecomputeContainers = true; + + // In case of a widget going invisible in view mode + if (updates[widgetId] === 0) { + if (shouldCollapse && isAutoHeightEnabledForWidget(widget)) { + minDynamicHeightInPixels = 0; + } else continue; + } + + const maxDynamicHeightInPixels = + getWidgetMaxAutoHeight(widget) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + let newHeightInPixels = updates[widgetId]; + + // If the new height is below the min threshold + if (newHeightInPixels < minDynamicHeightInPixels) { + newHeightInPixels = minDynamicHeightInPixels; + } + // If the new height is above the max threshold + if (newHeightInPixels > maxDynamicHeightInPixels) { + newHeightInPixels = maxDynamicHeightInPixels; + } + + const expectedHeightInRows = Math.ceil( + newHeightInPixels / GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + ); + + const treeLayoutData = dynamicHeightLayoutTree[widget.widgetId]; + + const currentHeightInRows = + treeLayoutData.bottomRow - treeLayoutData.topRow; + + // Push the updates into the initialised array. + expectedUpdates.push({ + widgetId, + expectedHeightinPx: newHeightInPixels, + expectedChangeInHeightInRows: + expectedHeightInRows - currentHeightInRows, + currentTopRow: treeLayoutData.topRow, + currentBottomRow: treeLayoutData.bottomRow, + expectedBottomRow: Math.ceil( + treeLayoutData.topRow + + newHeightInPixels / GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + ), + parentId: widget.parentId, + }); + } else if (widget) { + // For widgets like Modal Widget. (Rather this assumes that it is only the modal widget which needs a change) + const newHeight = updates[widgetId]; + + // Setting the height and dimensions of the Modal Widget + widgetsToUpdate[widgetId] = [ + { + propertyPath: "height", + propertyValue: newHeight, + }, + { + propertyPath: "bottomRow", + propertyValue: widget.topRow + newHeight, + }, + { + propertyPath: "topRow", + propertyValue: widget.topRow, + }, + ]; + // Setting the child canvas widget's dimensions in the Modal Widget + if (Array.isArray(widget.children) && widget.children.length === 1) { + widgetsToUpdate[widget.children[0]] = [ + { + propertyPath: "minHeight", + propertyValue: newHeight, + }, + { + propertyPath: "bottomRow", + propertyValue: newHeight, + }, + { + propertyPath: "topRow", + propertyValue: 0, + }, + ]; + } + } + } + + // If there are updates. + if (expectedUpdates.length > 0) { + // Get the canvas level map from the store + // This map tells us the nesting of each canvas widget in the DSL. + // MainContainer's level is 0. + const canvasLevelMap: CanvasLevelsReduxState = yield select( + getCanvasLevelMap, + ); + // 1. Get all siblings together. + + // Get all updates for that level. + // Move up a level, add it to the expectedUpdatesGroupedByParent. A new entry if unavailable, or append to an existing entry, + // Repeat for the next level. + const expectedUpdatesGroupedByParentCanvasWidget = groupBy( + expectedUpdates, + "parentId", + ); + + // Initialise a map of the levels and canvaswidgetIds at that level. + const parentCanvasWidgetsGroupedByLevel: { [level: string]: string[] } = { + "0": [MAIN_CONTAINER_WIDGET_ID], + }; + + let maxLevel = 0; + + // For each canvas widget which has updates. + for (const parentCanvasWidgetId in expectedUpdatesGroupedByParentCanvasWidget) { + // Get the level of the canvas widget + const _level = canvasLevelMap[parentCanvasWidgetId]; + + // If the level is higher than the previous level, increment maxLevel + if (_level > maxLevel) maxLevel = _level; + + // update the map with the canvas widgets in the current level. + parentCanvasWidgetsGroupedByLevel[_level] = uniq([ + ...(parentCanvasWidgetsGroupedByLevel[_level] || []), + parentCanvasWidgetId, + ]); + } + + // Initialise a list of changes so far. + // This contains a map of widgetIds with their new topRow and bottomRow + let changesSoFar: Record< + string, + { topRow: number; bottomRow: number } + > = {}; + + // start with the bottom most level (maxLevel) + // We do this so, that we don't have to re-comupte the higher levels, + // as children can modify their parent sizes. + for (let level = maxLevel; level >= 0; level--) { + // The canvas widgets at this level. + const parentCanvasWidgetsToConsider = + parentCanvasWidgetsGroupedByLevel[level]; + const delta: Record = {}; + + if ( + Array.isArray(parentCanvasWidgetsToConsider) && + parentCanvasWidgetsToConsider.length > 0 + ) { + // For each canvas widget at this level. + parentCanvasWidgetsToConsider.forEach((parentCanvasWidgetId) => { + // If we have expected updates for this widget already + if ( + expectedUpdatesGroupedByParentCanvasWidget.hasOwnProperty( + parentCanvasWidgetId, + ) + ) { + // For each widget to update, add to the delta, the expected change. + expectedUpdatesGroupedByParentCanvasWidget[ + parentCanvasWidgetId + ].forEach((update) => { + delta[ + (update as { + widgetId: string; + expectedChangeInHeightInRows: number; + }).widgetId + ] = (update as { + widgetId: string; + expectedChangeInHeightInRows: number; + }).expectedChangeInHeightInRows; + }); + } + }); + } + + if (Object.keys(delta).length > 0) { + // 2. Run the reflow computations for this parent's child updates + const siblingWidgetsToUpdate = computeChangeInPositionBasedOnDelta( + dynamicHeightLayoutTree, + delta, + ); + + // Add to the changes so far, the changes computed for this canvas widget's children. + changesSoFar = Object.assign(changesSoFar, siblingWidgetsToUpdate); + + // Repeat the previous loop, we need to do this, because we need the changesSoFar + // populated before we can reliably work on the parents + for (const parentCanvasWidgetId of parentCanvasWidgetsToConsider) { + // Get the current canvas Widget props + const parentCanvasWidget: FlattenedWidgetProps = + stateWidgets[parentCanvasWidgetId]; + // If this canvas widget has a parent then it is not the MainContainer + if (parentCanvasWidget.parentId) { + // Get the parent widget. This could be Tabs, Modal, Container, Form, etc. + // As these widgets have canvas children. + const parentContainerLikeWidget: FlattenedWidgetProps = + stateWidgets[parentCanvasWidget.parentId]; + + let minCanvasHeightInRows: number = yield getMinHeightBasedOnChildren( + parentCanvasWidget.widgetId, + changesSoFar, + true, + dynamicHeightLayoutTree, + ); + + // Add extra rows, this is to accommodate for padding and margins in the parent + minCanvasHeightInRows += GridDefaults.CANVAS_EXTENSION_OFFSET; + + // For widgets like Tabs Widget, some of the height is occupied by the + // tabs themselves, the child canvas as a result has less number of rows available + // To accommodate for this, we need to increase the new height by the offset amount. + const canvasHeightOffset: number = getCanvasHeightOffset( + parentContainerLikeWidget.type, + parentContainerLikeWidget, + ); + + // Widgets need to consider changing heights, only if they have dynamic height + // enabled. + if (isAutoHeightEnabledForWidget(parentContainerLikeWidget)) { + // Get the minimum number of rows this parent must have + + let minHeightInRows = getWidgetMinAutoHeight( + parentContainerLikeWidget, + ); + + minHeightInRows = Math.max( + minHeightInRows, + minCanvasHeightInRows + canvasHeightOffset, + ); + + // Setting this in a variable, as this will be the total scroll height in the canvas. + const minCanvasHeightInPixels = + (minHeightInRows - canvasHeightOffset) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + // We need to make sure that the canvas widget doesn't have + // any extra scroll, to this end, we need to add the `minHeight` update + // for the canvas widgets. Canvas Widgets are never updated in other flows + // As they simply take up whatever space the parent has, but this doesn't effect + // the `minHeight`, which leads to scroll if the `minHeight` is a larger value. + // Also, for canvas widgets, the values are in pure pixels instead of rows. + widgetsToUpdate[parentCanvasWidgetId] = [ + { + propertyPath: "bottomRow", + propertyValue: minCanvasHeightInPixels, + }, + { + propertyPath: "minHeight", + propertyValue: minCanvasHeightInPixels, + }, + ]; + + // Make sure we're not overflowing the max height bounds + const maxDynamicHeight = getWidgetMaxAutoHeight( + parentContainerLikeWidget, + ); + + minHeightInRows = Math.min(maxDynamicHeight, minHeightInRows); + + let layoutData = + dynamicHeightLayoutTree[parentContainerLikeWidget.widgetId]; + + if (layoutData === undefined) { + layoutData = parentContainerLikeWidget; + } + + // Convert this change into the standard expected update format. + const expectedUpdate = { + widgetId: parentContainerLikeWidget.widgetId, + expectedHeightinPx: + minHeightInRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + expectedChangeInHeightInRows: + minHeightInRows - (layoutData.bottomRow - layoutData.topRow), + currentTopRow: layoutData.topRow, + currentBottomRow: layoutData.bottomRow, + expectedBottomRow: layoutData.topRow + minHeightInRows, + parentId: parentContainerLikeWidget.parentId, + }; + + // If this widget is actually removed from the layout + // For example, if this is a ModalWidget + // We need to make sure that we change properties other than bottomRow and topRow + // In this case we're updating minHeight and height as well. + if (parentContainerLikeWidget.detachFromLayout) { + // DRY this + widgetsToUpdate[parentContainerLikeWidget.widgetId] = [ + { + propertyPath: "bottomRow", + propertyValue: minHeightInRows, + }, + { + propertyPath: "height", + propertyValue: + (minHeightInRows + GridDefaults.CANVAS_EXTENSION_OFFSET) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + }, + { + propertyPath: "minHeight", + propertyValue: + (minHeightInRows + GridDefaults.CANVAS_EXTENSION_OFFSET) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + }, + ]; + } + + // If this is not a widget which is outside of the layout, + // We must check if it has a parent + // It most likely will, as this widget cannot be the MainContainer + // The maincontainer is a Canvas Widget, not a container like widget. + if ( + !parentContainerLikeWidget.detachFromLayout && + parentContainerLikeWidget.parentId + ) { + // If this widget's parent canvas already has some updates + // We push this update to the existing array. + // DRY THIS + if ( + expectedUpdatesGroupedByParentCanvasWidget.hasOwnProperty( + parentContainerLikeWidget.parentId, + ) + ) { + expectedUpdatesGroupedByParentCanvasWidget[ + parentContainerLikeWidget.parentId + ].push(expectedUpdate); + } else { + // Otherwise, we add a new entry. + expectedUpdatesGroupedByParentCanvasWidget[ + parentContainerLikeWidget.parentId + ] = [expectedUpdate]; + } + + // The parent might not have been added to the previously created group + // parentCanvasWidgetGroupedByLevel + const _level = + canvasLevelMap[parentContainerLikeWidget.parentId]; + // So, we add it, if it is not the MainContainer. + // This way it will be used in parentCanvasWidgetsToConsider + // MainContainer was added when we initialised this variable, + // so we're skipping it. level === 0 is true only for the MainContainer. + if (_level !== 0) { + // DRY THIS + parentCanvasWidgetsGroupedByLevel[_level] = uniq([ + ...(parentCanvasWidgetsGroupedByLevel[_level] || []), + parentContainerLikeWidget.parentId, + ]); + } + } + } else { + let parentContainerHeightInRows = getParentCurrentHeightInRows( + dynamicHeightLayoutTree, + parentContainerLikeWidget.widgetId, + changesSoFar, + ); + + parentContainerHeightInRows -= canvasHeightOffset; + + // Setting this in a variable, as this will be the total scroll height in the canvas. + const minCanvasHeightInPixels = + Math.max(minCanvasHeightInRows, parentContainerHeightInRows) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + // We need to make sure that the canvas widget doesn't have + // any extra scroll, to this end, we need to add the `minHeight` update + // for the canvas widgets. Canvas Widgets are never updated in other flows + // As they simply take up whatever space the parent has, but this doesn't effect + // the `minHeight`, which leads to scroll if the `minHeight` is a larger value. + // Also, for canvas widgets, the values are in pure pixels instead of rows. + widgetsToUpdate[parentCanvasWidgetId] = [ + { + propertyPath: "bottomRow", + propertyValue: minCanvasHeightInPixels, + }, + { + propertyPath: "minHeight", + propertyValue: minCanvasHeightInPixels, + }, + ]; + } + } + } + } + } + // Let's consider the minimum Canvas Height + let maxCanvasHeightInRows = + CANVAS_DEFAULT_MIN_HEIGHT_PX / GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + // The same logic to compute the minimum height of the MainContainer + // Based on how many rows are being occuped by children. + + const maxPossibleCanvasHeightInRows: number = yield getMinHeightBasedOnChildren( + MAIN_CONTAINER_WIDGET_ID, + changesSoFar, + true, + dynamicHeightLayoutTree, + ); + + maxCanvasHeightInRows = Math.max( + maxPossibleCanvasHeightInRows, + maxCanvasHeightInRows, + ); + + // Add the MainContainer's update. + widgetsToUpdate[MAIN_CONTAINER_WIDGET_ID] = [ + { + propertyPath: "bottomRow", + propertyValue: + (maxCanvasHeightInRows + GridDefaults.MAIN_CANVAS_EXTENSION_OFFSET) * + GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + }, + ]; + + // Convert the changesSoFar (this are the computed changes) + // To the widgetsToUpdate data structure for final reducer update. + + for (const changedWidgetId in changesSoFar) { + const { originalBottomRow, originalTopRow } = dynamicHeightLayoutTree[ + changedWidgetId + ]; + + widgetsToUpdate[changedWidgetId] = [ + { + propertyPath: "bottomRow", + propertyValue: changesSoFar[changedWidgetId].bottomRow, + }, + { + propertyPath: "topRow", + propertyValue: changesSoFar[changedWidgetId].topRow, + }, + { + propertyPath: "originalTopRow", + propertyValue: originalTopRow, + }, + { + propertyPath: "originalBottomRow", + propertyValue: originalBottomRow, + }, + ]; + const containerLikeWidget = stateWidgets[changedWidgetId]; + + if ( + Array.isArray(containerLikeWidget.children) && + containerLikeWidget.children.length > 0 + ) { + const childWidgetId: + | string + | undefined = yield getChildOfContainerLikeWidget( + containerLikeWidget, + ); + + if (childWidgetId) { + const childCanvasWidget = stateWidgets[childWidgetId]; + const isCanvasWidget = childCanvasWidget?.type === "CANVAS_WIDGET"; + if (isCanvasWidget) { + let canvasHeight: number = yield getMinHeightBasedOnChildren( + childWidgetId, + changesSoFar, + false, + dynamicHeightLayoutTree, + ); + canvasHeight += GridDefaults.CANVAS_EXTENSION_OFFSET; + + const propertyUpdates = [ + { + propertyPath: "minHeight", + propertyValue: + canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + }, + { + propertyPath: "bottomRow", + propertyValue: + canvasHeight * GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + }, + ]; + + containerLikeWidget.children.forEach((childWidgetId) => { + if (!widgetsToUpdate.hasOwnProperty(childWidgetId)) { + widgetsToUpdate[childWidgetId] = propertyUpdates; + } + }); + } + } + } + } + } + + log.debug("Dynamic height: Widgets to update:", { widgetsToUpdate }); + + if (Object.keys(widgetsToUpdate).length > 0) { + // Push all updates to the CanvasWidgetsReducer. + // Note that we're not calling `UPDATE_LAYOUT` + // as we don't need to trigger an eval + yield put(updateMultipleWidgetPropertiesAction(widgetsToUpdate)); + resetAutoHeightUpdateQueue(); + yield put( + generateAutoHeightLayoutTreeAction(shouldRecomputeContainers, false), + ); + } + + log.debug( + "Dynamic Height: Overall time taken: ", + performance.now() - start, + "ms", + ); +} diff --git a/app/client/src/selectors/apiPaneSelectors.ts b/app/client/src/selectors/apiPaneSelectors.ts index bd2c5bbd26..4cc5091064 100644 --- a/app/client/src/selectors/apiPaneSelectors.ts +++ b/app/client/src/selectors/apiPaneSelectors.ts @@ -18,3 +18,6 @@ export const getApiPaneResponseSelectedTab = (state: AppState) => export const getApiPaneResponsePaneHeight = (state: AppState) => state.ui.apiPane.responseTabHeight; + +export const getApiRightPaneSelectedTab = (state: AppState) => + state.ui.apiPane.selectedRightPaneTab; diff --git a/app/client/src/selectors/autoHeightSelectors.ts b/app/client/src/selectors/autoHeightSelectors.ts new file mode 100644 index 0000000000..2e56a80b38 --- /dev/null +++ b/app/client/src/selectors/autoHeightSelectors.ts @@ -0,0 +1,7 @@ +import { AppState } from "@appsmith/reducers"; + +export const getAutoHeightLayoutTree = (state: AppState) => + state.entities.autoHeightLayoutTree; + +export const getCanvasLevelMap = (state: AppState) => + state.entities.canvasLevels; diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index b82d0b7355..a897f285a4 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -18,6 +18,7 @@ import { import { MAIN_CONTAINER_WIDGET_ID, RenderModes, + WidgetType, } from "constants/WidgetConstants"; import CanvasWidgetsNormalizer from "normalizers/CanvasWidgetsNormalizer"; import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory"; @@ -34,7 +35,11 @@ import { createCanvasWidget, createLoadingWidget, } from "utils/widgetRenderUtils"; +import WidgetFactory, { + NonSerialisableWidgetConfigs, +} from "utils/WidgetFactory"; import { LOCAL_STORAGE_KEYS } from "utils/localStorage"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; const getIsDraggingOrResizing = (state: AppState) => state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging; @@ -172,10 +177,11 @@ export const selectURLSlugs = createSelector( }, ); -export const getRenderMode = (state: AppState) => - state.entities.app.mode === APP_MODE.EDIT +export const getRenderMode = (state: AppState) => { + return state.entities.app.mode === APP_MODE.EDIT ? RenderModes.CANVAS : RenderModes.PAGE; +}; export const getViewModePageList = createSelector( getPageList, @@ -211,6 +217,33 @@ export const getCurrentPageName = createSelector( ?.pageName, ); +/** + * This returns the number of rows which is not occupied by a Canvas Widget within + * a parent container like widget of type widgetType + * For example, the Tabs Widget takes 4 rows for the tabs + * @param widgetType Type of widget + * @param props Widget properties + * @returns the offset in rows + */ +export const getCanvasHeightOffset = ( + widgetType: WidgetType, + props: WidgetProps, +) => { + // Get the non serialisable configs for the widget type + const config: + | Record + | undefined = WidgetFactory.nonSerialisableWidgetConfigMap.get(widgetType); + let offset = 0; + // If this widget has a registered canvasHeightOffset function + if (config?.canvasHeightOffset) { + // Run the function to get the offset value + offset = (config.canvasHeightOffset as (props: WidgetProps) => number)( + props, + ); + } + return offset; +}; + export const getWidgetCards = createSelector( getWidgetConfigs, (widgetConfigs: WidgetConfigReducerState) => { @@ -347,6 +380,10 @@ const getWidgetSpacesForContainer = ( widgets: FlattenedWidgetProps[], ): WidgetSpace[] => { return widgets.map((widget) => { + const hasAutoHeight = isAutoHeightEnabledForWidget(widget); + const fixedHeight = hasAutoHeight + ? widget.bottomRow - widget.topRow + : undefined; const occupiedSpace: WidgetSpace = { id: widget.widgetId, parentId: containerWidgetId, @@ -355,6 +392,7 @@ const getWidgetSpacesForContainer = ( bottom: widget.bottomRow, right: widget.rightColumn, type: widget.type, + fixedHeight, }; return occupiedSpace; }); @@ -406,7 +444,121 @@ const generateOccupiedSpacesMap = ( // returns occupied spaces export const getOccupiedSpaces = createSelector( getWidgets, - generateOccupiedSpacesMap, + ( + widgets: CanvasWidgetsReduxState, + ): { [containerWidgetId: string]: OccupiedSpace[] } | undefined => { + const occupiedSpaces: { + [containerWidgetId: string]: OccupiedSpace[]; + } = {}; + // Get all widgets with type "CONTAINER_WIDGET" and has children + const containerWidgets: FlattenedWidgetProps[] = Object.values( + widgets, + ).filter((widget) => widget.children && widget.children.length > 0); + + // If we have any container widgets + if (containerWidgets) { + containerWidgets.forEach((containerWidget: FlattenedWidgetProps) => { + const containerWidgetId = containerWidget.widgetId; + // Get child widgets for the container + // TODO: PERF_FIX (abhinav): This is iterating over all widgets for every widget which has children + // We can optimise this by iterating through the children for each widget which has children + const childWidgets = Object.keys(widgets).filter( + (widgetId) => + containerWidget.children && + containerWidget.children.indexOf(widgetId) > -1 && + !widgets[widgetId].detachFromLayout, + ); + // Get the occupied spaces in this container + // Assign it to the containerWidgetId key in occupiedSpaces + occupiedSpaces[containerWidgetId] = getOccupiedSpacesForContainer( + containerWidgetId, + childWidgets.map((widgetId) => widgets[widgetId]), + ); + }); + } + + // Return undefined if there are no occupiedSpaces. + return Object.keys(occupiedSpaces).length > 0 ? occupiedSpaces : undefined; + }, +); + +export const getOccupiedSpacesGroupedByParentCanvas = createSelector( + getWidgets, + ( + widgets: CanvasWidgetsReduxState, + ): { + occupiedSpaces: { + [parentCanvasWidgetId: string]: Array< + OccupiedSpace & { originalTop: number; originalBottom: number } + >; + }; + canvasLevelMap: Record; + } => { + const occupiedSpaces: { + [parentCanvasWidgetId: string]: Array< + OccupiedSpace & { originalTop: number; originalBottom: number } + >; + } = {}; + // Get all widgets with type "CANVAS_WIDGET" and has children + // What we're really doing is getting all widgets inside a drop target + const canvasWidgets: FlattenedWidgetProps[] = Object.values(widgets).filter( + (widget) => widget.type === "CANVAS_WIDGET", + ); + + // Levels signify how deeply nested a canvas is. + // For example, the main canvas is always at level 0, if a container exists on the canvas + // Then the canvas within this container will be level 1, and so on. + const canvasLevelMap: Record = {}; + + // If we have any canvas widgets + if (canvasWidgets) { + // Iterate through the list of canvas widgets + canvasWidgets.forEach((canvasWidget: FlattenedWidgetProps) => { + // Set the canvas widget id + const canvasWidgetId = canvasWidget.widgetId; + + // Get the nesting level of this Canvas: + let parentId = canvasWidget.parentId; + let level = 0; + while (parentId) { + const parent = widgets[parentId]; + if (parent.type === "CANVAS_WIDGET") level++; + parentId = parent.parentId; + } + canvasLevelMap[canvasWidget.widgetId] = level; + // Initialise the occupied spaces with an empty array + occupiedSpaces[canvasWidgetId] = []; + // If this canvas widget has children + if (canvasWidget.children && canvasWidget.children.length > 0) { + // Iterate through all children + canvasWidget.children.forEach((childWidgetId: string) => { + // Get the widget props + const widget = widgets[childWidgetId]; + // If the widget is not detached from layout, which means + // They actually exist by being displayed within the canvas + // (unlike a modal widget or another canvas widget) + if (!widget.detachFromLayout) { + // Add the occupied space co-ordinates to the initialised array + occupiedSpaces[canvasWidgetId].push({ + id: widget.widgetId, + parentId: canvasWidgetId, + left: widget.leftColumn, + top: widget.topRow, + bottom: widget.bottomRow, + right: widget.rightColumn, + originalTop: widget.originalTopRow, + originalBottom: widget.originalBottomRow, + }); + } + }); + } + }); + } + + // Return the occupied spaces and the canvas levels. + // In an empty canvas occupied spaces will be like so: { "0": [] } + return { occupiedSpaces, canvasLevelMap }; + }, ); // returns occupied spaces only while dragging or moving diff --git a/app/client/src/selectors/propertyPaneSelectors.tsx b/app/client/src/selectors/propertyPaneSelectors.tsx index 35c9bf41c8..30c3761b7d 100644 --- a/app/client/src/selectors/propertyPaneSelectors.tsx +++ b/app/client/src/selectors/propertyPaneSelectors.tsx @@ -18,6 +18,7 @@ import { generateClassName } from "utils/generators"; import { getWidgets } from "sagas/selectors"; import { getCurrentPageId } from "selectors/editorSelectors"; import { generatePropertyKey } from "utils/editorContextUtils"; +import { RegisteredWidgetFeatures } from "utils/WidgetFeatures"; export type WidgetProperties = WidgetProps & { [EVALUATION_PATH]?: DataTreeEntity; @@ -63,6 +64,7 @@ export const getWidgetPropsForPropertyPane = createSelector( ); type WidgetPropertiesForPropertyPaneView = { + disabledWidgetFeatures?: RegisteredWidgetFeatures[]; type: string; widgetId: string; widgetName: string; @@ -77,6 +79,7 @@ export const getWidgetPropsForPropertyPaneView = createSelector( "widgetId", "widgetName", "displayName", + "disabledWidgetFeatures", ]) as WidgetPropertiesForPropertyPaneView, ); diff --git a/app/client/src/store.ts b/app/client/src/store.ts index 01fa2fef0c..70c874aec9 100644 --- a/app/client/src/store.ts +++ b/app/client/src/store.ts @@ -1,9 +1,5 @@ import { reduxBatch } from "@manaflair/redux-batch"; import { createStore, applyMiddleware, compose } from "redux"; -import { - useSelector as useReduxSelector, - TypedUseSelectorHook, -} from "react-redux"; import appReducer, { AppState } from "@appsmith/reducers"; import createSagaMiddleware from "redux-saga"; import { rootSaga } from "@appsmith/sagas"; @@ -50,5 +46,3 @@ export const testStore = (initialState: Partial) => ); sagaMiddleware.run(rootSaga); - -export const useSelector: TypedUseSelectorHook = useReduxSelector; diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index b6184b0f27..41f898beec 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -266,6 +266,7 @@ export type EventName = | "JS_OBJECT_SETTINGS_CHANGED" | "SHOW_BINDINGS_TRIGGERED" | "BINDING_COPIED" + | "AUTO_HEIGHT_OVERLAY_HANDLES_UPDATE" | AUDIT_LOGS_EVENT_NAMES; export type AUDIT_LOGS_EVENT_NAMES = diff --git a/app/client/src/utils/CallbackHandler/CallbackHandlerEventType.ts b/app/client/src/utils/CallbackHandler/CallbackHandlerEventType.ts index 6fd556bddd..2505627148 100644 --- a/app/client/src/utils/CallbackHandler/CallbackHandlerEventType.ts +++ b/app/client/src/utils/CallbackHandler/CallbackHandlerEventType.ts @@ -1,3 +1,6 @@ export enum CallbackHandlerEventType { - CHECKBOX_GROUP_FOCUS, + MAX_HEIGHT_LIMIT_FOCUS, + MAX_HEIGHT_LIMIT_BLUR, + MIN_HEIGHT_LIMIT_FOCUS, + MIN_HEIGHT_LIMIT_BLUR, } diff --git a/app/client/src/utils/DSLMigration.test.ts b/app/client/src/utils/DSLMigration.test.ts index 32b6187f93..817281e590 100644 --- a/app/client/src/utils/DSLMigration.test.ts +++ b/app/client/src/utils/DSLMigration.test.ts @@ -21,6 +21,8 @@ import { LATEST_PAGE_VERSION } from "constants/WidgetConstants"; import { originalDSLForDSLMigrations } from "./testDSLs"; import * as rateWidgetMigrations from "./migrations/RateWidgetMigrations"; import * as codeScannerWidgetMigrations from "./migrations/CodeScannerWidgetMigrations"; +import * as migrateLabelPosition from "./migrations/MigrateLabelPosition"; +import * as migrateAutoHeight from "./migrations/autoHeightMigrations"; type Migration = { functionLookup: { @@ -653,6 +655,24 @@ const migrations: Migration[] = [ ], version: 66, }, + { + functionLookup: [ + { + moduleObj: migrateLabelPosition, + functionName: "migrateLabelPosition", + }, + ], + version: 67, + }, + { + functionLookup: [ + { + moduleObj: migrateAutoHeight, + functionName: "migratePropertiesForDynamicHeight", + }, + ], + version: 68, + }, ]; const mockFnObj: Record = {}; diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index d124807ea9..dcc0e3cf7c 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -1,5 +1,3 @@ -import { WidgetProps } from "widgets/BaseWidget"; -import { ContainerWidgetProps } from "widgets/ContainerWidget/widget"; import { generateReactKey } from "./generators"; import { GridDefaults, @@ -63,8 +61,11 @@ import { migrateCheckboxSwitchProperty } from "./migrations/PropertyPaneMigratio import { migrateChartWidgetReskinningData } from "./migrations/ChartWidgetReskinningMigrations"; import { MigrateSelectTypeWidgetDefaultValue } from "./migrations/SelectWidget"; import { migrateMapChartWidgetReskinningData } from "./migrations/MapChartReskinningMigrations"; + import { migrateRateWidgetDisabledState } from "./migrations/RateWidgetMigrations"; import { migrateCodeScannerLayout } from "./migrations/CodeScannerWidgetMigrations"; +import { migrateLabelPosition } from "./migrations/MigrateLabelPosition"; +import { migratePropertiesForDynamicHeight } from "./migrations/autoHeightMigrations"; /** * adds logBlackList key for all list widget children @@ -73,9 +74,9 @@ import { migrateCodeScannerLayout } from "./migrations/CodeScannerWidgetMigratio * @returns */ export const addLogBlackListToAllListWidgetChildren = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, ) => { - currentDSL.children = currentDSL.children?.map((children: WidgetProps) => { + currentDSL.children = currentDSL.children?.map((children: DSLWidget) => { if (children.type === "LIST_WIDGET") { const widgets = get( children, @@ -110,10 +111,8 @@ export const addLogBlackListToAllListWidgetChildren = ( * @param currentDSL * @returns */ -export const addPrivateWidgetsToAllListWidgets = ( - currentDSL: ContainerWidgetProps, -) => { - currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { +export const addPrivateWidgetsToAllListWidgets = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((child: DSLWidget) => { if (child.type === "LIST_WIDGET") { const privateWidgets: PrivateWidgets = {}; Object.keys(child.template).forEach((entityName) => { @@ -136,9 +135,7 @@ export const addPrivateWidgetsToAllListWidgets = ( * @param currentDSL * @returns */ -export const migrateItemsToListDataInListWidget = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateItemsToListDataInListWidget = (currentDSL: DSLWidget) => { if (currentDSL.type === "LIST_WIDGET") { currentDSL = renameKeyInObject(currentDSL, "items", "listData"); @@ -188,7 +185,7 @@ export const migrateItemsToListDataInListWidget = ( return currentDSL; }; -export const updateContainers = (dsl: ContainerWidgetProps) => { +export const updateContainers = (dsl: DSLWidget) => { if (dsl.type === "CONTAINER_WIDGET" || dsl.type === "FORM_WIDGET") { if ( !( @@ -229,10 +226,8 @@ export const updateContainers = (dsl: ContainerWidgetProps) => { //transform chart data, from old chart widget to new chart widget //updated chart widget has support for multiple series -export const chartDataMigration = ( - currentDSL: ContainerWidgetProps, -) => { - currentDSL.children = currentDSL.children?.map((children: WidgetProps) => { +export const chartDataMigration = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((children: DSLWidget) => { if ( children.type === "CHART_WIDGET" && children.chartData && @@ -253,9 +248,7 @@ export const chartDataMigration = ( return currentDSL; }; -export const singleChartDataMigration = ( - currentDSL: ContainerWidgetProps, -) => { +export const singleChartDataMigration = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((child) => { if (child.type === "CHART_WIDGET") { // Check if chart widget has the deprecated singleChartData property @@ -284,10 +277,8 @@ export const singleChartDataMigration = ( return currentDSL; }; -export const mapDataMigration = ( - currentDSL: ContainerWidgetProps, -) => { - currentDSL.children = currentDSL.children?.map((children: WidgetProps) => { +export const mapDataMigration = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((children: DSLWidget) => { if (children.type === "MAP_WIDGET") { if (children.markers) { children.markers = children.markers.map( @@ -346,9 +337,7 @@ export const mapDataMigration = ( return currentDSL; }; -export const mapAllowHorizontalScrollMigration = ( - currentDSL: ContainerWidgetProps, -) => { +export const mapAllowHorizontalScrollMigration = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children?.map((child: DSLWidget) => { if (child.type === "CHART_WIDGET") { child.allowScroll = child.allowHorizontalScroll; @@ -364,12 +353,10 @@ export const mapAllowHorizontalScrollMigration = ( return currentDSL; }; -export const tabsWidgetTabsPropertyMigration = ( - currentDSL: ContainerWidgetProps, -) => { +export const tabsWidgetTabsPropertyMigration = (currentDSL: DSLWidget) => { currentDSL.children = currentDSL.children ?.filter(Boolean) - .map((child: WidgetProps) => { + .map((child: DSLWidget) => { if (child.type === "TABS_WIDGET") { try { const tabs = isString(child.tabs) @@ -378,7 +365,7 @@ export const tabsWidgetTabsPropertyMigration = ( const newTabs = tabs.map((tab: any) => { const childForTab = child.children ?.filter(Boolean) - .find((tabChild: WidgetProps) => tabChild.tabId === tab.id); + .find((tabChild: DSLWidget) => tabChild.tabId === tab.id); if (childForTab) { tab.widgetId = childForTab.widgetId; } @@ -397,9 +384,7 @@ export const tabsWidgetTabsPropertyMigration = ( return currentDSL; }; -export const dynamicPathListMigration = ( - currentDSL: ContainerWidgetProps, -) => { +export const dynamicPathListMigration = (currentDSL: DSLWidget) => { if (currentDSL.children && currentDSL.children.length) { currentDSL.children = currentDSL.children.map(dynamicPathListMigration); } @@ -424,9 +409,7 @@ export const dynamicPathListMigration = ( return currentDSL; }; -export const addVersionNumberMigration = ( - currentDSL: ContainerWidgetProps, -) => { +export const addVersionNumberMigration = (currentDSL: DSLWidget) => { if (currentDSL.children && currentDSL.children.length) { currentDSL.children = currentDSL.children.map(addVersionNumberMigration); } @@ -437,9 +420,9 @@ export const addVersionNumberMigration = ( }; export const canvasNameConflictMigration = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, props = { counter: 1 }, -): ContainerWidgetProps => { +): DSLWidget => { if ( currentDSL.type === "CANVAS_WIDGET" && currentDSL.widgetName.startsWith("Canvas") @@ -457,9 +440,9 @@ export const canvasNameConflictMigration = ( }; export const renamedCanvasNameConflictMigration = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, props = { counter: 1 }, -): ContainerWidgetProps => { +): DSLWidget => { // Rename all canvas widgets except for MainContainer if ( currentDSL.type === "CANVAS_WIDGET" && @@ -477,9 +460,7 @@ export const renamedCanvasNameConflictMigration = ( return currentDSL; }; -export const rteDefaultValueMigration = ( - currentDSL: ContainerWidgetProps, -): ContainerWidgetProps => { +export const rteDefaultValueMigration = (currentDSL: DSLWidget): DSLWidget => { if (currentDSL.type === "RICH_TEXT_EDITOR_WIDGET") { currentDSL.inputType = "html"; } @@ -490,9 +471,7 @@ export const rteDefaultValueMigration = ( return currentDSL; }; -function migrateTabsDataUsingMigrator( - currentDSL: ContainerWidgetProps, -) { +function migrateTabsDataUsingMigrator(currentDSL: DSLWidget) { if (currentDSL.type === "TABS_WIDGET" && currentDSL.version === 1) { try { currentDSL.type = "TABS_MIGRATOR_WIDGET"; @@ -512,9 +491,7 @@ function migrateTabsDataUsingMigrator( return currentDSL; } -export const migrateTabsData = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateTabsData = (currentDSL: DSLWidget) => { if ( ["TABS_WIDGET", "TABS_MIGRATOR_WIDGET"].includes(currentDSL.type as any) && currentDSL.version === 1 @@ -596,9 +573,7 @@ export const migrateTabsData = ( }; // A rudimentary transform function which updates the DSL based on its version. -export const migrateOldChartData = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateOldChartData = (currentDSL: DSLWidget) => { if (currentDSL.type === "CHART_WIDGET") { if (isString(currentDSL.chartData)) { try { @@ -625,10 +600,8 @@ export const migrateOldChartData = ( * @param currentDSL * @returns */ -export const migrateChartDataFromArrayToObject = ( - currentDSL: ContainerWidgetProps, -) => { - currentDSL.children = currentDSL.children?.map((children: WidgetProps) => { +export const migrateChartDataFromArrayToObject = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((children: DSLWidget) => { if (children.type === "CHART_WIDGET") { if (Array.isArray(children.chartData)) { const newChartData = {}; @@ -697,10 +670,8 @@ export const calculateDynamicHeight = () => { return calculatedMinHeight; }; -export const migrateInitialValues = ( - currentDSL: ContainerWidgetProps, -) => { - currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { +export const migrateInitialValues = (currentDSL: DSLWidget) => { + currentDSL.children = currentDSL.children?.map((child: DSLWidget) => { if (child.type === "INPUT_WIDGET") { child = { isRequired: false, @@ -766,10 +737,7 @@ export const migrateInitialValues = ( // A rudimentary transform function which updates the DSL based on its version. // A more modular approach needs to be designed. -export const transformDSL = ( - currentDSL: ContainerWidgetProps, - newPage = false, -) => { +export const transformDSL = (currentDSL: DSLWidget, newPage = false) => { if (currentDSL.version === undefined) { // Since this top level widget is a CANVAS_WIDGET, // DropTargetComponent needs to know the minimum height the canvas can take @@ -1139,15 +1107,23 @@ export const transformDSL = ( if (currentDSL.version === 66) { currentDSL = migrateTableWidgetV2ValidationBinding(currentDSL); + currentDSL.version = 67; + } + + if (currentDSL.version === 67) { + currentDSL = migrateLabelPosition(currentDSL); + currentDSL.version = 68; + } + + if (currentDSL.version === 68) { + currentDSL = migratePropertiesForDynamicHeight(currentDSL); currentDSL.version = LATEST_PAGE_VERSION; } return currentDSL; }; -export const migrateButtonVariant = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateButtonVariant = (currentDSL: DSLWidget) => { if ( currentDSL.type === "BUTTON_WIDGET" || currentDSL.type === "FORM_BUTTON_WIDGET" || @@ -1202,9 +1178,7 @@ export const migrateButtonVariant = ( return currentDSL; }; -export const revertTableDefaultSelectedRow = ( - currentDSL: ContainerWidgetProps, -) => { +export const revertTableDefaultSelectedRow = (currentDSL: DSLWidget) => { if (currentDSL.type === "TABLE_WIDGET") { if (currentDSL.version === 1 && currentDSL.defaultSelectedRow === "0") currentDSL.defaultSelectedRow = undefined; @@ -1219,9 +1193,7 @@ export const revertTableDefaultSelectedRow = ( return currentDSL; }; -export const revertButtonStyleToButtonColor = ( - currentDSL: ContainerWidgetProps, -) => { +export const revertButtonStyleToButtonColor = (currentDSL: DSLWidget) => { if ( currentDSL.type === "BUTTON_WIDGET" || currentDSL.type === "FORM_BUTTON_WIDGET" || @@ -1306,9 +1278,7 @@ export const revertButtonStyleToButtonColor = ( return currentDSL; }; -export const migrateInputValidation = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateInputValidation = (currentDSL: DSLWidget) => { if (currentDSL.type === "INPUT_WIDGET") { if (has(currentDSL, "validation")) { // convert boolean to string expression @@ -1328,27 +1298,21 @@ export const migrateInputValidation = ( return currentDSL; }; -export const migrateButtonWidgetValidation = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateButtonWidgetValidation = (currentDSL: DSLWidget) => { if (currentDSL.type === "INPUT_WIDGET") { if (!has(currentDSL, "validation")) { currentDSL.validation = true; } } if (currentDSL.children && currentDSL.children.length) { - currentDSL.children.map( - (eachWidgetDSL: ContainerWidgetProps) => { - migrateButtonWidgetValidation(eachWidgetDSL); - }, - ); + currentDSL.children.map((eachWidgetDSL: DSLWidget) => { + migrateButtonWidgetValidation(eachWidgetDSL); + }); } return currentDSL; }; -export const migrateTableDefaultSelectedRow = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateTableDefaultSelectedRow = (currentDSL: DSLWidget) => { if (currentDSL.type === "TABLE_WIDGET") { if (!currentDSL.defaultSelectedRow) currentDSL.defaultSelectedRow = "0"; } @@ -1360,9 +1324,7 @@ export const migrateTableDefaultSelectedRow = ( return currentDSL; }; -const addIsDisabledToButtonColumn = ( - currentDSL: ContainerWidgetProps, -) => { +const addIsDisabledToButtonColumn = (currentDSL: DSLWidget) => { if (currentDSL.type === "TABLE_WIDGET") { if (!isEmpty(currentDSL.primaryColumns)) { for (const key of Object.keys( @@ -1382,20 +1344,16 @@ const addIsDisabledToButtonColumn = ( return currentDSL; }; -export const migrateIsDisabledToButtonColumn = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateIsDisabledToButtonColumn = (currentDSL: DSLWidget) => { const newDSL = addIsDisabledToButtonColumn(currentDSL); - newDSL.children = newDSL.children?.map((children: WidgetProps) => { + newDSL.children = newDSL.children?.map((children: DSLWidget) => { return migrateIsDisabledToButtonColumn(children); }); return currentDSL; }; -export const migrateToNewMultiSelect = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateToNewMultiSelect = (currentDSL: DSLWidget) => { if (currentDSL.type === "DROP_DOWN_WIDGET") { if (currentDSL.selectionType === "MULTI_SELECT") { currentDSL.type = "MULTI_SELECT_WIDGET"; @@ -1411,13 +1369,11 @@ export const migrateToNewMultiSelect = ( return currentDSL; }; -export const migrateObjectFitToImageWidget = ( - dsl: ContainerWidgetProps, -) => { - const addObjectFitProperty = (widgetProps: WidgetProps) => { +export const migrateObjectFitToImageWidget = (dsl: DSLWidget) => { + const addObjectFitProperty = (widgetProps: DSLWidget) => { widgetProps.objectFit = "cover"; if (widgetProps.children && widgetProps.children.length) { - widgetProps.children.forEach((eachWidgetProp: WidgetProps) => { + widgetProps.children.forEach((eachWidgetProp: DSLWidget) => { if (widgetProps.type === "IMAGE_WIDGET") { addObjectFitProperty(eachWidgetProp); } @@ -1429,7 +1385,7 @@ export const migrateObjectFitToImageWidget = ( }; export const migrateOverFlowingTabsWidgets = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, canvasWidgets: any, ) => { if ( @@ -1442,7 +1398,7 @@ export const migrateOverFlowingTabsWidgets = ( (currentDSL.bottomRow - currentDSL.topRow) * currentDSL.parentRowSpace; const widgetHasOverflowingChildren = currentDSL.children.some((eachTab) => { if (eachTab.children && eachTab.children.length) { - return eachTab.children.some((child: WidgetProps) => { + return eachTab.children.some((child: DSLWidget) => { if (canvasWidgets[child.widgetId].repositioned) { const tabHeight = child.bottomRow * child.parentRowSpace; return tabsWidgetHeight < tabHeight; @@ -1465,7 +1421,7 @@ export const migrateOverFlowingTabsWidgets = ( }; export const migrateWidgetsWithoutLeftRightColumns = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, canvasWidgets: any, ) => { if ( @@ -1509,7 +1465,7 @@ export const migrateWidgetsWithoutLeftRightColumns = ( }; export const migrateNewlyAddedTabsWidgetsMissingData = ( - currentDSL: ContainerWidgetProps, + currentDSL: DSLWidget, ) => { if (currentDSL.type === "TABS_WIDGET" && currentDSL.version === 2) { try { @@ -1546,14 +1502,14 @@ export const migrateNewlyAddedTabsWidgetsMissingData = ( return currentDSL; }; -export const migrateToNewLayout = (dsl: ContainerWidgetProps) => { - const scaleWidget = (widgetProps: WidgetProps) => { +export const migrateToNewLayout = (dsl: DSLWidget) => { + const scaleWidget = (widgetProps: DSLWidget) => { widgetProps.bottomRow *= GRID_DENSITY_MIGRATION_V1; widgetProps.topRow *= GRID_DENSITY_MIGRATION_V1; widgetProps.leftColumn *= GRID_DENSITY_MIGRATION_V1; widgetProps.rightColumn *= GRID_DENSITY_MIGRATION_V1; if (widgetProps.children && widgetProps.children.length) { - widgetProps.children.forEach((eachWidgetProp: WidgetProps) => { + widgetProps.children.forEach((eachWidgetProp: DSLWidget) => { scaleWidget(eachWidgetProp); }); } @@ -1570,9 +1526,7 @@ export const checkIfMigrationIsNeeded = ( return currentDSL.version !== LATEST_PAGE_VERSION; }; -export const migrateDatePickerMinMaxDate = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateDatePickerMinMaxDate = (currentDSL: DSLWidget) => { if (currentDSL.type === "DATE_PICKER_WIDGET2" && currentDSL.version === 2) { if (currentDSL.minDate === "2001-01-01 00:00") { currentDSL.minDate = "1920-12-31T18:30:00.000Z"; @@ -1582,18 +1536,14 @@ export const migrateDatePickerMinMaxDate = ( } } if (currentDSL.children && currentDSL.children.length) { - currentDSL.children.map( - (eachWidgetDSL: ContainerWidgetProps) => { - migrateDatePickerMinMaxDate(eachWidgetDSL); - }, - ); + currentDSL.children.map((eachWidgetDSL: DSLWidget) => { + migrateDatePickerMinMaxDate(eachWidgetDSL); + }); } return currentDSL; }; -const addFilterDefaultValue = ( - currentDSL: ContainerWidgetProps, -) => { +const addFilterDefaultValue = (currentDSL: DSLWidget) => { if (currentDSL.type === "DROP_DOWN_WIDGET") { if (!currentDSL.hasOwnProperty("isFilterable")) { currentDSL.isFilterable = true; @@ -1601,12 +1551,10 @@ const addFilterDefaultValue = ( } return currentDSL; }; -export const migrateFilterValueForDropDownWidget = ( - currentDSL: ContainerWidgetProps, -) => { +export const migrateFilterValueForDropDownWidget = (currentDSL: DSLWidget) => { const newDSL = addFilterDefaultValue(currentDSL); - newDSL.children = newDSL.children?.map((children: WidgetProps) => { + newDSL.children = newDSL.children?.map((children: DSLWidget) => { return migrateFilterValueForDropDownWidget(children); }); diff --git a/app/client/src/utils/DSLMigrationsUtils.test.ts b/app/client/src/utils/DSLMigrationsUtils.test.ts index 21e6bf556b..76daea9eb6 100644 --- a/app/client/src/utils/DSLMigrationsUtils.test.ts +++ b/app/client/src/utils/DSLMigrationsUtils.test.ts @@ -477,6 +477,9 @@ describe("correctly migrate dsl", () => { shouldTruncate: false, truncateButtonColor: "#FFC13D", text: "{{currentItem.id}}", + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, key: "yd217bk315", rightColumn: 24, textAlign: "LEFT", @@ -910,6 +913,9 @@ describe("correctly migrate dsl", () => { isDeletable: false, animateLoading: true, leftColumn: 0, + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, children: [ { widgetName: "Canvas2", @@ -1014,6 +1020,9 @@ describe("correctly migrate dsl", () => { key: "text", }, ], + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, leftColumn: 16, truncateButtonColor: "#FFC13D", text: "{{currentItem.name}}", @@ -1085,6 +1094,9 @@ describe("correctly migrate dsl", () => { key: "text", }, ], + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, leftColumn: 16, truncateButtonColor: "#FFC13D", text: "{{currentItem.id}}", @@ -2127,6 +2139,9 @@ describe("correctly migrate dsl", () => { isDeletable: false, animateLoading: true, leftColumn: 0, + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, children: [ { widgetName: "Canvas2", @@ -2231,6 +2246,9 @@ describe("correctly migrate dsl", () => { key: "text", }, ], + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, leftColumn: 16, truncateButtonColor: "#FFC13D", text: "{{currentItem.name}}", @@ -2302,6 +2320,9 @@ describe("correctly migrate dsl", () => { key: "text", }, ], + dynamicHeight: "FIXED", + maxDynamicHeight: 9000, + minDynamicHeight: 4, leftColumn: 16, truncateButtonColor: "#FFC13D", text: "{{currentItem.id}}", diff --git a/app/client/src/utils/WidgetFactory.tsx b/app/client/src/utils/WidgetFactory.tsx index 93088a4af7..c05d29e8d5 100644 --- a/app/client/src/utils/WidgetFactory.tsx +++ b/app/client/src/utils/WidgetFactory.tsx @@ -10,13 +10,18 @@ import { addPropertyConfigIds, convertFunctionsToString, enhancePropertyPaneConfig, + PropertyPaneConfigTypes, } from "./WidgetFactoryHelpers"; import { CanvasWidgetStructure } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; type WidgetDerivedPropertyType = any; export type DerivedPropertiesMap = Record; export type WidgetType = typeof WidgetFactory.widgetTypes[number]; +export enum NonSerialisableWidgetConfigs { + CANVAS_HEIGHT_OFFSET = "canvasHeightOffset", +} class WidgetFactory { static widgetTypes: Record = {}; static widgetMap: Map< @@ -49,12 +54,18 @@ class WidgetFactory { readonly PropertyPaneConfig[] > = new Map(); static loadingProperties: Map> = new Map(); + static stylesheetConfigMap: Map = new Map(); static widgetConfigMap: Map< WidgetType, Partial & WidgetConfigProps & { type: string } > = new Map(); + static nonSerialisableWidgetConfigMap: Map< + WidgetType, + Record + > = new Map(); + static registerWidgetBuilder( widgetType: string, widgetBuilder: WidgetBuilder, @@ -66,17 +77,23 @@ class WidgetFactory { propertyPaneStyleConfig?: PropertyPaneConfig[], features?: WidgetFeatures, loadingProperties?: Array, + stylesheetConfig?: Stylesheet, ) { if (!this.widgetTypes[widgetType]) { this.widgetTypes[widgetType] = widgetType; this.widgetMap.set(widgetType, widgetBuilder); this.derivedPropertiesMap.set(widgetType, derivedPropertiesMap); - this.defaultPropertiesMap.set(widgetType, defaultPropertiesMap); + this.defaultPropertiesMap.set( + widgetType, + defaultPropertiesMap as Record, + ); this.metaPropertiesMap.set(widgetType, metaPropertiesMap); loadingProperties && this.loadingProperties.set(widgetType, loadingProperties); + stylesheetConfig && + this.stylesheetConfigMap.set(widgetType, stylesheetConfig); - if (propertyPaneConfig) { + if (Array.isArray(propertyPaneConfig) && propertyPaneConfig.length > 0) { const enhancedPropertyPaneConfig = enhancePropertyPaneConfig( propertyPaneConfig, features, @@ -100,6 +117,8 @@ class WidgetFactory { const enhancedPropertyPaneConfig = enhancePropertyPaneConfig( propertyPaneContentConfig, features, + PropertyPaneConfigTypes.CONTENT, + widgetType, ); const serializablePropertyPaneConfig = convertFunctionsToString( @@ -120,6 +139,7 @@ class WidgetFactory { const enhancedPropertyPaneConfig = enhancePropertyPaneConfig( propertyPaneStyleConfig, features, + PropertyPaneConfigTypes.STYLE, ); const serializablePropertyPaneConfig = convertFunctionsToString( @@ -145,6 +165,13 @@ class WidgetFactory { this.widgetConfigMap.set(widgetType, Object.freeze(config)); } + static storeNonSerialisablewidgetConfig( + widgetType: string, + config: Record, + ) { + this.nonSerialisableWidgetConfigMap.set(widgetType, config); + } + static createWidget( widgetData: CanvasWidgetStructure, renderMode: RenderMode, @@ -220,7 +247,7 @@ class WidgetFactory { const map = this.propertyPaneConfigsMap.get(type); if (!map || (map && map.length === 0)) { const config = WidgetFactory.getWidgetPropertyPaneCombinedConfig(type); - if (config.length === 0) { + if (config === undefined) { log.error("Widget property pane config not defined", type); } return config; @@ -263,6 +290,15 @@ class WidgetFactory { static getLoadingProperties(type: WidgetType): Array | undefined { return this.loadingProperties.get(type); } + + static getWidgetStylesheetConfigMap(widgetType: WidgetType) { + const map = this.stylesheetConfigMap.get(widgetType); + if (!map) { + log.error("Widget stylesheet properties not defined: ", widgetType); + return undefined; + } + return map; + } } export type WidgetTypeConfigMap = Record< diff --git a/app/client/src/utils/WidgetFactoryHelpers.test.ts b/app/client/src/utils/WidgetFactoryHelpers.test.ts deleted file mode 100644 index 055b4e5dc9..0000000000 --- a/app/client/src/utils/WidgetFactoryHelpers.test.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { ValidationTypes } from "constants/WidgetValidation"; -import { WidgetProps } from "widgets/BaseWidget"; -import { AutocompleteDataType } from "./autocomplete/TernServer"; -import { - convertFunctionsToString, - enhancePropertyPaneConfig, -} from "./WidgetFactoryHelpers"; -import { DynamicHeight } from "./WidgetFeatures"; - -const ORIGINAL_PROPERTY_CONFIG = [ - { - sectionName: "General", - children: [ - { - propertyName: "url", - label: "URL", - controlType: "INPUT_TEXT", - placeholderText: "Enter URL", - inputType: "TEXT", - isBindProperty: true, - isTriggerProperty: false, - }, - ], - }, - { - sectionName: "Events", - children: [ - { - helpText: "Triggers an action when the video is played", - propertyName: "onPlay", - label: "onPlay", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - { - helpText: "Triggers an action when the video is paused", - propertyName: "onPause", - label: "onPause", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - { - helpText: "Triggers an action when the video ends", - propertyName: "onEnd", - label: "onEnd", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - ], - }, -]; - -const EXPECTED_PROPERTY_CONFIG = [ - { - sectionName: "General", - children: [ - { - propertyName: "url", - label: "URL", - controlType: "INPUT_TEXT", - placeholderText: "Enter URL", - inputType: "TEXT", - isBindProperty: true, - isTriggerProperty: false, - }, - ], - }, - { - sectionName: "Layout Features", - children: [ - { - helpText: - "Dynamic Height: Configure the way the widget height react to content changes.", - propertyName: "dynamicHeight", - label: "Height", - controlType: "DROP_DOWN", - isBindProperty: false, - isTriggerProperty: false, - options: [ - { - label: "Hug Contents", - value: DynamicHeight.HUG_CONTENTS, - }, - { - label: "Fixed", - value: DynamicHeight.FIXED, - }, - ], - }, - { - propertyName: "minDynamicHeight", - label: "Min Height (in rows)", - helpText: "Minimum number of rows to occupy irrespective of contents", - controlType: "INPUT_TEXT", - hidden: (props: WidgetProps) => { - return props.dynamicHeight !== DynamicHeight.HUG_CONTENTS; - }, - dependencies: ["dynamicHeight"], - isJSConvertible: false, - isBindProperty: false, - isTriggerProperty: false, - }, - { - propertyName: "maxDynamicHeight", - label: "Max Height (in rows)", - helpText: "Maximum Height, after which contents will scroll", - controlType: "INPUT_TEXT", - dependencies: ["dynamicHeight"], - hidden: (props: WidgetProps) => { - return props.dynamicHeight !== DynamicHeight.HUG_CONTENTS; - }, - isJSConvertible: false, - isBindProperty: false, - isTriggerProperty: false, - }, - ], - }, - { - sectionName: "Events", - children: [ - { - helpText: "Triggers an action when the video is played", - propertyName: "onPlay", - label: "onPlay", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - { - helpText: "Triggers an action when the video is paused", - propertyName: "onPause", - label: "onPause", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - { - helpText: "Triggers an action when the video ends", - propertyName: "onEnd", - label: "onEnd", - controlType: "ACTION_SELECTOR", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, - }, - ], - }, -]; - -describe("Widget Factory Helper tests", () => { - it("Make sure dynamicHeight property configs are added when enabled in widget", () => { - const features = { - dynamicHeight: true, - }; - const result = enhancePropertyPaneConfig( - ORIGINAL_PROPERTY_CONFIG, - features, - ); - expect(JSON.stringify(result)).toEqual( - JSON.stringify(EXPECTED_PROPERTY_CONFIG), - ); - }); - it("Make sure dynamicHeight property configs are NOT added when disabled in widget", () => { - const features = { - dynamicHeight: false, - }; - - const result_with_false = enhancePropertyPaneConfig( - ORIGINAL_PROPERTY_CONFIG, - features, - ); - const result_with_undefined = enhancePropertyPaneConfig( - ORIGINAL_PROPERTY_CONFIG, - undefined, - ); - - expect(result_with_false).toStrictEqual(ORIGINAL_PROPERTY_CONFIG); - expect(result_with_undefined).toStrictEqual(ORIGINAL_PROPERTY_CONFIG); - }); - - it("Makes sure that fn function validation params are converted to fnString", () => { - const add = (value: unknown) => { - return { - parsed: (value as string) + "__suffix", - message: [], - isValid: true, - }; - }; - const config = [ - { - sectionName: "General", - children: [ - { - propertyName: "url", - label: "URL", - controlType: "INPUT_TEXT", - placeholderText: "Enter URL", - inputType: "TEXT", - isBindProperty: true, - isTriggerProperty: false, - validation: { - type: ValidationTypes.FUNCTION, - params: { - fn: add, - expected: { - type: "number", - example: `100`, - autocompleteDataType: AutocompleteDataType.STRING, - }, - }, - }, - }, - ], - }, - ]; - - const expected = [ - { - sectionName: "General", - children: [ - { - propertyName: "url", - label: "URL", - controlType: "INPUT_TEXT", - placeholderText: "Enter URL", - inputType: "TEXT", - isBindProperty: true, - isTriggerProperty: false, - validation: { - type: ValidationTypes.FUNCTION, - params: { - fnString: add.toString(), - expected: { - type: "number", - example: `100`, - autocompleteDataType: AutocompleteDataType.STRING, - }, - }, - }, - }, - ], - }, - ]; - const result = convertFunctionsToString(config); - expect(result).toStrictEqual(expected); - }); -}); diff --git a/app/client/src/utils/WidgetFactoryHelpers.ts b/app/client/src/utils/WidgetFactoryHelpers.ts index d61e718170..ed0b036b27 100644 --- a/app/client/src/utils/WidgetFactoryHelpers.ts +++ b/app/client/src/utils/WidgetFactoryHelpers.ts @@ -1,10 +1,23 @@ import { PropertyPaneConfig, PropertyPaneControlConfig, + PropertyPaneSectionConfig, } from "constants/PropertyControlConstants"; import { ValidationTypes } from "constants/WidgetValidation"; +import log from "loglevel"; import { generateReactKey } from "./generators"; -import { PropertyPaneConfigTemplates, WidgetFeatures } from "./WidgetFeatures"; +import { WidgetType } from "./WidgetFactory"; +import { + PropertyPaneConfigTemplates, + RegisteredWidgetFeatures, + WidgetFeaturePropertyPaneEnhancements, + WidgetFeatures, +} from "./WidgetFeatures"; + +export enum PropertyPaneConfigTypes { + STYLE = "STYLE", + CONTENT = "CONTENT", +} /* This function recursively parses the property pane configuration and adds random hash values as `id`. @@ -63,11 +76,43 @@ export const addPropertyConfigIds = (config: PropertyPaneConfig[]) => { export function enhancePropertyPaneConfig( config: PropertyPaneConfig[], features?: WidgetFeatures, + configType?: PropertyPaneConfigTypes, + widgetType?: WidgetType, ) { - // Enhance property pane for dynamic height feature - if (features && features.dynamicHeight) { - config.splice(1, 0, PropertyPaneConfigTemplates.DYNAMIC_HEIGHT); + // Enhance property pane with widget features + // TODO(abhinav): The following "configType" check should come + // from the features themselves. + if ( + features && + (configType === undefined || configType === PropertyPaneConfigTypes.CONTENT) + ) { + Object.keys(features).forEach((registeredFeature: string) => { + const { sectionIndex } = features[ + registeredFeature as RegisteredWidgetFeatures + ]; + const sectionName = (config[sectionIndex] as PropertyPaneSectionConfig) + ?.sectionName; + if (!sectionName || sectionName !== "General") { + log.error(`Invalid section index for feature: ${registeredFeature}`); + } + if ( + Array.isArray(config[sectionIndex].children) && + PropertyPaneConfigTemplates[ + registeredFeature as RegisteredWidgetFeatures + ] + ) { + config[sectionIndex].children?.push( + ...PropertyPaneConfigTemplates[ + registeredFeature as RegisteredWidgetFeatures + ], + ); + config = WidgetFeaturePropertyPaneEnhancements[ + registeredFeature as RegisteredWidgetFeatures + ](config, widgetType); + } + }); } + return config; } diff --git a/app/client/src/utils/WidgetFeatures.test.ts b/app/client/src/utils/WidgetFeatures.test.ts index 6b1f6440f6..32fb4eda7e 100644 --- a/app/client/src/utils/WidgetFeatures.test.ts +++ b/app/client/src/utils/WidgetFeatures.test.ts @@ -39,8 +39,22 @@ describe("Widget Features tests", () => { expect(result).toBe(true); }); }); - it("Make sure hidden hook for dynamic Height enabled if dynamic height is enabled", () => { - const inputs = [DynamicHeight.HUG_CONTENTS, "HUG_CONTENTS"]; + it("Make sure hidden hook for dynamic Height disabled if dynamic height with limits is disabled", () => { + const inputs = [DynamicHeight.AUTO_HEIGHT, "AUTO_HEIGHT"]; + + inputs.forEach((dynamicHeight) => { + const result = hideDynamicHeightPropertyControl({ + ...DUMMY_WIDGET, + dynamicHeight, + }); + expect(result).toBe(true); + }); + }); + it("Make sure hidden hook for dynamic Height enabled if dynamic height with limits is enabled", () => { + const inputs = [ + DynamicHeight.AUTO_HEIGHT_WITH_LIMITS, + "AUTO_HEIGHT_WITH_LIMITS", + ]; inputs.forEach((dynamicHeight) => { const result = hideDynamicHeightPropertyControl({ diff --git a/app/client/src/utils/WidgetFeatures.ts b/app/client/src/utils/WidgetFeatures.ts index 2cc061b539..be0a768511 100644 --- a/app/client/src/utils/WidgetFeatures.ts +++ b/app/client/src/utils/WidgetFeatures.ts @@ -1,16 +1,41 @@ -import { PropertyPaneConfig } from "constants/PropertyControlConstants"; +import { ReduxActionTypes } from "ce/constants/ReduxActionConstants"; +import { + PropertyPaneConfig, + PropertyPaneControlConfig, + PropertyPaneSectionConfig, +} from "constants/PropertyControlConstants"; +import { + GridDefaults, + WidgetHeightLimits, + WidgetType, +} from "constants/WidgetConstants"; +import { klona } from "klona/lite"; import { WidgetProps } from "widgets/BaseWidget"; +import { WidgetConfiguration } from "widgets/constants"; +import WidgetFactory from "./WidgetFactory"; -export interface WidgetFeatures { - dynamicHeight: boolean; +export enum RegisteredWidgetFeatures { + DYNAMIC_HEIGHT = "dynamicHeight", } +interface WidgetFeatureConfig { + active: boolean; + defaultValue?: DynamicHeight; + sectionIndex: number; +} + +export type WidgetFeatures = Record< + RegisteredWidgetFeatures, + WidgetFeatureConfig +>; + export enum DynamicHeight { - HUG_CONTENTS = "HUG_CONTENTS", + AUTO_HEIGHT = "AUTO_HEIGHT", FIXED = "FIXED", + AUTO_HEIGHT_WITH_LIMITS = "AUTO_HEIGHT_WITH_LIMITS", } -/* This contains all properties which will be added +/* This contains all properties which will be added to a widget, automatically, by the Appsmith platform Each feature, is a unique key, whose value is an object with the list of properties to be added to a widget along @@ -18,68 +43,334 @@ export enum DynamicHeight { Note: These are added to the widget configs during registration */ -export const WidgetFeatureProps = { - DYNAMIC_HEIGHT: { - minDynamicHeight: 0, - maxDynamicHeight: 0, +export const WidgetFeatureProps: Record< + RegisteredWidgetFeatures, + Record +> = { + [RegisteredWidgetFeatures.DYNAMIC_HEIGHT]: { + minDynamicHeight: WidgetHeightLimits.MIN_HEIGHT_IN_ROWS, + maxDynamicHeight: WidgetHeightLimits.MAX_HEIGHT_IN_ROWS, dynamicHeight: DynamicHeight.FIXED, }, }; +export const WidgetFeaturePropertyEnhancements: Record< + RegisteredWidgetFeatures, + (config: WidgetConfiguration) => Record +> = { + [RegisteredWidgetFeatures.DYNAMIC_HEIGHT]: (config: WidgetConfiguration) => { + const newProperties: Partial = {}; + newProperties.dynamicHeight = + config.features?.dynamicHeight?.defaultValue || DynamicHeight.AUTO_HEIGHT; + if (config.isCanvas) { + newProperties.dynamicHeight = DynamicHeight.AUTO_HEIGHT; + newProperties.minDynamicHeight = + config.defaults.minDynamicHeight || + WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS; + newProperties.shouldScrollContents = true; + } + if (config.defaults.overflow) newProperties.overflow = "NONE"; + return newProperties; + }, +}; + +function findAndUpdatePropertyPaneControlConfig( + config: PropertyPaneConfig[], + propertyPaneUpdates: Record>, +): PropertyPaneConfig[] { + return config.map((sectionConfig: PropertyPaneConfig) => { + if ( + Array.isArray(sectionConfig.children) && + sectionConfig.children.length > 0 + ) { + Object.keys(propertyPaneUpdates).forEach((propertyName: string) => { + const controlConfigIndex: + | number + | undefined = sectionConfig.children?.findIndex( + (controlConfig: PropertyPaneConfig) => + (controlConfig as PropertyPaneControlConfig).propertyName === + propertyName, + ); + + if ( + controlConfigIndex !== undefined && + controlConfigIndex > -1 && + sectionConfig.children + ) { + sectionConfig.children[controlConfigIndex] = { + ...sectionConfig.children[controlConfigIndex], + ...propertyPaneUpdates[propertyName], + }; + } + }); + } + return sectionConfig; + }); +} + +export const WidgetFeaturePropertyPaneEnhancements: Record< + RegisteredWidgetFeatures, + ( + config: PropertyPaneConfig[], + widgetType?: WidgetType, + ) => PropertyPaneConfig[] +> = { + [RegisteredWidgetFeatures.DYNAMIC_HEIGHT]: ( + config: PropertyPaneConfig[], + widgetType?: WidgetType, + ) => { + function hideWhenDynamicHeightIsEnabled(props: WidgetProps) { + return ( + props.dynamicHeight === DynamicHeight.AUTO_HEIGHT_WITH_LIMITS || + props.dynamicHeight === DynamicHeight.AUTO_HEIGHT + ); + } + let update = findAndUpdatePropertyPaneControlConfig(config, { + shouldScrollContents: { + hidden: hideWhenDynamicHeightIsEnabled, + dependencies: ["dynamicHeight"], + }, + scrollContents: { + hidden: hideWhenDynamicHeightIsEnabled, + dependencies: ["dynamicHeight"], + }, + fixedFooter: { + hidden: hideWhenDynamicHeightIsEnabled, + dependencies: ["dynamicHeight"], + }, + overflow: { + hidden: hideWhenDynamicHeightIsEnabled, + dependencies: ["dynamicHeight"], + }, + }); + if (widgetType === "MODAL_WIDGET") { + update = findAndUpdatePropertyPaneControlConfig(update, { + dynamicHeight: { + options: [ + { + label: "Auto Height", + value: DynamicHeight.AUTO_HEIGHT, + }, + { + label: "Fixed", + value: DynamicHeight.FIXED, + }, + ], + }, + }); + } + return update; + }, +}; + /* Hide the min height and max height properties using this function as the `hidden` hook in the property pane configuration This function checks if the `dynamicHeight` property is enabled and returns true if disabled, and false if enabled. */ export function hideDynamicHeightPropertyControl(props: WidgetProps) { - return props.dynamicHeight !== DynamicHeight.HUG_CONTENTS; + return props.dynamicHeight !== DynamicHeight.AUTO_HEIGHT_WITH_LIMITS; } -export const PropertyPaneConfigTemplates: Record = { - DYNAMIC_HEIGHT: { - sectionName: "Layout Features", - children: [ +// TODO (abhinav): ADD_UNIT_TESTS +function updateMinMaxDynamicHeight( + props: WidgetProps, + propertyName: string, + propertyValue: unknown, +) { + const updates = [ + { + propertyPath: propertyName, + propertyValue: propertyValue, + }, + ]; + + if (propertyValue === DynamicHeight.AUTO_HEIGHT_WITH_LIMITS) { + const minDynamicHeight = parseInt(props.minDynamicHeight, 10); + + if ( + isNaN(minDynamicHeight) || + minDynamicHeight < WidgetHeightLimits.MIN_HEIGHT_IN_ROWS + ) { + updates.push({ + propertyPath: "minDynamicHeight", + propertyValue: WidgetHeightLimits.MIN_HEIGHT_IN_ROWS, + }); + } + const maxDynamicHeight = parseInt(props.maxDynamicHeight, 10); + if ( + isNaN(maxDynamicHeight) || + maxDynamicHeight === WidgetHeightLimits.MAX_HEIGHT_IN_ROWS || + maxDynamicHeight <= WidgetHeightLimits.MIN_HEIGHT_IN_ROWS + ) { + updates.push({ + propertyPath: "maxDynamicHeight", + propertyValue: + props.bottomRow - props.topRow + GridDefaults.CANVAS_EXTENSION_OFFSET, + }); + } + + // Case where maxDynamicHeight is zero + if (isNaN(maxDynamicHeight) || maxDynamicHeight === 0) { + updates.push({ + propertyPath: "maxDynamicHeight", + propertyValue: props.bottomRow - props.topRow, + }); + } + } else if (propertyValue === DynamicHeight.AUTO_HEIGHT) { + const minHeightInRows = props.isCanvas + ? WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS + : WidgetHeightLimits.MIN_HEIGHT_IN_ROWS; + updates.push( { - helpText: - "Dynamic Height: Configure the way the widget height react to content changes.", - propertyName: "dynamicHeight", - label: "Height", - controlType: "DROP_DOWN", - isBindProperty: false, - isTriggerProperty: false, - options: [ - { - label: "Hug Contents", - value: DynamicHeight.HUG_CONTENTS, - }, - { - label: "Fixed", - value: DynamicHeight.FIXED, - }, - ], + propertyPath: "minDynamicHeight", + propertyValue: minHeightInRows, }, { - propertyName: "minDynamicHeight", - label: "Min Height (in rows)", - helpText: "Minimum number of rows to occupy irrespective of contents", - controlType: "INPUT_TEXT", - hidden: hideDynamicHeightPropertyControl, - dependencies: ["dynamicHeight"], - isJSConvertible: false, - isBindProperty: false, - isTriggerProperty: false, + propertyPath: "maxDynamicHeight", + propertyValue: WidgetHeightLimits.MAX_HEIGHT_IN_ROWS, }, - { - propertyName: "maxDynamicHeight", - label: "Max Height (in rows)", - helpText: "Maximum Height, after which contents will scroll", - controlType: "INPUT_TEXT", - dependencies: ["dynamicHeight"], - hidden: hideDynamicHeightPropertyControl, - isJSConvertible: false, - isBindProperty: false, - isTriggerProperty: false, + ); + } + + if (propertyValue === DynamicHeight.FIXED) { + updates.push({ + propertyPath: "originalBottomRow", + propertyValue: undefined, + }); + updates.push({ + propertyPath: "originalTopRow", + propertyValue: undefined, + }); + } + + // The following are updates which apply to specific widgets. + if ( + propertyValue === DynamicHeight.AUTO_HEIGHT || + propertyValue === DynamicHeight.AUTO_HEIGHT_WITH_LIMITS + ) { + if (props.dynamicHeight === DynamicHeight.FIXED) { + updates.push({ + propertyPath: "originalBottomRow", + propertyValue: props.bottomRow, + }); + updates.push({ + propertyPath: "originalTopRow", + propertyValue: props.topRow, + }); + } + if (!props.shouldScrollContents) { + updates.push({ + propertyPath: "shouldScrollContents", + propertyValue: true, + }); + } + if (props.overflow !== undefined) { + updates.push({ + propertyPath: "overflow", + propertyValue: "NONE", + }); + } + if (props.scrollContents === true) { + updates.push({ + propertyPath: "scrollContents", + propertyValue: false, + }); + } + if (props.fixedFooter === true) { + updates.push({ + propertyPath: "fixedFooter", + propertyValue: false, + }); + } + } + + return updates; +} + +// TODO FEATURE:(abhinav) Add validations to these properties + +const CONTAINER_SCROLL_HELPER_TEXT = + "This widget shows an internal scroll when you add widgets in edit mode. It'll resize after you've added widgets. The scroll won't exist in view mode."; + +export const PropertyPaneConfigTemplates: Record< + RegisteredWidgetFeatures, + PropertyPaneConfig[] +> = { + [RegisteredWidgetFeatures.DYNAMIC_HEIGHT]: [ + { + helpText: + "Auto Height: Configure the way the widget height reacts to content changes.", + propertyName: "dynamicHeight", + label: "Height", + controlType: "DROP_DOWN", + isBindProperty: false, + isTriggerProperty: false, + dependencies: [ + "shouldScrollContents", + "maxDynamicHeight", + "minDynamicHeight", + "bottomRow", + "topRow", + "overflow", + "dynamicHeight", + "isCanvas", + ], + updateHook: updateMinMaxDynamicHeight, + helperText: (props: WidgetProps) => { + return props.isCanvas && + props.dynamicHeight === DynamicHeight.AUTO_HEIGHT + ? CONTAINER_SCROLL_HELPER_TEXT + : ""; }, - ], - }, + options: [ + { + label: "Auto Height", + value: DynamicHeight.AUTO_HEIGHT, + }, + { + label: "Auto Height with limits", + value: DynamicHeight.AUTO_HEIGHT_WITH_LIMITS, + }, + { + label: "Fixed", + value: DynamicHeight.FIXED, + }, + ], + postUpdateAction: ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT, + }, + ], }; + +//TODO make this logic a lot cleaner +export function disableWidgetFeatures( + widgetType: WidgetType, + disabledWidgetFeatures?: string[], +): PropertyPaneConfig[] { + const widgetConfig = WidgetFactory.getWidgetPropertyPaneContentConfig( + widgetType, + ) as PropertyPaneConfig[]; + + if (!disabledWidgetFeatures || disabledWidgetFeatures.length <= 0) + return widgetConfig; + + const clonedConfig = klona(widgetConfig); + const GeneralConfig = clonedConfig.find( + (sectionConfig) => + (sectionConfig as PropertyPaneSectionConfig)?.sectionName === "General", + ); + + for (let i = 0; i < (GeneralConfig?.children?.length || -1); i++) { + const config = GeneralConfig?.children?.[i]; + if ( + disabledWidgetFeatures.indexOf( + (config as PropertyPaneControlConfig)?.propertyName || "", + ) > -1 + ) { + GeneralConfig?.children?.splice(i, 1); + i--; + } + } + + return clonedConfig; +} diff --git a/app/client/src/utils/WidgetRegisterHelpers.tsx b/app/client/src/utils/WidgetRegisterHelpers.tsx index e3fbcd1dd1..fc93371885 100644 --- a/app/client/src/utils/WidgetRegisterHelpers.tsx +++ b/app/client/src/utils/WidgetRegisterHelpers.tsx @@ -4,13 +4,17 @@ import * as Sentry from "@sentry/react"; import store from "store"; import BaseWidget from "widgets/BaseWidget"; -import WidgetFactory from "./WidgetFactory"; +import WidgetFactory, { NonSerialisableWidgetConfigs } from "./WidgetFactory"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import withMeta from "widgets/MetaHOC"; import { generateReactKey } from "./generators"; import { memoize } from "lodash"; -import { WidgetFeatureProps } from "./WidgetFeatures"; +import { + RegisteredWidgetFeatures, + WidgetFeaturePropertyEnhancements, + WidgetFeatureProps, +} from "./WidgetFeatures"; import { WidgetConfiguration } from "widgets/constants"; import withWidgetProps from "widgets/withWidgetProps"; @@ -45,18 +49,28 @@ export const registerWidget = (Widget: any, config: WidgetConfiguration) => { config.properties.styleConfig, config.features, config.properties.loadingProperties, + config.properties.stylesheetConfig, ); configureWidget(config); }; export const configureWidget = (config: WidgetConfiguration) => { - let features = {}; - if (config.features && config.features.dynamicHeight) { - features = Object.assign({}, WidgetFeatureProps.DYNAMIC_HEIGHT); + let features: Record = {}; + if (config.features) { + Object.keys(config.features).forEach((registeredFeature: string) => { + features = Object.assign( + {}, + WidgetFeatureProps[registeredFeature as RegisteredWidgetFeatures], + WidgetFeaturePropertyEnhancements[ + registeredFeature as RegisteredWidgetFeatures + ](config), + ); + }); } + const _config = { - ...features, ...config.defaults, + ...features, searchTags: config.searchTags, type: config.type, hideCard: !!config.hideCard || !config.iconSVG, @@ -66,12 +80,25 @@ export const configureWidget = (config: WidgetConfiguration) => { key: generateReactKey(), iconSVG: config.iconSVG, isCanvas: config.isCanvas, + canvasHeightOffset: config.canvasHeightOffset, }; + const nonSerialisableWidgetConfigs: Record = {}; + Object.values(NonSerialisableWidgetConfigs).forEach((entry) => { + if (_config[entry] !== undefined) { + nonSerialisableWidgetConfigs[entry] = _config[entry]; + } + delete _config[entry]; + }); + + WidgetFactory.storeNonSerialisablewidgetConfig( + config.type, + nonSerialisableWidgetConfigs, + ); + WidgetFactory.storeWidgetConfig(config.type, _config); + store.dispatch({ type: ReduxActionTypes.ADD_WIDGET_CONFIG, payload: _config, }); - - WidgetFactory.storeWidgetConfig(config.type, _config); }; diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index 07aafe1bc1..58a88a8cdd 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -162,7 +162,7 @@ import CodeScannerWidget, { CONFIG as CODE_SCANNER_WIDGET_CONFIG, } from "widgets/CodeScannerWidget"; -export const ALL_WIDGETS_AND_CONFIG = [ +export const ALL_WIDGETS_AND_CONFIG: [any, WidgetConfiguration][] = [ [CanvasWidget, CANVAS_WIDGET_CONFIG], [SkeletonWidget, SKELETON_WIDGET_CONFIG], [ContainerWidget, CONTAINER_WIDGET_CONFIG], diff --git a/app/client/src/utils/autoHeight/constants.ts b/app/client/src/utils/autoHeight/constants.ts index 4c4f254a7a..3b8272fed0 100644 --- a/app/client/src/utils/autoHeight/constants.ts +++ b/app/client/src/utils/autoHeight/constants.ts @@ -5,6 +5,7 @@ export type TreeNode = { bottomRow: number; originalTopRow: number; originalBottomRow: number; + distanceToNearestAbove: number; }; export type NodeSpace = { diff --git a/app/client/src/utils/autoHeight/generateTree.test.ts b/app/client/src/utils/autoHeight/generateTree.test.ts index 1a15087973..f723195aef 100644 --- a/app/client/src/utils/autoHeight/generateTree.test.ts +++ b/app/client/src/utils/autoHeight/generateTree.test.ts @@ -17,6 +17,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 30, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: [], @@ -25,6 +26,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 30, originalTopRow: 0, + distanceToNearestAbove: 0, }, }; @@ -36,7 +38,7 @@ describe("Generate Auto Height Layout tree", () => { it("Does conflict when part of the boxes overlap horizontally", () => { const input: NodeSpace[] = [ { left: 0, right: 100, top: 0, bottom: 30, id: "1" }, - { left: 80, top: 30, bottom: 40, right: 120, id: "2" }, + { left: 80, top: 40, bottom: 80, right: 120, id: "2" }, ]; const previousTree: Record = {}; const layoutUpdated = false; @@ -48,14 +50,16 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 30, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], belows: [], - topRow: 30, - bottomRow: 40, - originalBottomRow: 40, - originalTopRow: 30, + topRow: 40, + bottomRow: 80, + originalBottomRow: 80, + originalTopRow: 40, + distanceToNearestAbove: 10, }, }; @@ -77,6 +81,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 20, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -85,6 +90,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 40, originalBottomRow: 30, originalTopRow: 20, + distanceToNearestAbove: 0, }, }; const layoutUpdated = false; @@ -96,6 +102,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 20, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -104,6 +111,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 40, originalBottomRow: 30, originalTopRow: 20, + distanceToNearestAbove: 0, }, }; @@ -125,6 +133,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 20, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -133,6 +142,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 40, originalBottomRow: 30, originalTopRow: 20, + distanceToNearestAbove: 0, }, }; const layoutUpdated = true; @@ -144,6 +154,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 30, originalBottomRow: 30, originalTopRow: 0, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -152,6 +163,7 @@ describe("Generate Auto Height Layout tree", () => { bottomRow: 40, originalBottomRow: 40, originalTopRow: 30, + distanceToNearestAbove: 0, }, }; diff --git a/app/client/src/utils/autoHeight/generateTree.ts b/app/client/src/utils/autoHeight/generateTree.ts index e18eabf5c3..dbfb13ee8d 100644 --- a/app/client/src/utils/autoHeight/generateTree.ts +++ b/app/client/src/utils/autoHeight/generateTree.ts @@ -1,6 +1,7 @@ import { areIntersecting } from "utils/boxHelpers"; import { pushToArray } from "utils/helpers"; import { MAX_BOX_SIZE, NodeSpace, TreeNode } from "./constants"; +import { getNearestAbove } from "./helpers"; // This function uses the spaces occupied by sibling boxes and provides us with // a data structure which defines the relative vertical positioning of the boxes @@ -11,7 +12,15 @@ export function generateTree( previousTree: Record, ): Record { // If widget doesn't exist in this DS, this means that its height changes does not effect any other sibling - spaces.sort((a, b) => a.top - b.top); // Sort based on position, top to bottom, so that we know which is above the other + spaces.sort((a, b) => { + //if both are of the same level and previous tree exists, check originalTops + if (a.top === b.top && previousTree[a.id] && previousTree[b.id]) { + return ( + previousTree[a.id].originalTopRow - previousTree[b.id].originalTopRow + ); + } + return a.top - b.top; + }); // Sort based on position, top to bottom, so that we know which is above the other const _spaces = [...spaces]; const aboveMap: Record = {}; @@ -68,6 +77,7 @@ export function generateTree( if (originalBottomRow === undefined || layoutUpdated) { originalBottomRow = currentSpace.bottom - MAX_BOX_SIZE; } + tree[currentSpace.id] = { aboves: aboveMap[currentSpace.id] || [], belows: belowMap[currentSpace.id] || [], @@ -75,9 +85,21 @@ export function generateTree( bottomRow: currentSpace.bottom - MAX_BOX_SIZE, originalTopRow, originalBottomRow, + distanceToNearestAbove: 0, }; } } + for (const boxId in tree) { + // For each box, get the nearest above node + // Then get the distance between this node and the nearest above + // We'll try to maintain this distance when reflowing due to auto height + const nearestAbove = getNearestAbove(tree, boxId, {}); + if (nearestAbove.length > 0) { + tree[boxId].distanceToNearestAbove = + tree[boxId].topRow - tree[nearestAbove[0]].bottomRow; + } + } + return tree; } diff --git a/app/client/src/utils/autoHeight/helpers.ts b/app/client/src/utils/autoHeight/helpers.ts new file mode 100644 index 0000000000..de6b058595 --- /dev/null +++ b/app/client/src/utils/autoHeight/helpers.ts @@ -0,0 +1,58 @@ +import { TreeNode } from "./constants"; + +/** + * Gets the nearest above box for the current box. Including the aboves which have changes so far. + * + * @param tree: Auto Height Layout Tree + * @param effectedBoxId: Current box in consideration + * @param repositionedBoxes: Boxes repositioned so far + * @returns An array of boxIds which are above and nearest the effectedBoxId + */ +export function getNearestAbove( + tree: Record, + effectedBoxId: string, + repositionedBoxes: Record, +) { + // Get all the above boxes + const aboves = tree[effectedBoxId].aboves; + // We're trying to find the nearest boxes above this box + + return aboves.reduce((prev: string[], next: string) => { + if (!prev[0]) return [next]; + // Get the bottomRow of the above box + let nextBottomRow = tree[next].bottomRow; + let prevBottomRow = tree[prev[0]].bottomRow; + // If we've already repositioned this, use the new bottomRow of the box + if (repositionedBoxes[next]) { + nextBottomRow = repositionedBoxes[next].bottomRow; + } + if (repositionedBoxes[prev[0]]) { + prevBottomRow = repositionedBoxes[prev[0]].bottomRow; + } + + // If the current box's (next) bottomRow is larger than the previous + // This (next) box is the bottom most above so far + if (nextBottomRow > prevBottomRow) return [next]; + // If this (next) box's bottom row is the same as the previous + // We have two bottom most boxes + else if (nextBottomRow === prevBottomRow) { + if ( + repositionedBoxes[prev[0]] && + repositionedBoxes[prev[0]].bottomRow === + repositionedBoxes[prev[0]].topRow + ) { + return prev; + } + if ( + repositionedBoxes[next] && + repositionedBoxes[next].bottomRow === repositionedBoxes[next].topRow + ) { + return [next]; + } + return [...prev, next]; + } + // This (next) box's bottom row is lower than the boxes selected so far + // so, we ignore it. + else return prev; + }, []); +} diff --git a/app/client/src/utils/autoHeight/reflow.test.ts b/app/client/src/utils/autoHeight/reflow.test.ts index 21190ae337..22a3960fbf 100644 --- a/app/client/src/utils/autoHeight/reflow.test.ts +++ b/app/client/src/utils/autoHeight/reflow.test.ts @@ -18,6 +18,7 @@ describe("reflow", () => { bottomRow: box1BottomRow, originalTopRow: box1TopRow, originalBottomRow: box1BottomRow, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -26,6 +27,7 @@ describe("reflow", () => { bottomRow: box2BottomRow, originalTopRow: box2TopRow, originalBottomRow: box2BottomRow, + distanceToNearestAbove: 10, }, }; @@ -70,6 +72,7 @@ describe("reflow", () => { bottomRow: box1BottomRow, originalTopRow: box1OriginalTopRow, originalBottomRow: box1OriginalBottomRow, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -78,6 +81,7 @@ describe("reflow", () => { bottomRow: box2BottomRow, originalTopRow: box2OriginalTopRow, originalBottomRow: box2OriginalBottomRow, + distanceToNearestAbove: 10, }, }; @@ -122,6 +126,7 @@ describe("reflow", () => { bottomRow: box1BottomRow, originalTopRow: box1OriginalTopRow, originalBottomRow: box1OriginalBottomRow, + distanceToNearestAbove: 0, }, "2": { aboves: ["1"], @@ -130,6 +135,7 @@ describe("reflow", () => { bottomRow: box2BottomRow, originalTopRow: box2OriginalTopRow, originalBottomRow: box2OriginalBottomRow, + distanceToNearestAbove: 40, }, }; @@ -173,6 +179,7 @@ describe("reflow", () => { bottomRow: 120, originalBottomRow: 20, originalTopRow: 10, + distanceToNearestAbove: 0, }; const tree: Record = { @@ -183,6 +190,7 @@ describe("reflow", () => { bottomRow: box1BottomRow, originalTopRow: box1OriginalTopRow, originalBottomRow: box1OriginalBottomRow, + distanceToNearestAbove: 0, }, "2": { aboves: ["1", "3"], @@ -191,6 +199,7 @@ describe("reflow", () => { bottomRow: box2BottomRow, originalTopRow: box2OriginalTopRow, originalBottomRow: box2OriginalBottomRow, + distanceToNearestAbove: 20, }, "3": box3, }; @@ -208,8 +217,8 @@ describe("reflow", () => { bottomRow: box1BottomRow + box1DeltaHeight, }, "2": { - topRow: 130, - bottomRow: 170, + topRow: 140, + bottomRow: 180, }, }; diff --git a/app/client/src/utils/autoHeight/reflow.ts b/app/client/src/utils/autoHeight/reflow.ts index 15c421dffb..9118b50af4 100644 --- a/app/client/src/utils/autoHeight/reflow.ts +++ b/app/client/src/utils/autoHeight/reflow.ts @@ -1,92 +1,21 @@ -import { pushToArray } from "utils/helpers"; import { TreeNode } from "./constants"; +import { getNearestAbove } from "./helpers"; -/** - * - * @param tree : Auto Height Layout Tree - * @param effectedBoxId : Current box in consideration - * @param aboveId : Above box which may or maynot have changed - * @param offsetSoFar : Offset of the above box, or changes to be applied so far - * @returns : The offset expected to be applied to the effectedBoxId. This is how much this box should move - */ -export function getNegativeOffset( +function getAllEffectedBoxes( + effectorBoxId: string, tree: Record, - effectedBoxId: string, - aboveId: string, - offsetSoFar = 0, -): number { - if (offsetSoFar <= 0) { - // Let's take in to account the old spacing between the effected box and bottom most above box - // when the layout was last updated. - const oldSpacing = - tree[effectedBoxId].originalTopRow - tree[aboveId].originalBottomRow; - // Let's compute the spacing between the effected box and bottom most above box - const currentSpacing = tree[effectedBoxId].topRow - tree[aboveId].bottomRow; - // If the old spacing is less than current spacing and the offset of the bottom most above, - // we need to make sure that we're sticking to the original spacing between the bottom most above - // and the current effected box. - // Note: This applies only if the offset is negative, which is to say that the box is to move up - if (oldSpacing < currentSpacing + offsetSoFar) { - return oldSpacing + offsetSoFar - currentSpacing; + effectedBoxes: string[] = [], + _processed: { [key: string]: boolean } = {}, +): string[] { + const belows = tree[effectorBoxId].belows; + belows.forEach((belowId) => { + if (!_processed[belowId]) { + getAllEffectedBoxes(belowId, tree, effectedBoxes, _processed); + (effectedBoxes as string[]).push(belowId); + _processed[belowId] = true; } - } - return offsetSoFar; -} -/** - * Gets the nearest above box for the current box. Including the aboves which have changes so far. - * - * @param tree: Auto Height Layout Tree - * @param effectedBoxId: Current box in consideration - * @param repositionedBoxes: Boxes repositioned so far - * @returns An array of boxIds which are above and nearest the effectedBoxId - */ -export function getNearestAbove( - tree: Record, - effectedBoxId: string, - repositionedBoxes: Record, -) { - // Get all the above boxes - const aboves = tree[effectedBoxId].aboves; - // We're trying to find the nearest boxes above this box - - return aboves.reduce((prev: string[], next: string) => { - if (!prev[0]) return [next]; - // Get the bottomRow of the above box - let nextBottomRow = tree[next].bottomRow; - let prevBottomRow = tree[prev[0]].bottomRow; - // If we've already repositioned this, use the new bottomRow of the box - if (repositionedBoxes[next]) { - nextBottomRow = repositionedBoxes[next].bottomRow; - } - if (repositionedBoxes[prev[0]]) { - prevBottomRow = repositionedBoxes[prev[0]].bottomRow; - } - - // If the current box's (next) bottomRow is larger than the previous - // This (next) box is the bottom most above so far - if (nextBottomRow > prevBottomRow) return [next]; - // If this (next) box's bottom row is the same as the previous - // We have two bottom most boxes - else if (nextBottomRow === prevBottomRow) { - if ( - repositionedBoxes[prev[0]] && - repositionedBoxes[prev[0]].bottomRow === - repositionedBoxes[prev[0]].topRow - ) { - return prev; - } - if ( - repositionedBoxes[next] && - repositionedBoxes[next].bottomRow === repositionedBoxes[next].topRow - ) { - return [next]; - } - return [...prev, next]; - } - // This (next) box's bottom row is lower than the boxes selected so far - // so, we ignore it. - else return prev; - }, []); + }); + return effectedBoxes; } // This function computes the new positions for boxes based on the boxes which have changed height @@ -101,7 +30,11 @@ export function computeChangeInPositionBasedOnDelta( { topRow: number; bottomRow: number } > = {}; - const effectedBoxMap: Record = {}; + let effectedBoxes: string[] = []; + + // This value stores all the effectedBoxes that have already been computed, + // So that it doesn't repeat itself while computing the effectedBoxes + const _processed = {}; // For each box which has changed height (box delta) for (const boxId in delta) { @@ -110,12 +43,7 @@ export function computeChangeInPositionBasedOnDelta( // We simply take all the boxes which are below this box from the tree // and add the delta to the effectedBoxMap where the key is the below boxId from the tree - tree[boxId].belows.forEach((effectedId) => { - effectedBoxMap[effectedId] = pushToArray( - delta[boxId], - effectedBoxMap[effectedId], - ) as number[]; - }); + effectedBoxes = getAllEffectedBoxes(boxId, tree, effectedBoxes, _processed); // Add this box's delta to the repositioning, as this won't show up in the effectedBoxMap repositionedBoxes[boxId] = { @@ -125,69 +53,45 @@ export function computeChangeInPositionBasedOnDelta( } // Sort the effected box ids, this is to make sure we compute from top to bottom. - const sortedEffectedBoxIds = Object.keys(effectedBoxMap).sort( + const sortedEffectedBoxIds = effectedBoxes.sort( (a, b) => tree[a].topRow - tree[b].topRow, ); // For each of the boxes which have been effected for (const effectedBoxId of sortedEffectedBoxIds) { - let _offset; + let _offset = 0; const bottomMostAboves = getNearestAbove( tree, effectedBoxId, repositionedBoxes, ); + // for each of the bottom most above boxes. // Note: There can be more than one if two above widgets have the same bottomrow for (const aboveId of bottomMostAboves) { // If the above box has been effected by another box change height // Or, if this above box itself has changed height - if (Array.isArray(effectedBoxMap[aboveId]) || delta[aboveId]) { - // In case the above box has changed heights - const _aboveOffset = repositionedBoxes[aboveId] - ? repositionedBoxes[aboveId].bottomRow - tree[aboveId].bottomRow - : 0; - - // If so far, we haven't got any _offset updates - // This can happen if this is the first aboveId we're checking - if (_offset === undefined) _offset = _aboveOffset; - - const negativeOffset = getNegativeOffset( - tree, - effectedBoxId, - aboveId, - _aboveOffset, - ); - - // If the bottom most above (_aboveOffset), has moved down (either by increasing height and/or due to its above) - // Let's take the effected boxs' change to be the max of _offset and _aboveOffset - // The _offset so far will be due to other bottomMostAbove effecting this effected box. - if (_aboveOffset > 0) _offset = Math.max(_aboveOffset, _offset); - // If the bottom most above (_aboveOffset) has moved up (either by decreasing height and/or due to its above) - // Let's take the Min (negative values, so max offset in the upward direction) of the _aboveOffset, _offset, negativeOffset. - else if (_aboveOffset < 0) { - _offset = Math.min(_aboveOffset, _offset, negativeOffset); + if (effectedBoxes.includes(aboveId) || delta[aboveId]) { + // If we have the above repositioned + if (repositionedBoxes[aboveId]) { + // Get the new expected top row of this effectedBox + const newTopRow = + repositionedBoxes[aboveId].bottomRow + + tree[effectedBoxId].distanceToNearestAbove; + // Get the offset this effectedBox needs to consider moving + _offset = newTopRow - tree[effectedBoxId].topRow; + } else { + // Since the above hasn't changed, don't change this. + _offset = 0; } } else { - // Stick to the widget above if the bottomMost above box hasn't changed - // TODO(abhinav): Here we may want to use the same logic as negativeOffset using originals as done previously. - // Test this. - // Let's take in to account the old spacing between the effected box and bottom most above box - // when the layout was last updated. - const negativeOffset = getNegativeOffset(tree, effectedBoxId, aboveId); - _offset = negativeOffset; + // Maintain distance from the bottom most above. + const newTopRow = + tree[aboveId].bottomRow + tree[effectedBoxId].distanceToNearestAbove; + _offset = newTopRow - tree[effectedBoxId].topRow; } } - // If _offset is not defined, this means that this box is the topmost box - if (_offset === undefined) { - // The effectedBoxId is the topmost box, so the _offset will most likely always be 0 - _offset = effectedBoxMap[effectedBoxId].reduce( - (prev, next) => prev + next, - 0, - ); - } - // Finally update the repositioned box with the _offset. if (repositionedBoxes[effectedBoxId]) { repositionedBoxes[effectedBoxId].bottomRow += _offset; diff --git a/app/client/src/utils/autocomplete/AutocompleteSortRules.ts b/app/client/src/utils/autocomplete/AutocompleteSortRules.ts index 68a3ec55b8..715a876905 100644 --- a/app/client/src/utils/autocomplete/AutocompleteSortRules.ts +++ b/app/client/src/utils/autocomplete/AutocompleteSortRules.ts @@ -5,7 +5,7 @@ import { Completion, createCompletionHeader, DataTreeDefEntityInformation, -} from "./TernServer"; +} from "./CodemirrorTernService"; interface AutocompleteRule { computeScore(completion: Completion): number; diff --git a/app/client/src/utils/autocomplete/TernServer.ts b/app/client/src/utils/autocomplete/CodemirrorTernService.ts similarity index 98% rename from app/client/src/utils/autocomplete/TernServer.ts rename to app/client/src/utils/autocomplete/CodemirrorTernService.ts index 28e12e84de..8f4fb61051 100644 --- a/app/client/src/utils/autocomplete/TernServer.ts +++ b/app/client/src/utils/autocomplete/CodemirrorTernService.ts @@ -21,6 +21,7 @@ import { FieldEntityInformation } from "components/editorComponents/CodeEditor/E import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import { AutocompleteSorter } from "./AutocompleteSortRules"; import { getCompletionsForKeyword } from "./keywordCompletion"; +import TernWorkerServer from "./TernWorkerService"; const DEFS: Def[] = [ // @ts-expect-error: Types are not available @@ -129,7 +130,7 @@ export function typeToIcon(type: string, isKeyword: boolean) { return cls + "completion " + cls + "completion-" + suffix; } -class TernServer { +class CodeMirrorTernService { server: Server; docs: TernDocs = Object.create(null); cachedArgHints: ArgHints | null = null; @@ -139,12 +140,11 @@ class TernServer { string, DataTreeDefEntityInformation >(); + options: { async: boolean; defs: Def[] }; - constructor() { - this.server = new tern.Server({ - async: true, - defs: DEFS, - }); + constructor(options: { async: boolean; defs: Def[] }) { + this.options = options; + this.server = new TernWorkerServer(this); } resetServer() { @@ -846,4 +846,7 @@ export const createCompletionHeader = (name: string): Completion => ({ isHeader: true, }); -export default new TernServer(); +export default new CodeMirrorTernService({ + async: true, + defs: DEFS, +}); diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 91644633d5..c7e4f37988 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -155,6 +155,7 @@ export const entityDefinitions = { updatedRowIndices: generateTypeDef(widget.updatedRowIndices), triggeredRowIndex: generateTypeDef(widget.triggeredRowIndex), pageOffset: generateTypeDef(widget.pageOffset), + tableHeaders: generateTypeDef(widget.tableHeaders), newRow: generateTypeDef(widget.newRow), isAddRowInProgress: "bool", }), diff --git a/app/client/src/utils/autocomplete/TernServer.test.ts b/app/client/src/utils/autocomplete/TernServer.test.ts index 0adbb04b0b..bd4f8ba9c8 100644 --- a/app/client/src/utils/autocomplete/TernServer.test.ts +++ b/app/client/src/utils/autocomplete/TernServer.test.ts @@ -1,9 +1,9 @@ -import TernServer, { +import CodemirrorTernService, { AutocompleteDataType, Completion, createCompletionHeader, DataTreeDefEntityInformation, -} from "./TernServer"; +} from "./CodemirrorTernService"; import { MockCodemirrorEditor } from "../../../test/__mocks__/CodeMirrorEditorMock"; import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; import _ from "lodash"; @@ -63,7 +63,9 @@ describe("Tern server", () => { ]; testCases.forEach((testCase) => { - const { value } = TernServer.getFocusedDocValueAndPos(testCase.input); + const { value } = CodemirrorTernService.getFocusedDocValueAndPos( + testCase.input, + ); expect(value).toBe(testCase.expectedOutput); }); }); @@ -125,13 +127,12 @@ describe("Tern server", () => { ]; testCases.forEach((testCase) => { - const request = TernServer.buildRequest(testCase.input, {}); + const request = CodemirrorTernService.buildRequest(testCase.input, {}); expect(request.query.end).toEqual(testCase.expectedOutput); }); }); - it(`Check whether the position is evaluated correctly for placing the selected - autocomplete value`, () => { + it(`Check whether the position is evaluated correctly for placing the selected autocomplete value`, () => { const testCases = [ { input: { @@ -186,13 +187,18 @@ describe("Tern server", () => { testCase.input.codeEditor.doc, ); - const value: any = TernServer.requestCallback( + const mockAddFile = jest.fn(); + CodemirrorTernService.server.addFile = mockAddFile; + + const value: any = CodemirrorTernService.requestCallback( null, testCase.input.requestCallbackData, (MockCodemirrorEditor as unknown) as CodeMirror.Editor, () => null, ); + expect(mockAddFile).toBeCalled(); + expect(value.from).toEqual(testCase.expectedOutput); }); }); @@ -342,12 +348,12 @@ describe("Tern server sorting", () => { ]; it("shows best match results", () => { - TernServer.setEntityInformation({ + CodemirrorTernService.setEntityInformation({ entityName: "sameEntity", entityType: ENTITY_TYPE.WIDGET, expectedType: AutocompleteDataType.OBJECT, }); - TernServer.defEntityInformation = defEntityInformation; + CodemirrorTernService.defEntityInformation = defEntityInformation; const sortedCompletions = AutocompleteSorter.sort( _.shuffle(completions), { diff --git a/app/client/src/utils/autocomplete/TernWorkerService.ts b/app/client/src/utils/autocomplete/TernWorkerService.ts new file mode 100644 index 0000000000..e308da7f23 --- /dev/null +++ b/app/client/src/utils/autocomplete/TernWorkerService.ts @@ -0,0 +1,82 @@ +import { Def, Server } from "tern"; +import { CallbackFn, TernWorkerAction } from "./types"; + +const ternWorker = new Worker( + new URL("../../workers/Tern/tern.worker.ts", import.meta.url), + { + name: "TernWorker", + type: "module", + }, +); + +function getFile(ts: any, name: string, c: CallbackFn) { + const buf = ts.docs[name]; + if (buf) c(ts.docValue(ts, buf)); + else if (ts.options.getFile) ts.options.getFile(name, c); + else c(null); +} + +type TernWorkerServerConstructor = { + (ts: any): void; + new (ts: any): Server; +}; + +function TernWorkerServer(this: any, ts: any) { + const worker = (ts.worker = ternWorker); + worker.postMessage({ + type: TernWorkerAction.INIT, + defs: ts.options.defs, + plugins: ts.options.plugins, + scripts: ts.options.workerDeps, + }); + let msgId = 0; + let pending: { [x: number]: CallbackFn } = {}; + + function send(data: any, c?: CallbackFn) { + if (c) { + data.id = ++msgId; + pending[msgId] = c; + } + worker.postMessage(data); + } + worker.onmessage = function(e) { + const data = e.data; + if (data.type == TernWorkerAction.GET_FILE) { + getFile(ts, data.name, function(err, text) { + send({ + type: TernWorkerAction.GET_FILE, + err: String(err), + text: text, + id: data.id, + }); + }); + } else if (data.type == TernWorkerAction.DEBUG) { + window.console.log(data.message); + } else if (data.id && pending[data.id]) { + pending[data.id](data.err, data.body); + delete pending[data.id]; + } + }; + worker.onerror = function(e) { + for (const id in pending) pending[id](e); + pending = {}; + }; + + this.addFile = function(name: string, text: string) { + send({ type: TernWorkerAction.ADD_FILE, name: name, text: text }); + }; + this.delFile = function(name: string) { + send({ type: TernWorkerAction.DELETE_FILE, name: name }); + }; + this.request = function(body: any, c: CallbackFn) { + send({ type: TernWorkerAction.REQUEST, body: body }, c); + }; + this.addDefs = function(defs: Def) { + send({ type: TernWorkerAction.ADD_DEF, defs }); + }; + this.deleteDefs = function(name: string) { + send({ type: TernWorkerAction.DELETE_DEF, name }); + }; +} + +export default TernWorkerServer as TernWorkerServerConstructor; diff --git a/app/client/src/utils/autocomplete/customDefUtils.ts b/app/client/src/utils/autocomplete/customDefUtils.ts index fda5961fb3..a37ab6efb4 100644 --- a/app/client/src/utils/autocomplete/customDefUtils.ts +++ b/app/client/src/utils/autocomplete/customDefUtils.ts @@ -5,7 +5,7 @@ import { AdditionalDynamicDataTree, customTreeTypeDefCreator, } from "./customTreeTypeDefCreator"; -import TernServer from "./TernServer"; +import CodemirrorTernService from "./CodemirrorTernService"; class CustomDef { private static lastCustomDataDef: AdditionalDynamicDataTree | undefined; @@ -16,7 +16,7 @@ class CustomDef { if (!equal(CustomDef.lastCustomDataDef, customDataDef)) { const start = performance.now(); - TernServer.updateDef("customDataTree", customDataDef); + CodemirrorTernService.updateDef("customDataTree", customDataDef); debug( "Tern: updateDef for customDataTree took", @@ -28,7 +28,7 @@ class CustomDef { } } else if (CustomDef.lastCustomDataDef) { const start = performance.now(); - TernServer.removeDef("customDataTree"); + CodemirrorTernService.removeDef("customDataTree"); debug( "Tern: removeDef for customDataTree took", (performance.now() - start).toFixed(), @@ -40,7 +40,7 @@ class CustomDef { } /** - * This method is responsible for both add and remove def in TernServer for customDataTree. + * This method is responsible for both add and remove def in CodemirrorTernService for customDataTree. * * if customData is not defined then check if lastCustomDataDef was present and remove it. * diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts index 10b1cc72c4..c4000cf01d 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.ts @@ -10,7 +10,7 @@ import { isTrueObject, isWidget, } from "workers/Evaluation/evaluationUtils"; -import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer"; +import { DataTreeDefEntityInformation } from "utils/autocomplete/CodemirrorTernService"; export type ExtraDef = Record; diff --git a/app/client/src/utils/autocomplete/dataTypeSortRules.ts b/app/client/src/utils/autocomplete/dataTypeSortRules.ts index 82a6db24a6..cab71ba85d 100644 --- a/app/client/src/utils/autocomplete/dataTypeSortRules.ts +++ b/app/client/src/utils/autocomplete/dataTypeSortRules.ts @@ -1,4 +1,4 @@ -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export const PriorityOrder: Record = { STRING: ["selectedRow", "data", "text"], diff --git a/app/client/src/utils/autocomplete/keywordCompletion.ts b/app/client/src/utils/autocomplete/keywordCompletion.ts index 564aa960b3..cdbbfbe5da 100644 --- a/app/client/src/utils/autocomplete/keywordCompletion.ts +++ b/app/client/src/utils/autocomplete/keywordCompletion.ts @@ -1,4 +1,4 @@ -import { Completion } from "./TernServer"; +import { Completion } from "./CodemirrorTernService"; export const getCompletionsForKeyword = ( completion: Completion, diff --git a/app/client/src/utils/autocomplete/types.ts b/app/client/src/utils/autocomplete/types.ts new file mode 100644 index 0000000000..c353ffd570 --- /dev/null +++ b/app/client/src/utils/autocomplete/types.ts @@ -0,0 +1,12 @@ +export enum TernWorkerAction { + INIT = "INIT", + ADD_FILE = "ADD_FILE", + DELETE_FILE = "DELETE_FILE", + REQUEST = "REQUEST", + GET_FILE = "GET_FILE", + DELETE_DEF = "DELETE_DEF", + ADD_DEF = "ADD_DEF", + DEBUG = "DEBUG", +} + +export type CallbackFn = (...args: any) => any; diff --git a/app/client/src/utils/helpers.test.ts b/app/client/src/utils/helpers.test.ts index 60668521b4..1b6aea72fd 100644 --- a/app/client/src/utils/helpers.test.ts +++ b/app/client/src/utils/helpers.test.ts @@ -1,7 +1,7 @@ import { RenderModes } from "constants/WidgetConstants"; import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "./autocomplete/TernServer"; +import { AutocompleteDataType } from "./autocomplete/CodemirrorTernService"; import { flattenObject, getLocale, diff --git a/app/client/src/utils/helpers.tsx b/app/client/src/utils/helpers.tsx index aa346748a2..3780662823 100644 --- a/app/client/src/utils/helpers.tsx +++ b/app/client/src/utils/helpers.tsx @@ -228,7 +228,10 @@ export const quickScrollToWidget = (widgetId?: string) => { const canvas = document.getElementById("canvas-viewport"); if (el && canvas && !isElementVisibleInContainer(el, canvas)) { - el.scrollIntoView({ block: "center", behavior: "smooth" }); + el.scrollIntoView({ + block: "nearest", + behavior: "smooth", + }); } }, 200); }; @@ -242,10 +245,14 @@ function isElementVisibleInContainer( const elementRect = element.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); return ( - elementRect.top >= containerRect.top && - elementRect.left >= containerRect.left && - elementRect.bottom <= containerRect.bottom && - elementRect.right <= containerRect.right + ((elementRect.top > containerRect.top && + elementRect.top < containerRect.bottom) || + (elementRect.bottom < containerRect.bottom && + elementRect.bottom > containerRect.top)) && + ((elementRect.left > containerRect.left && + elementRect.left < containerRect.right) || + (elementRect.right < containerRect.right && + elementRect.right > containerRect.left)) ); } diff --git a/app/client/src/utils/hooks/autoHeightUIHooks.ts b/app/client/src/utils/hooks/autoHeightUIHooks.ts new file mode 100644 index 0000000000..59e6aa14b6 --- /dev/null +++ b/app/client/src/utils/hooks/autoHeightUIHooks.ts @@ -0,0 +1,22 @@ +import { ReduxActionTypes } from "ce/constants/ReduxActionConstants"; +import { AppState } from "ce/reducers"; +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +export const useAutoHeightUIState = () => { + const dispatch = useDispatch(); + return { + isAutoHeightWithLimitsChanging: useSelector( + (state: AppState) => state.ui.autoHeightUI.isAutoHeightWithLimitsChanging, + ), + setIsAutoHeightWithLimitsChanging: useCallback( + (isAutoHeightWithLimitsChanging: boolean) => { + dispatch({ + type: ReduxActionTypes.SET_AUTO_HEIGHT_WITH_LIMITS_CHANGING, + payload: { isAutoHeightWithLimitsChanging }, + }); + }, + [dispatch], + ), + }; +}; diff --git a/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts b/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts index a1399115b6..5c55530655 100644 --- a/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts +++ b/app/client/src/utils/hooks/useAllowEditorDragToSelect.ts @@ -1,9 +1,9 @@ import { AppState } from "@appsmith/reducers"; import { - previewModeSelector, snipingModeSelector, + previewModeSelector, } from "selectors/editorSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; export const useAllowEditorDragToSelect = () => { // This state tells us whether a `ResizableComponent` is resizing diff --git a/app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts b/app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts index 094a7c5659..3a177ffacc 100644 --- a/app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts +++ b/app/client/src/utils/hooks/useCanvasMinHeightUpdateHook.ts @@ -5,7 +5,7 @@ import { AppState } from "@appsmith/reducers"; import { APP_MODE } from "entities/App"; import { getWidget } from "sagas/selectors"; import { getAppMode } from "selectors/applicationSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { updateWidgetMetaPropAndEval } from "actions/metaActions"; import WidgetFactory from "utils/WidgetFactory"; diff --git a/app/client/src/utils/hooks/useClickToSelectWidget.tsx b/app/client/src/utils/hooks/useClickToSelectWidget.tsx index 2854a5df76..d6101c2126 100644 --- a/app/client/src/utils/hooks/useClickToSelectWidget.tsx +++ b/app/client/src/utils/hooks/useClickToSelectWidget.tsx @@ -1,5 +1,5 @@ import { getIsPropertyPaneVisible } from "selectors/propertyPaneSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { AppState } from "@appsmith/reducers"; import { useWidgetSelection } from "./useWidgetSelection"; import React, { ReactNode, useCallback } from "react"; @@ -41,15 +41,17 @@ export function ClickContentToOpenPropPane({ e.stopPropagation(); }; + const styles = { + width: "100%", + height: "100%", + }; + return (
{children}
diff --git a/app/client/src/utils/hooks/useDynamicAppLayout.tsx b/app/client/src/utils/hooks/useDynamicAppLayout.tsx index aa1504c4e4..8bdbd498ba 100644 --- a/app/client/src/utils/hooks/useDynamicAppLayout.tsx +++ b/app/client/src/utils/hooks/useDynamicAppLayout.tsx @@ -22,7 +22,6 @@ import { useWindowSizeHooks } from "./dragResizeHooks"; import { getAppMode } from "selectors/entitiesSelector"; import { updateCanvasLayoutAction } from "actions/editorActions"; import { getIsCanvasInitialized } from "selectors/mainCanvasSelectors"; -import { calculateDynamicHeight } from "utils/DSLMigrations"; const BORDERS_WIDTH = 2; const GUTTER_WIDTH = 72; @@ -32,19 +31,19 @@ export const useDynamicAppLayout = () => { const explorerWidth = useSelector(getExplorerWidth); const isExplorerPinned = useSelector(getExplorerPinned); const appMode: APP_MODE | undefined = useSelector(getAppMode); - const { height: screenHeight, width: screenWidth } = useWindowSizeHooks(); + const { width: screenWidth } = useWindowSizeHooks(); const mainCanvasProps = useSelector(getMainCanvasProps); const isPreviewMode = useSelector(previewModeSelector); const currentPageId = useSelector(getCurrentPageId); const isCanvasInitialized = useSelector(getIsCanvasInitialized); const appLayout = useSelector(getCurrentApplicationLayout); - /** - * calculates min height - */ - const calculatedMinHeight = useMemo(() => { - return calculateDynamicHeight(); - }, [mainCanvasProps]); + // /** + // * calculates min height + // */ + // const calculatedMinHeight = useMemo(() => { + // return calculateDynamicHeight(); + // }, [mainCanvasProps]); /** * app layout range i.e minWidth and maxWidth for the current layout @@ -135,7 +134,7 @@ export const useDynamicAppLayout = () => { const { width: rightColumn } = mainCanvasProps || {}; if (rightColumn !== calculatedWidth || !isCanvasInitialized) { - dispatch(updateCanvasLayoutAction(calculatedWidth, calculatedMinHeight)); + dispatch(updateCanvasLayoutAction(calculatedWidth)); } }; @@ -147,13 +146,11 @@ export const useDynamicAppLayout = () => { /** * when screen height is changed, update canvas layout */ - useEffect(() => { - if (calculatedMinHeight !== mainCanvasProps?.height) { - dispatch( - updateCanvasLayoutAction(mainCanvasProps?.width, calculatedMinHeight), - ); - } - }, [screenHeight, mainCanvasProps?.height]); + // useEffect(() => { + // if (calculatedMinHeight !== mainCanvasProps?.height) { + // // dispatch(updateCanvasLayoutAction(mainCanvasProps?.width)); + // } + // }, [screenHeight, mainCanvasProps?.height]); useEffect(() => { if (isCanvasInitialized) debouncedResize(); diff --git a/app/client/src/utils/hooks/usePositionedContainerZIndex.ts b/app/client/src/utils/hooks/usePositionedContainerZIndex.ts index c43bd4ebcd..d71fa28899 100644 --- a/app/client/src/utils/hooks/usePositionedContainerZIndex.ts +++ b/app/client/src/utils/hooks/usePositionedContainerZIndex.ts @@ -4,7 +4,7 @@ import { Layers } from "constants/Layers"; import { useMemo } from "react"; import { AppState } from "@appsmith/reducers"; import { isWidgetSelected } from "selectors/widgetSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; export const usePositionedContainerZIndex = ( props: PositionedContainerProps, diff --git a/app/client/src/utils/hooks/useWidgetConfig.ts b/app/client/src/utils/hooks/useWidgetConfig.ts new file mode 100644 index 0000000000..0e464a95f6 --- /dev/null +++ b/app/client/src/utils/hooks/useWidgetConfig.ts @@ -0,0 +1,11 @@ +import { AppState } from "@appsmith/reducers"; + +import { useSelector } from "react-redux"; +import { WidgetType } from "utils/WidgetFactory"; + +export default function useWidgetConfig(type: WidgetType, attr: string) { + const config = useSelector( + (state: AppState) => state.entities.widgetConfig.config[type], + ); + return config[attr]; +} diff --git a/app/client/src/utils/migrations/MigrateLabelPosition.ts b/app/client/src/utils/migrations/MigrateLabelPosition.ts new file mode 100644 index 0000000000..b09767c297 --- /dev/null +++ b/app/client/src/utils/migrations/MigrateLabelPosition.ts @@ -0,0 +1,16 @@ +import { LabelPosition } from "components/constants"; +import { traverseDSLAndMigrate } from "utils/WidgetMigrationUtils"; +import { WidgetProps } from "widgets/BaseWidget"; +import { DSLWidget } from "widgets/constants"; + +export function migrateLabelPosition(currentDSL: DSLWidget) { + return traverseDSLAndMigrate(currentDSL, (widget: WidgetProps) => { + if ( + (widget.type === "PHONE_INPUT_WIDGET" || + widget.type === "CURRENCY_INPUT_WIDGET") && + widget.labelPosition === undefined + ) { + widget.labelPosition = LabelPosition.Left; + } + }); +} diff --git a/app/client/src/utils/migrations/autoHeightMigrations.ts b/app/client/src/utils/migrations/autoHeightMigrations.ts new file mode 100644 index 0000000000..758565cef9 --- /dev/null +++ b/app/client/src/utils/migrations/autoHeightMigrations.ts @@ -0,0 +1,51 @@ +import { + RegisteredWidgetFeatures, + WidgetFeatureProps, +} from "utils/WidgetFeatures"; +import { DSLWidget } from "widgets/constants"; +export const migratePropertiesForDynamicHeight = (currentDSL: DSLWidget) => { + /* const widgetsWithDynamicHeight = compact( + ALL_WIDGETS_AND_CONFIG.map(([, config]) => { + if (config.features?.dynamicHeight) return config.type; + }), + ); */ + // Ideally the above should be the code, however, + // there seems to be some cyclic imports which + // cause the test to fail in CI. + const widgetsWithDynamicHeight = [ + "CONTAINER_WIDGET", + "TEXT_WIDGET", + "CHECKBOX_WIDGET", + "RADIO_GROUP_WIDGET", + "TABS_WIDGET", + "MODAL_WIDGET", + "RICH_TEXT_EDITOR_WIDGET", + "DATE_PICKER_WIDGET2", + "SWITCH_WIDGET", + "FORM_WIDGET", + "RATE_WIDGET", + "CHECKBOX_GROUP_WIDGET", + "STATBOX_WIDGET", + "MULTI_SELECT_TREE_WIDGET", + "SINGLE_SELECT_TREE_WIDGET", + "SWITCH_GROUP_WIDGET", + "SELECT_WIDGET", + "MULTI_SELECT_WIDGET_V2", + "INPUT_WIDGET_V2", + "PHONE_INPUT_WIDGET", + "CURRENCY_INPUT_WIDGET", + "JSON_FORM_WIDGET", + ]; + if (widgetsWithDynamicHeight.includes(currentDSL.type)) { + currentDSL = { + ...currentDSL, + ...WidgetFeatureProps[RegisteredWidgetFeatures.DYNAMIC_HEIGHT], + }; + } + if (Array.isArray(currentDSL.children)) { + currentDSL.children = currentDSL.children.map( + migratePropertiesForDynamicHeight, + ); + } + return currentDSL; +}; diff --git a/app/client/src/utils/validation/common.ts b/app/client/src/utils/validation/common.ts index 66a4d7e0be..8f400a7999 100644 --- a/app/client/src/utils/validation/common.ts +++ b/app/client/src/utils/validation/common.ts @@ -7,7 +7,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import moment from "moment"; import { sample } from "lodash"; import { CodeEditorExpected } from "components/editorComponents/CodeEditor"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export const required = (value: any) => { if (value === undefined || value === null || value === "") { diff --git a/app/client/src/widgets/AudioRecorderWidget/index.ts b/app/client/src/widgets/AudioRecorderWidget/index.ts index 72364c3b84..226d49da33 100644 --- a/app/client/src/widgets/AudioRecorderWidget/index.ts +++ b/app/client/src/widgets/AudioRecorderWidget/index.ts @@ -24,6 +24,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx index 4cf53d3bd0..0b2803c420 100644 --- a/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx +++ b/app/client/src/widgets/AudioRecorderWidget/widget/index.tsx @@ -8,6 +8,7 @@ import AudioRecorderComponent from "../component"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { createBlobUrl } from "utils/AppsmithUtils"; import { FileDataTypes } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; export interface AudioRecorderWidgetProps extends WidgetProps { accentColor: string; @@ -148,6 +149,14 @@ class AudioRecorderWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getMetaPropertiesMap(): Record { return { blobURL: undefined, diff --git a/app/client/src/widgets/AudioWidget/widget/index.tsx b/app/client/src/widgets/AudioWidget/widget/index.tsx index 298f5ebc86..f643be6a45 100644 --- a/app/client/src/widgets/AudioWidget/widget/index.tsx +++ b/app/client/src/widgets/AudioWidget/widget/index.tsx @@ -6,7 +6,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import Skeleton from "components/utils/Skeleton"; import { retryPromise } from "utils/AppsmithUtils"; import ReactPlayer from "react-player"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; const AudioComponent = lazy(() => retryPromise(() => import("../component"))); diff --git a/app/client/src/widgets/BaseInputWidget/component/index.tsx b/app/client/src/widgets/BaseInputWidget/component/index.tsx index f8f996648a..39bf4fa996 100644 --- a/app/client/src/widgets/BaseInputWidget/component/index.tsx +++ b/app/client/src/widgets/BaseInputWidget/component/index.tsx @@ -24,16 +24,15 @@ import { InputTypes } from "../constants"; // TODO(abhinav): All of the following imports should not be in widgets. import ErrorTooltip from "components/editorComponents/ErrorTooltip"; -import { - Icon, - LabelWithTooltip, - labelLayoutStyles, - LABEL_CONTAINER_CLASS, -} from "design-system"; +import { Icon } from "design-system"; import { InputType } from "widgets/InputWidget/constants"; import { getBaseWidgetClassName } from "constants/componentClassNameConstants"; import { LabelPosition } from "components/constants"; import { lightenColor } from "widgets/WidgetUtils"; +import LabelWithTooltip, { + labelLayoutStyles, + LABEL_CONTAINER_CLASS, +} from "widgets/components/LabelWithTooltip"; import { getLocale } from "utils/helpers"; /** @@ -60,6 +59,7 @@ const InputComponentWrapper = styled((props) => ( "borderRadius", "boxShadow", "accentColor", + "isDynamicHeightEnabled", ])} /> ))<{ @@ -74,6 +74,7 @@ const InputComponentWrapper = styled((props) => ( borderRadius?: string; boxShadow?: string; accentColor?: string; + isDynamicHeightEnabled?: boolean; }>` ${labelLayoutStyles} @@ -121,6 +122,11 @@ const InputComponentWrapper = styled((props) => ( return "max-height: 20px; .bp3-popover-wrapper {max-height: 20px}"; } }}; + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled + ? "{ max-height: none; .bp3-popover-wrapper {max-height: none; } }" + : ""}; } .currency-type-filter, .country-type-filter { @@ -298,6 +304,9 @@ const InputComponentWrapper = styled((props) => ( return "flex-start"; }}; } + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled ? "&&&& { align-items: stretch; }" : ""}; `; const StyledNumericInput = styled(NumericInput)` @@ -333,6 +342,7 @@ const TextInputWrapper = styled.div<{ accentColor?: string; hasError?: boolean; disabled?: boolean; + isDynamicHeightEnabled?: boolean; }>` width: 100%; display: flex; @@ -383,6 +393,9 @@ const TextInputWrapper = styled.div<{ ${({ inputHtmlType }) => inputHtmlType && inputHtmlType !== InputTypes.TEXT && `&&& {flex-grow: 0;}`} + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled ? "&& { height: auto; }" : ""}; `; export type InputHTMLType = "TEXT" | "NUMBER" | "PASSWORD" | "EMAIL" | "TEL"; @@ -631,6 +644,7 @@ class BaseInputComponent extends React.Component< errorMessage, inputHTMLType, inputType, + isDynamicHeightEnabled, isInvalid, isLoading, label, @@ -654,6 +668,7 @@ class BaseInputComponent extends React.Component< fill hasError={isInvalid} inputType={inputType} + isDynamicHeightEnabled={isDynamicHeightEnabled} labelPosition={labelPosition} labelStyle={labelStyle} labelTextColor={labelTextColor} @@ -672,6 +687,7 @@ class BaseInputComponent extends React.Component< fontSize={labelTextSize} fontStyle={labelStyle} helpText={tooltip} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={isLoading} position={labelPosition} text={label} @@ -687,6 +703,7 @@ class BaseInputComponent extends React.Component< disabled={this.props.disabled} hasError={this.props.isInvalid} inputHtmlType={inputHTMLType} + isDynamicHeightEnabled={isDynamicHeightEnabled} labelPosition={labelPosition} > { + const paddedHeight = + Math.ceil( + Math.ceil(height + WIDGET_PADDING * 2) / + GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + ) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; + + const shouldUpdate = shouldUpdateWidgetHeightAutomatically( + paddedHeight, + this.props, + ); + const { updateWidgetAutoHeight } = this.context; + + if (updateWidgetAutoHeight) { + const { widgetId } = this.props; + shouldUpdate && updateWidgetAutoHeight(widgetId, paddedHeight); + } + }; + /* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-unused-vars */ componentDidUpdate(prevProps: T) {} @@ -316,14 +363,45 @@ abstract class BaseWidget< return {content}; } - addPreviewModeWidget(content: ReactNode): React.ReactElement { + addAutoHeightOverlay(content: ReactNode, style?: CSSProperties) { + const onBatchUpdate = (height: number, propertiesToUpdate?: string[]) => { + if (propertiesToUpdate === undefined) { + propertiesToUpdate = ["minDynamicHeight", "maxDynamicHeight"]; + } + const modifyObj: Record = {}; + propertiesToUpdate.forEach((propertyName) => { + modifyObj[propertyName] = Math.floor( + height / GridDefaults.DEFAULT_GRID_ROW_HEIGHT, + ); + }); + this.batchUpdateWidgetProperty({ + modify: modifyObj, + postUpdateAction: ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT, + }); + AnalyticsUtil.logEvent("AUTO_HEIGHT_OVERLAY_HANDLES_UPDATE", modifyObj); + }; + + const onMaxHeightSet = (height: number) => + onBatchUpdate(height, ["maxDynamicHeight"]); + + const onMinHeightSet = (height: number) => + onBatchUpdate(height, ["minDynamicHeight"]); + return ( - + <> + {content} - + ); } - getWidgetComponent = () => { const { renderMode, type } = this.props; @@ -333,16 +411,32 @@ abstract class BaseWidget< * values are not present (which will not be during mount), the widget type is changed to * SKELETON_WIDGET. * - * Note: This is done to retain the old rendering flow without any breaking changes. + * Note:- This is done to retain the old rendering flow without any breaking changes. * This could be refactored into not changing the widget type but to have a boolean flag. */ if (type === "SKELETON_WIDGET") { return ; } - return renderMode === RenderModes.CANVAS - ? this.getCanvasView() - : this.getPageView(); + const content = + renderMode === RenderModes.CANVAS + ? this.getCanvasView() + : this.getPageView(); + + if ( + isAutoHeightEnabledForWidget(this.props) && + !this.props.isAutoGeneratedWidget // To skip list widget's auto generated widgets + ) { + return ( + this.updateAutoHeight(height)} + widgetProps={this.props} + > + {content} + + ); + } + return this.addErrorBoundary(content); }; private getWidgetView(): ReactNode { @@ -350,7 +444,6 @@ abstract class BaseWidget< switch (this.props.renderMode) { case RenderModes.CANVAS: content = this.getWidgetComponent(); - content = this.addPreviewModeWidget(content); if (!this.props.detachFromLayout) { if (!this.props.resizeDisabled) content = this.makeResizable(content); content = this.showWidgetName(content); @@ -358,14 +451,17 @@ abstract class BaseWidget< content = this.makeSnipeable(content); // NOTE: In sniping mode we are not blocking onClick events from PositionWrapper. content = this.makePositioned(content); + if (isAutoHeightEnabledForWidget(this.props, true)) { + content = this.addAutoHeightOverlay(content); + } } + return content; // return this.getCanvasView(); case RenderModes.PAGE: content = this.getWidgetComponent(); if (this.props.isVisible) { - content = this.addErrorBoundary(content); if (!this.props.detachFromLayout) { content = this.makePositioned(content); } @@ -380,8 +476,7 @@ abstract class BaseWidget< abstract getPageView(): ReactNode; getCanvasView(): ReactNode { - const content = this.getPageView(); - return this.addErrorBoundary(content); + return this.getPageView(); } // TODO(abhinav): Maybe make this a pure component to bailout from updating altogether. @@ -447,6 +542,7 @@ export type WidgetRowCols = { topRow: number; bottomRow: number; minHeight?: number; // Required to reduce the size of CanvasWidgets. + height?: number; }; export interface WidgetPositionProps extends WidgetRowCols { @@ -477,6 +573,7 @@ export const WIDGET_STATIC_PROPS = { renderMode: true, detachFromLayout: true, noContainerOffset: false, + height: false, }; export const WIDGET_DISPLAY_PROPS = { diff --git a/app/client/src/widgets/ButtonGroupWidget/index.ts b/app/client/src/widgets/ButtonGroupWidget/index.ts index b5cbd2f1a5..e47130134a 100644 --- a/app/client/src/widgets/ButtonGroupWidget/index.ts +++ b/app/client/src/widgets/ButtonGroupWidget/index.ts @@ -145,6 +145,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts b/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts index 4b36182473..f4fd81dec4 100644 --- a/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts +++ b/app/client/src/widgets/ButtonGroupWidget/widget/helpers.ts @@ -1,7 +1,8 @@ -import { ButtonGroupWidgetProps } from "."; -import { AppTheme } from "entities/AppTheming"; import { get } from "lodash"; +import { ButtonGroupWidgetProps } from "."; +import { Stylesheet } from "entities/AppTheming"; + /** * this is a getter function to get stylesheet value of the property from the config * @@ -13,7 +14,7 @@ import { get } from "lodash"; export const getStylesheetValue = ( props: ButtonGroupWidgetProps, propertyPath: string, - widgetStylesheet?: AppTheme["stylesheet"][string], + widgetStylesheet?: Stylesheet, ) => { const propertyName = propertyPath.split(".").slice(-1)[0]; diff --git a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx index 7f723f59f6..b9e05913a3 100644 --- a/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/ButtonGroupWidget/widget/index.tsx @@ -14,6 +14,7 @@ import { import ButtonGroupComponent from "../component"; import { MinimumPopupRows } from "widgets/constants"; import { getStylesheetValue } from "./helpers"; +import { Stylesheet } from "entities/AppTheming"; class ButtonGroupWidget extends BaseWidget< ButtonGroupWidgetProps, @@ -538,6 +539,18 @@ class ButtonGroupWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + childStylesheet: { + button: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + }, + }, + }; + } + handleClick = (onClick: string | undefined, callback: () => void): void => { if (onClick) { super.executeAction({ diff --git a/app/client/src/widgets/ButtonWidget/index.ts b/app/client/src/widgets/ButtonWidget/index.ts index a5115c9ca4..1f79257425 100644 --- a/app/client/src/widgets/ButtonWidget/index.ts +++ b/app/client/src/widgets/ButtonWidget/index.ts @@ -35,6 +35,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ButtonWidget/widget/index.tsx b/app/client/src/widgets/ButtonWidget/widget/index.tsx index 5877bf9b71..80a87f8ccf 100644 --- a/app/client/src/widgets/ButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/ButtonWidget/widget/index.tsx @@ -18,6 +18,7 @@ import { ButtonPlacementTypes, ButtonPlacement, } from "components/constants"; +import { Stylesheet } from "entities/AppTheming"; class ButtonWidget extends BaseWidget { onButtonClickBound: (event: React.MouseEvent) => void; @@ -356,6 +357,14 @@ class ButtonWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getMetaPropertiesMap(): Record { return { recaptchaToken: undefined, diff --git a/app/client/src/widgets/CameraWidget/component/index.tsx b/app/client/src/widgets/CameraWidget/component/index.tsx index 00b4484f6c..70727d99a0 100644 --- a/app/client/src/widgets/CameraWidget/component/index.tsx +++ b/app/client/src/widgets/CameraWidget/component/index.tsx @@ -26,7 +26,7 @@ import { } from "components/constants"; import { SupportedLayouts } from "reducers/entityReducers/pageListReducer"; import { getCurrentApplicationLayout } from "selectors/editorSelectors"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { Colors } from "constants/Colors"; import { getBrowserInfo, diff --git a/app/client/src/widgets/CameraWidget/index.ts b/app/client/src/widgets/CameraWidget/index.ts index 087835cb8f..db2da2a0a4 100644 --- a/app/client/src/widgets/CameraWidget/index.ts +++ b/app/client/src/widgets/CameraWidget/index.ts @@ -26,6 +26,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CameraWidget/widget/index.tsx b/app/client/src/widgets/CameraWidget/widget/index.tsx index 0895bbae48..03b3a85e9f 100644 --- a/app/client/src/widgets/CameraWidget/widget/index.tsx +++ b/app/client/src/widgets/CameraWidget/widget/index.tsx @@ -14,6 +14,7 @@ import { CameraModeTypes, MediaCaptureStatusTypes, } from "../constants"; +import { Stylesheet } from "entities/AppTheming"; class CameraWidget extends BaseWidget { static getPropertyPaneContentConfig() { @@ -198,6 +199,13 @@ class CameraWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getWidgetType(): string { return "CAMERA_WIDGET"; } diff --git a/app/client/src/widgets/CanvasWidget.tsx b/app/client/src/widgets/CanvasWidget.tsx index 5eed86d8ec..3d4efcb629 100644 --- a/app/client/src/widgets/CanvasWidget.tsx +++ b/app/client/src/widgets/CanvasWidget.tsx @@ -1,13 +1,11 @@ import React, { CSSProperties } from "react"; -import { WidgetProps } from "widgets/BaseWidget"; -import ContainerWidget, { - ContainerWidgetProps, -} from "widgets/ContainerWidget/widget"; +import ContainerWidget from "widgets/ContainerWidget/widget"; import { GridDefaults } from "constants/WidgetConstants"; import DropTargetComponent from "components/editorComponents/DropTargetComponent"; import { getCanvasSnapRows } from "utils/WidgetPropsUtils"; import { getCanvasClassName } from "utils/generators"; import WidgetFactory, { DerivedPropertiesMap } from "utils/WidgetFactory"; +import { DSLWidget } from "./constants"; import { CanvasWidgetStructure } from "./constants"; import { CANVAS_DEFAULT_MIN_HEIGHT_PX } from "constants/AppConstants"; @@ -19,7 +17,7 @@ class CanvasWidget extends ContainerWidget { return "CANVAS_WIDGET"; } - getCanvasProps(): ContainerWidgetProps { + getCanvasProps(): DSLWidget & { minHeight: number } { return { ...this.props, parentRowSpace: 1, @@ -28,17 +26,14 @@ class CanvasWidget extends ContainerWidget { leftColumn: 0, containerStyle: "none", detachFromLayout: true, + minHeight: this.props.minHeight || CANVAS_DEFAULT_MIN_HEIGHT_PX, }; } renderAsDropTarget() { const canvasProps = this.getCanvasProps(); return ( - + {this.renderAsContainerComponent(canvasProps)} ); @@ -61,12 +56,8 @@ class CanvasWidget extends ContainerWidget { getPageView() { let height = 0; - const snapRows = getCanvasSnapRows( - this.props.bottomRow, - this.props.canExtend, - ); + const snapRows = getCanvasSnapRows(this.props.bottomRow, false); height = snapRows * GridDefaults.DEFAULT_GRID_ROW_HEIGHT; - const style: CSSProperties = { width: "100%", height: `${height}px`, diff --git a/app/client/src/widgets/CategorySliderWidget/index.ts b/app/client/src/widgets/CategorySliderWidget/index.ts index 8cc2386e7c..794fd10a5c 100644 --- a/app/client/src/widgets/CategorySliderWidget/index.ts +++ b/app/client/src/widgets/CategorySliderWidget/index.ts @@ -21,9 +21,9 @@ export const CONFIG = { defaultOptionValue: "md", isVisible: true, isDisabled: false, - showMarksLabel: false, + showMarksLabel: true, rows: 8, - columns: 42, + columns: 40, widgetName: "CategorySlider", shouldScroll: false, shouldTruncate: false, @@ -42,6 +42,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CategorySliderWidget/widget/index.tsx b/app/client/src/widgets/CategorySliderWidget/widget/index.tsx index f85a98673a..cf113f0d5d 100644 --- a/app/client/src/widgets/CategorySliderWidget/widget/index.tsx +++ b/app/client/src/widgets/CategorySliderWidget/widget/index.tsx @@ -8,6 +8,7 @@ import styleConfig from "./propertyConfig/styleConfig"; import SliderComponent, { SliderComponentProps, } from "../../NumberSliderWidget/component/Slider"; +import { Stylesheet } from "entities/AppTheming"; export type SliderOption = { label: string; @@ -62,7 +63,7 @@ class CategorySliderWidget extends BaseWidget< } } - static getDefaultPropertiesMap(): Record { + static getDefaultPropertiesMap(): Record { return { value: "defaultOptionValue", }; @@ -75,6 +76,12 @@ class CategorySliderWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + }; + } + getSliderOptions = () => { const options = this.props.options || []; /** get the stepSize - if we have 4 options stepSize is 25 */ @@ -140,6 +147,7 @@ class CategorySliderWidget extends BaseWidget< labelText={this.props.labelText} labelTextColor={this.props.labelTextColor} labelTextSize={this.props.labelTextSize} + labelTooltip={this.props.labelTooltip} labelWidth={this.getLabelWidth()} loading={this.props.isLoading} marks={sliderOptions} diff --git a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts index c304d84a03..da6f3f0ba8 100644 --- a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/contentConfig.ts @@ -1,5 +1,5 @@ import { LabelPosition } from "components/constants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { Alignment } from "@blueprintjs/core"; import { ValidationTypes } from "constants/WidgetValidation"; @@ -84,6 +84,7 @@ export default [ { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Left, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -134,6 +135,16 @@ export default [ { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { propertyName: "showMarksLabel", helpText: "Controls the visibility of the marks Label widget", diff --git a/app/client/src/widgets/ChartWidget/constants.ts b/app/client/src/widgets/ChartWidget/constants.ts index 46858010d5..5465a2a770 100644 --- a/app/client/src/widgets/ChartWidget/constants.ts +++ b/app/client/src/widgets/ChartWidget/constants.ts @@ -67,7 +67,7 @@ export const CUSTOM_CHART_TYPES = [ "bulb", "realtimecolumn", "cylinder", - "HLinearGauge", + "hlineargauge", "realtimeline", "realtimelinedy", "realtimestackedarea", diff --git a/app/client/src/widgets/ChartWidget/index.ts b/app/client/src/widgets/ChartWidget/index.ts index 071f524bf3..0aa3ba7f4f 100644 --- a/app/client/src/widgets/ChartWidget/index.ts +++ b/app/client/src/widgets/ChartWidget/index.ts @@ -102,6 +102,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ChartWidget/widget/index.tsx b/app/client/src/widgets/ChartWidget/widget/index.tsx index 7f59dcf25d..f26a5e1847 100644 --- a/app/client/src/widgets/ChartWidget/widget/index.tsx +++ b/app/client/src/widgets/ChartWidget/widget/index.tsx @@ -15,6 +15,7 @@ import { import { WidgetType } from "constants/WidgetConstants"; import { ChartComponentProps } from "../component"; import { Colors } from "constants/Colors"; +import { Stylesheet } from "entities/AppTheming"; const ChartComponent = lazy(() => retryPromise(() => @@ -39,6 +40,15 @@ class ChartWidget extends BaseWidget { return styleConfig; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + accentColor: "{{appsmith.theme.colors.primaryColor}}", + fontFamily: "{{appsmith.theme.fontFamily.appFont}}", + }; + } + onDataPointClick = (selectedDataPoint: ChartSelectedDataPoint) => { this.props.updateWidgetMetaProperty( "selectedDataPoint", diff --git a/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx index e0c35f78ff..43979bdf54 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx @@ -16,11 +16,10 @@ import { StyledCheckbox, } from "widgets/CheckboxWidget/component"; import { OptionProps, SelectAllState, SelectAllStates } from "../constants"; -import { - LabelWithTooltip, +import LabelWithTooltip, { labelLayoutStyles, LABEL_CONTAINER_CLASS, -} from "design-system"; +} from "widgets/components/LabelWithTooltip"; import { ThemeProp, AlignWidgetTypes } from "widgets/constants"; export interface InputContainerProps { @@ -28,6 +27,7 @@ export interface InputContainerProps { optionCount: number; valid?: boolean; optionAlignment?: string; + isDynamicHeightEnabled?: boolean; } const InputContainer = styled.div` @@ -46,9 +46,9 @@ const InputContainer = styled.div` ? `flex-start` : `center`}; width: 100%; - height: ${({ inline }) => (inline ? "32px" : "100%")}; flex-grow: 1; height: 100%; + border: 1px solid transparent; .${Classes.CONTROL} { @@ -71,6 +71,7 @@ export interface CheckboxGroupContainerProps { export const CheckboxGroupContainer = styled.div` ${labelLayoutStyles} & .${LABEL_CONTAINER_CLASS} { + align-self: center; ${({ labelPosition }) => labelPosition === LabelPosition.Left && "min-height: 30px"}; } @@ -127,6 +128,7 @@ function SelectAll(props: SelectAllProps) { export interface CheckboxGroupComponentProps extends ComponentProps { isDisabled: boolean; + isDynamicHeightEnabled?: boolean; isInline: boolean; isSelectAll?: boolean; isRequired?: boolean; @@ -147,6 +149,7 @@ export interface CheckboxGroupComponentProps extends ComponentProps { labelTextSize?: TextSize; labelStyle?: string; labelWidth?: number; + labelTooltip?: string; accentColor: string; borderRadius: string; } @@ -156,6 +159,7 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { borderRadius, compactMode, isDisabled, + isDynamicHeightEnabled, isInline, isSelectAll, isValid, @@ -165,6 +169,7 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { labelText, labelTextColor, labelTextSize, + labelTooltip, labelWidth, onChange, onSelectAllChange, @@ -202,7 +207,9 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { disabled={isDisabled} fontSize={labelTextSize} fontStyle={labelStyle} + helpText={labelTooltip} inline={isInline} + isDynamicHeightEnabled={isDynamicHeightEnabled} optionCount={optionCount} position={labelPosition} text={labelText} @@ -212,6 +219,7 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { diff --git a/app/client/src/widgets/CheckboxGroupWidget/index.ts b/app/client/src/widgets/CheckboxGroupWidget/index.ts index 76196520e3..b3ed085223 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/index.ts +++ b/app/client/src/widgets/CheckboxGroupWidget/index.ts @@ -4,6 +4,12 @@ import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Checkbox Group", iconSVG: IconSVG, @@ -18,7 +24,7 @@ export const CONFIG = { { label: "Green", value: "GREEN" }, { label: "Red", value: "RED" }, ], - defaultSelectedValues: "BLUE", + defaultSelectedValues: ["BLUE"], isDisabled: false, isInline: true, isRequired: false, @@ -37,6 +43,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx index bd3f842483..d68c722200 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/widget/index.tsx @@ -1,26 +1,25 @@ import React from "react"; import { compact, xor } from "lodash"; - -import { - ValidationResponse, - ValidationTypes, -} from "constants/WidgetValidation"; import { TextSize, WidgetType } from "constants/WidgetConstants"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; - +import { Alignment } from "@blueprintjs/core"; +import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import CheckboxGroupComponent from "../component"; +import { OptionProps, SelectAllState, SelectAllStates } from "../constants"; +import { Stylesheet } from "entities/AppTheming"; +import { + ValidationResponse, + ValidationTypes, +} from "constants/WidgetValidation"; import { CheckboxGroupAlignmentTypes, LabelPosition, } from "components/constants"; -import { Alignment } from "@blueprintjs/core"; -import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; - -import CheckboxGroupComponent from "../component"; -import { OptionProps, SelectAllState, SelectAllStates } from "../constants"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; export function defaultSelectedValuesValidation( value: unknown, @@ -147,6 +146,7 @@ class CheckboxGroupWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -215,6 +215,16 @@ class CheckboxGroupWidget extends BaseWidget< { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { propertyName: "isVisible", label: "Visible", @@ -481,6 +491,13 @@ class CheckboxGroupWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }; + } + componentDidUpdate(prevProps: CheckboxGroupWidgetProps) { if ( Array.isArray(prevProps.options) && @@ -497,13 +514,24 @@ class CheckboxGroupWidget extends BaseWidget< .filter((option) => !options.includes(option)) .concat(options.filter((option) => !prevOptions.includes(option))); - let selectedValues = this.props.selectedValues.filter( + // TODO(abhinav): Not sure why we have to do this. + // Stuff breaks after release merge, fixing it here. + let _selectedValues = this.props.selectedValues; + if (!Array.isArray(_selectedValues)) { + if ( + this.props.defaultSelectedValues && + this.props.defaultSelectedValues.length && + !Array.isArray(this.props.defaultSelectedValues) + ) { + _selectedValues = [this.props.defaultSelectedValues]; + } else { + _selectedValues = []; + } + } + + const selectedValues = _selectedValues.filter( (selectedValue: string) => !diffOptions.includes(selectedValue), ); - // if selectedValues empty, and options have changed, set defaultSelectedValues - if (!selectedValues.length && this.props.defaultSelectedValues.length) { - selectedValues = this.props.defaultSelectedValues; - } this.props.updateWidgetMetaProperty("selectedValues", selectedValues, { triggerPropertyName: "onSelectionChange", @@ -536,6 +564,7 @@ class CheckboxGroupWidget extends BaseWidget< ) } isDisabled={this.props.isDisabled} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isInline={this.props.isInline} isRequired={this.props.isRequired} isSelectAll={this.props.isSelectAll} @@ -547,6 +576,7 @@ class CheckboxGroupWidget extends BaseWidget< labelText={this.props.labelText} labelTextColor={this.props.labelTextColor} labelTextSize={this.props.labelTextSize} + labelTooltip={this.props.labelTooltip} labelWidth={this.getLabelWidth()} onChange={this.handleCheckboxChange} onSelectAllChange={this.handleSelectAllChange} diff --git a/app/client/src/widgets/CheckboxWidget/component/index.tsx b/app/client/src/widgets/CheckboxWidget/component/index.tsx index 3ada125047..aeb23e5b0f 100644 --- a/app/client/src/widgets/CheckboxWidget/component/index.tsx +++ b/app/client/src/widgets/CheckboxWidget/component/index.tsx @@ -37,6 +37,7 @@ export const CheckboxLabel = styled.div<{ labelTextColor?: string; labelTextSize?: string; labelStyle?: string; + isDynamicHeightEnabled?: boolean; }>` width: 100%; display: inline-block; @@ -52,6 +53,9 @@ export const CheckboxLabel = styled.div<{ labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : "normal" }; `} + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled ? "&& { word-break: break-all; }" : ""}; `; export const StyledCheckbox = styled(Checkbox)` @@ -100,6 +104,7 @@ class CheckboxComponent extends React.Component { alignment={this.props.alignWidget || AlignWidgetTypes.LEFT} className="t--checkbox-widget-label" disabled={this.props.isDisabled} + isDynamicHeightEnabled={this.props.isDynamicHeightEnabled} labelStyle={this.props.labelStyle} labelTextColor={this.props.labelTextColor} labelTextSize={this.props.labelTextSize} @@ -130,6 +135,7 @@ export interface CheckboxComponentProps extends ComponentProps { inputRef?: (el: HTMLInputElement | null) => any; accentColor: string; borderRadius: string; + isDynamicHeightEnabled?: boolean; labelPosition: LabelPosition; labelTextColor?: string; labelTextSize?: string; diff --git a/app/client/src/widgets/CheckboxWidget/index.ts b/app/client/src/widgets/CheckboxWidget/index.ts index fc539cc611..a9fa95a491 100644 --- a/app/client/src/widgets/CheckboxWidget/index.ts +++ b/app/client/src/widgets/CheckboxWidget/index.ts @@ -4,6 +4,12 @@ import { LabelPosition } from "components/constants"; import { AlignWidgetTypes } from "widgets/constants"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 2, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Checkbox", iconSVG: IconSVG, @@ -29,6 +35,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CheckboxWidget/widget/index.tsx b/app/client/src/widgets/CheckboxWidget/widget/index.tsx index ca4a80a101..923b809835 100644 --- a/app/client/src/widgets/CheckboxWidget/widget/index.tsx +++ b/app/client/src/widgets/CheckboxWidget/widget/index.tsx @@ -7,6 +7,8 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { LabelPosition } from "components/constants"; import { AlignWidgetTypes } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; class CheckboxWidget extends BaseWidget { static getPropertyPaneContentConfig() { @@ -34,6 +36,7 @@ class CheckboxWidget extends BaseWidget { { label: "Left", value: LabelPosition.Left }, { label: "Right", value: LabelPosition.Right }, ], + defaultValue: LabelPosition.Left, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -276,6 +279,13 @@ class CheckboxWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }; + } + componentDidUpdate(prevProps: CheckboxWidgetProps) { if ( this.props.defaultCheckedState !== prevProps.defaultCheckedState && @@ -293,6 +303,7 @@ class CheckboxWidget extends BaseWidget { borderRadius={this.props.borderRadius} isChecked={!!this.props.isChecked} isDisabled={this.props.isDisabled} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isLoading={this.props.isLoading} isRequired={this.props.isRequired} key={this.props.widgetId} diff --git a/app/client/src/widgets/CircularProgressWidget/index.ts b/app/client/src/widgets/CircularProgressWidget/index.ts index 6fe791811a..0e285a6571 100644 --- a/app/client/src/widgets/CircularProgressWidget/index.ts +++ b/app/client/src/widgets/CircularProgressWidget/index.ts @@ -29,6 +29,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CircularProgressWidget/widget/index.tsx b/app/client/src/widgets/CircularProgressWidget/widget/index.tsx index 587f412bb8..a9bf0fa8f7 100644 --- a/app/client/src/widgets/CircularProgressWidget/widget/index.tsx +++ b/app/client/src/widgets/CircularProgressWidget/widget/index.tsx @@ -2,10 +2,11 @@ import * as React from "react"; import { ValidationTypes } from "constants/WidgetValidation"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import CircularProgressComponent, { CircularProgressComponentProps, } from "../component"; +import { Stylesheet } from "entities/AppTheming"; interface CircularProgressWidgetProps extends WidgetProps, @@ -90,6 +91,13 @@ class CircularProgressWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + fillColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }; + } + getPageView() { return ( { this.props.updateWidgetMetaProperty("value", value, { triggerPropertyName: "onCodeDetected", diff --git a/app/client/src/widgets/ContainerWidget/component/index.tsx b/app/client/src/widgets/ContainerWidget/component/index.tsx index 7ec7eabff2..aa087cdfb8 100644 --- a/app/client/src/widgets/ContainerWidget/component/index.tsx +++ b/app/client/src/widgets/ContainerWidget/component/index.tsx @@ -4,7 +4,6 @@ import tinycolor from "tinycolor2"; import { invisible } from "constants/DefaultTheme"; import { Color } from "constants/Colors"; import { generateClassName, getCanvasClassName } from "utils/generators"; -import { useCanvasMinHeightUpdateHook } from "utils/hooks/useCanvasMinHeightUpdateHook"; import WidgetStyleContainer, { WidgetStyleContainerProps, } from "components/designSystems/appsmith/WidgetStyleContainer"; @@ -86,7 +85,6 @@ function ContainerComponentWrapper(props: ContainerComponentProps) { } function ContainerComponent(props: ContainerComponentProps) { - useCanvasMinHeightUpdateHook(props.widgetId, props.minHeight); return props.widgetId === MAIN_CONTAINER_WIDGET_ID ? ( ) : ( diff --git a/app/client/src/widgets/ContainerWidget/index.ts b/app/client/src/widgets/ContainerWidget/index.ts index ee9bd3f7fb..a90ebcda78 100644 --- a/app/client/src/widgets/ContainerWidget/index.ts +++ b/app/client/src/widgets/ContainerWidget/index.ts @@ -1,5 +1,6 @@ import { ButtonBoxShadowTypes } from "components/constants"; import { Colors } from "constants/Colors"; +import { WidgetHeightLimits } from "constants/WidgetConstants"; import IconSVG from "./icon.svg"; import Widget from "./widget"; @@ -8,10 +9,16 @@ export const CONFIG = { name: "Container", iconSVG: IconSVG, isCanvas: true, + features: { + dynamicHeight: { + sectionIndex: 0, + active: true, + }, + }, searchTags: ["div", "parent", "group"], defaults: { backgroundColor: "#FFFFFF", - rows: 40, + rows: WidgetHeightLimits.MIN_CANVAS_HEIGHT_IN_ROWS, columns: 24, widgetName: "Container", containerStyle: "card", @@ -43,6 +50,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ContainerWidget/widget/index.tsx b/app/client/src/widgets/ContainerWidget/widget/index.tsx index a526c66665..dc1a71fb75 100644 --- a/app/client/src/widgets/ContainerWidget/widget/index.tsx +++ b/app/client/src/widgets/ContainerWidget/widget/index.tsx @@ -20,6 +20,7 @@ import WidgetsMultiSelectBox from "pages/Editor/WidgetsMultiSelectBox"; import { CanvasDraggingArena } from "pages/common/CanvasArenas/CanvasDraggingArena"; import { getCanvasSnapRows } from "utils/WidgetPropsUtils"; +import { Stylesheet } from "entities/AppTheming"; class ContainerWidget extends BaseWidget< ContainerWidgetProps, @@ -147,6 +148,13 @@ class ContainerWidget extends BaseWidget< return {}; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + getSnapSpaces = () => { const { componentWidth } = this.getComponentDimensions(); // For all widgets inside a container, we remove both container padding as well as widget padding from component width @@ -224,14 +232,16 @@ class ContainerWidget extends BaseWidget< snapRows={snapRows} widgetId={props.widgetId} /> + + )} - + {/* without the wrapping div onClick events are triggered twice */} <>{this.renderChildren()} diff --git a/app/client/src/widgets/CurrencyInputWidget/component/index.tsx b/app/client/src/widgets/CurrencyInputWidget/component/index.tsx index 85caa29b05..5e7dd3ec64 100644 --- a/app/client/src/widgets/CurrencyInputWidget/component/index.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/component/index.tsx @@ -54,6 +54,7 @@ class CurrencyInputComponent extends React.Component< inputHTMLType="NUMBER" inputType={InputTypes.CURRENCY} intent={this.props.intent} + isDynamicHeightEnabled={this.props.isDynamicHeightEnabled} isInvalid={this.props.isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/CurrencyInputWidget/index.ts b/app/client/src/widgets/CurrencyInputWidget/index.ts index c2c73f8007..7f1d33fc66 100644 --- a/app/client/src/widgets/CurrencyInputWidget/index.ts +++ b/app/client/src/widgets/CurrencyInputWidget/index.ts @@ -3,8 +3,16 @@ import IconSVG from "./icon.svg"; import { CONFIG as BaseConfig } from "widgets/BaseInputWidget"; import { getDefaultCurrency } from "./component/CurrencyCodeDropdown"; import { LabelPosition } from "components/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Currency Input", iconSVG: IconSVG, @@ -27,6 +35,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx index 9f1ebd4b74..5400c51f6f 100644 --- a/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/widget/index.tsx @@ -18,7 +18,7 @@ import { CurrencyDropdownOptions, getCountryCodeFromCurrencyCode, } from "../component/CurrencyCodeDropdown"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import _ from "lodash"; import derivedProperties from "./parsedDerivedProperties"; import BaseInputWidget from "widgets/BaseInputWidget"; @@ -34,7 +34,9 @@ import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { getLocaleDecimalSeperator, getLocaleThousandSeparator, + isAutoHeightEnabledForWidget, } from "widgets/WidgetUtils"; +import { Stylesheet } from "entities/AppTheming"; export function defaultValueValidation( value: any, @@ -255,6 +257,14 @@ class CurrencyInputWidget extends BaseInputWidget< }); } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + componentDidMount() { //format the defaultText and store it in text this.formatText(); @@ -428,6 +438,7 @@ class CurrencyInputWidget extends BaseInputWidget< iconAlign={this.props.iconAlign} iconName={this.props.iconName} inputType={this.props.inputType} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isInvalid={isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/DatePickerWidget/widget/index.tsx b/app/client/src/widgets/DatePickerWidget/widget/index.tsx index e04c6a50b9..2353bf761e 100644 --- a/app/client/src/widgets/DatePickerWidget/widget/index.tsx +++ b/app/client/src/widgets/DatePickerWidget/widget/index.tsx @@ -11,7 +11,7 @@ import { import { DerivedPropertiesMap } from "utils/WidgetFactory"; import moment from "moment"; import { DatePickerType } from "../constants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; function defaultDateValidation( value: unknown, diff --git a/app/client/src/widgets/DatePickerWidget2/component/index.tsx b/app/client/src/widgets/DatePickerWidget2/component/index.tsx index 3a09a28bbe..3e2d8c192f 100644 --- a/app/client/src/widgets/DatePickerWidget2/component/index.tsx +++ b/app/client/src/widgets/DatePickerWidget2/component/index.tsx @@ -17,8 +17,10 @@ import { } from "@appsmith/constants/messages"; import { LabelPosition } from "components/constants"; import { parseDate } from "./utils"; -import { LabelWithTooltip, labelLayoutStyles } from "design-system"; import { lightenColor, PopoverStyles } from "widgets/WidgetUtils"; +import LabelWithTooltip, { + labelLayoutStyles, +} from "widgets/components/LabelWithTooltip"; const DATEPICKER_POPUP_CLASSNAME = "datepickerwidget-popup"; @@ -298,6 +300,7 @@ class DatePickerComponent extends React.Component< disabled={isDisabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={this.props.isDynamicHeightEnabled} loading={isLoading} position={labelPosition} text={labelText} @@ -437,6 +440,7 @@ interface DatePickerComponentProps extends ComponentProps { timezone?: string; datePickerType: DatePickerType; isDisabled: boolean; + isDynamicHeightEnabled?: boolean; onDateSelected: (selectedDate: string) => void; isLoading: boolean; withoutPortal?: boolean; diff --git a/app/client/src/widgets/DatePickerWidget2/index.ts b/app/client/src/widgets/DatePickerWidget2/index.ts index 2372f99290..3b870fac3f 100644 --- a/app/client/src/widgets/DatePickerWidget2/index.ts +++ b/app/client/src/widgets/DatePickerWidget2/index.ts @@ -1,11 +1,19 @@ import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; import moment from "moment"; +import { DynamicHeight } from "utils/WidgetFeatures"; import { TimePrecision } from "./constants"; import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "DatePicker", iconSVG: IconSVG, @@ -41,6 +49,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/DatePickerWidget2/widget/index.tsx b/app/client/src/widgets/DatePickerWidget2/widget/index.tsx index c2ecffddb2..14cf6a0ff1 100644 --- a/app/client/src/widgets/DatePickerWidget2/widget/index.tsx +++ b/app/client/src/widgets/DatePickerWidget2/widget/index.tsx @@ -6,7 +6,7 @@ import DatePickerComponent from "../component"; import { ValidationTypes } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import derivedProperties from "./parseDerivedProperties"; import { DatePickerType, TimePrecision } from "../constants"; @@ -14,6 +14,8 @@ import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { DateFormatOptions } from "./constants"; +import { Stylesheet } from "entities/AppTheming"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; function allowedRange(value: any) { const allowedValues = [0, 1, 2, 3, 4, 5, 6]; @@ -139,6 +141,7 @@ class DatePickerWidget extends BaseWidget { { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -440,6 +443,14 @@ class DatePickerWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + componentDidUpdate(prevProps: DatePickerWidget2Props): void { if ( this.props.defaultDate !== prevProps.defaultDate && @@ -468,6 +479,7 @@ class DatePickerWidget extends BaseWidget { datePickerType={"DATE_PICKER"} firstDayOfWeek={this.props.firstDayOfWeek} isDisabled={this.props.isDisabled} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isLoading={this.props.isLoading} labelAlignment={this.props.labelAlignment} labelPosition={this.props.labelPosition} diff --git a/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx b/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx index a1a7b85d65..00ce965f37 100644 --- a/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx +++ b/app/client/src/widgets/DocumentViewerWidget/widget/index.tsx @@ -5,7 +5,7 @@ import { ValidationTypes, ValidationResponse, } from "constants/WidgetValidation"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export function documentUrlValidation(value: unknown): ValidationResponse { // applied validations if value exist diff --git a/app/client/src/widgets/DropdownWidget/index.ts b/app/client/src/widgets/DropdownWidget/index.ts index 1fab0ee131..3ae5eeb312 100644 --- a/app/client/src/widgets/DropdownWidget/index.ts +++ b/app/client/src/widgets/DropdownWidget/index.ts @@ -39,6 +39,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/DropdownWidget/widget/index.tsx b/app/client/src/widgets/DropdownWidget/widget/index.tsx index 02d27d2df1..a1f38d62a3 100644 --- a/app/client/src/widgets/DropdownWidget/widget/index.tsx +++ b/app/client/src/widgets/DropdownWidget/widget/index.tsx @@ -10,10 +10,11 @@ import { ValidationTypes, } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; +import { Stylesheet } from "entities/AppTheming"; function defaultOptionValueValidation(value: unknown): ValidationResponse { if (typeof value === "string") return { isValid: true, parsed: value.trim() }; @@ -190,6 +191,7 @@ class DropdownWidget extends BaseWidget { { label: "Top", value: LabelPosition.Top }, { label: "Auto", value: LabelPosition.Auto }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -363,6 +365,14 @@ class DropdownWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + componentDidMount() { this.changeSelectedOption(); } diff --git a/app/client/src/widgets/FilePickerWidgetV2/index.ts b/app/client/src/widgets/FilePickerWidgetV2/index.ts index 5f8c2e1faa..ecf0de35a4 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/index.ts +++ b/app/client/src/widgets/FilePickerWidgetV2/index.ts @@ -33,6 +33,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx index 89d544304e..7d22ac74d5 100644 --- a/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/FilePickerWidgetV2/widget/index.tsx @@ -24,6 +24,7 @@ import { Colors } from "constants/Colors"; import Papa from "papaparse"; import { klona } from "klona"; import { UppyFile } from "@uppy/utils"; +import { Stylesheet } from "entities/AppTheming"; const CSV_ARRAY_LABEL = "Array (CSVs only)"; const CSV_FILE_TYPE_REGEX = /.+(\/csv)$/; @@ -508,6 +509,14 @@ class FilePickerWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + /** * if uppy is not initialized before, initialize it * else setState of uppy instance diff --git a/app/client/src/widgets/FormButtonWidget/index.ts b/app/client/src/widgets/FormButtonWidget/index.ts index 85d3ad8947..b3bc99b285 100644 --- a/app/client/src/widgets/FormButtonWidget/index.ts +++ b/app/client/src/widgets/FormButtonWidget/index.ts @@ -25,6 +25,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/FormButtonWidget/widget/index.tsx b/app/client/src/widgets/FormButtonWidget/widget/index.tsx index 02725ac8d5..3834ab1e3c 100644 --- a/app/client/src/widgets/FormButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/FormButtonWidget/widget/index.tsx @@ -19,6 +19,7 @@ import { import { IconName } from "@blueprintjs/icons"; import { Alignment } from "@blueprintjs/core"; import { ButtonWidgetProps } from "widgets/ButtonWidget/widget"; +import { Stylesheet } from "entities/AppTheming"; class FormButtonWidget extends ButtonWidget { constructor(props: FormButtonWidgetProps) { @@ -310,6 +311,14 @@ class FormButtonWidget extends ButtonWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + clickWithRecaptcha(token: string) { if (this.props.onClick) { this.setState({ diff --git a/app/client/src/widgets/FormWidget/index.ts b/app/client/src/widgets/FormWidget/index.ts index 70a0afa945..25912f432e 100644 --- a/app/client/src/widgets/FormWidget/index.ts +++ b/app/client/src/widgets/FormWidget/index.ts @@ -9,6 +9,12 @@ export const CONFIG = { iconSVG: IconSVG, needsMeta: true, isCanvas: true, + features: { + dynamicHeight: { + sectionIndex: 0, + active: true, + }, + }, searchTags: ["group"], defaults: { rows: 40, @@ -97,6 +103,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/FormWidget/widget/index.tsx b/app/client/src/widgets/FormWidget/widget/index.tsx index a8388be636..d9bb393f18 100644 --- a/app/client/src/widgets/FormWidget/widget/index.tsx +++ b/app/client/src/widgets/FormWidget/widget/index.tsx @@ -113,6 +113,13 @@ class FormWidget extends ContainerWidget { static getWidgetType(): WidgetType { return "FORM_WIDGET"; } + + static getStylsheetConfig() { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } } export interface FormWidgetProps extends ContainerComponentProps { diff --git a/app/client/src/widgets/IconButtonWidget/index.ts b/app/client/src/widgets/IconButtonWidget/index.ts index cbb9395e7b..c89c7a8fb3 100644 --- a/app/client/src/widgets/IconButtonWidget/index.ts +++ b/app/client/src/widgets/IconButtonWidget/index.ts @@ -26,6 +26,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/IconButtonWidget/widget/index.tsx b/app/client/src/widgets/IconButtonWidget/widget/index.tsx index f18a86e85d..e60ad7a21f 100644 --- a/app/client/src/widgets/IconButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/IconButtonWidget/widget/index.tsx @@ -9,6 +9,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import IconButtonComponent from "../component"; import { IconNames } from "@blueprintjs/icons"; import { ButtonVariant, ButtonVariantTypes } from "components/constants"; +import { Stylesheet } from "entities/AppTheming"; const ICON_NAMES = Object.keys(IconNames).map( (name: string) => IconNames[name as keyof typeof IconNames], @@ -200,6 +201,14 @@ class IconButtonWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + getPageView() { const { borderRadius, diff --git a/app/client/src/widgets/IframeWidget/component/index.tsx b/app/client/src/widgets/IframeWidget/component/index.tsx index ee70b6d10a..f0cf84e198 100644 --- a/app/client/src/widgets/IframeWidget/component/index.tsx +++ b/app/client/src/widgets/IframeWidget/component/index.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import { hexToRgba } from "widgets/WidgetUtils"; import { ComponentProps } from "widgets/BaseComponent"; -import { useSelector } from "store"; +import { useSelector } from "react-redux"; import { getWidgetPropsForPropertyPane } from "selectors/propertyPaneSelectors"; import { getAppMode } from "selectors/applicationSelectors"; import { APP_MODE } from "entities/App"; diff --git a/app/client/src/widgets/IframeWidget/index.ts b/app/client/src/widgets/IframeWidget/index.ts index fe567ecca1..b529390a90 100644 --- a/app/client/src/widgets/IframeWidget/index.ts +++ b/app/client/src/widgets/IframeWidget/index.ts @@ -24,6 +24,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/IframeWidget/widget/index.tsx b/app/client/src/widgets/IframeWidget/widget/index.tsx index 1f451e8c13..1f4d611571 100644 --- a/app/client/src/widgets/IframeWidget/widget/index.tsx +++ b/app/client/src/widgets/IframeWidget/widget/index.tsx @@ -4,6 +4,7 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import IframeComponent from "../component"; import { IframeWidgetProps } from "../constants"; +import { Stylesheet } from "entities/AppTheming"; class IframeWidget extends BaseWidget { static getPropertyPaneContentConfig() { @@ -181,6 +182,13 @@ class IframeWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + handleUrlChange = (url: string) => { if (url && this.props.onURLChanged) { super.executeAction({ diff --git a/app/client/src/widgets/ImageWidget/index.ts b/app/client/src/widgets/ImageWidget/index.ts index c4d1ec619a..b50f7c2988 100644 --- a/app/client/src/widgets/ImageWidget/index.ts +++ b/app/client/src/widgets/ImageWidget/index.ts @@ -26,6 +26,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ImageWidget/widget/index.tsx b/app/client/src/widgets/ImageWidget/widget/index.tsx index d2a322de8f..fc2a05d0ee 100644 --- a/app/client/src/widgets/ImageWidget/widget/index.tsx +++ b/app/client/src/widgets/ImageWidget/widget/index.tsx @@ -6,6 +6,7 @@ import ImageComponent from "../component"; import { ValidationTypes } from "constants/WidgetValidation"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; +import { Stylesheet } from "entities/AppTheming"; class ImageWidget extends BaseWidget { constructor(props: ImageWidgetProps) { @@ -213,6 +214,13 @@ class ImageWidget extends BaseWidget { return {}; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + getPageView() { const { maxZoomLevel, objectFit } = this.props; return ( diff --git a/app/client/src/widgets/InputWidget/index.ts b/app/client/src/widgets/InputWidget/index.ts index ffc25f9964..5a05eacdad 100644 --- a/app/client/src/widgets/InputWidget/index.ts +++ b/app/client/src/widgets/InputWidget/index.ts @@ -36,6 +36,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/InputWidget/widget/index.tsx b/app/client/src/widgets/InputWidget/widget/index.tsx index 42706f2667..cdad567b9c 100644 --- a/app/client/src/widgets/InputWidget/widget/index.tsx +++ b/app/client/src/widgets/InputWidget/widget/index.tsx @@ -22,13 +22,14 @@ import { InputType, InputTypes } from "../constants"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { ISDCodeDropdownOptions } from "../component/ISDCodeDropdown"; import { CurrencyDropdownOptions } from "../component/CurrencyCodeDropdown"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { formatCurrencyNumber, getDecimalSeparator, getLocale, } from "../component/utilities"; import { LabelPosition } from "components/constants"; +import { Stylesheet } from "entities/AppTheming"; export function defaultValueValidation( value: any, @@ -405,6 +406,7 @@ class InputWidget extends BaseWidget { { label: "Top", value: LabelPosition.Top }, { label: "Auto", value: LabelPosition.Auto }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -709,6 +711,14 @@ class InputWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + onValueChange = (value: string) => { this.props.updateWidgetMetaProperty("text", value, { triggerPropertyName: "onTextChanged", diff --git a/app/client/src/widgets/InputWidgetV2/component/index.tsx b/app/client/src/widgets/InputWidgetV2/component/index.tsx index db8c7c5980..4a076206b4 100644 --- a/app/client/src/widgets/InputWidgetV2/component/index.tsx +++ b/app/client/src/widgets/InputWidgetV2/component/index.tsx @@ -57,6 +57,7 @@ class InputComponent extends React.Component { inputRef={this.props.inputRef} inputType={this.props.inputType} intent={this.props.intent} + isDynamicHeightEnabled={this.props.isDynamicHeightEnabled} isInvalid={this.props.isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/InputWidgetV2/index.ts b/app/client/src/widgets/InputWidgetV2/index.ts index 823a1414e3..99c7c10641 100644 --- a/app/client/src/widgets/InputWidgetV2/index.ts +++ b/app/client/src/widgets/InputWidgetV2/index.ts @@ -2,8 +2,16 @@ import Widget from "./widget"; import IconSVG from "./icon.svg"; import { CONFIG as BaseConfig } from "widgets/BaseInputWidget"; import { LabelPosition } from "components/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Input", iconSVG: IconSVG, @@ -24,6 +32,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/InputWidgetV2/widget/index.tsx b/app/client/src/widgets/InputWidgetV2/widget/index.tsx index c707188032..1c4781b247 100644 --- a/app/client/src/widgets/InputWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/InputWidgetV2/widget/index.tsx @@ -17,7 +17,7 @@ import { } from "@appsmith/constants/messages"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { GRID_DENSITY_MIGRATION_V1, ICON_NAMES } from "widgets/constants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import BaseInputWidget from "widgets/BaseInputWidget"; import { isNil, isNumber, merge, toString } from "lodash"; import derivedProperties from "./parsedDerivedProperties"; @@ -25,6 +25,8 @@ import { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget"; import { mergeWidgetConfig } from "utils/helpers"; import { InputTypes } from "widgets/BaseInputWidget/constants"; import { getParsedText } from "./Utilities"; +import { Stylesheet } from "entities/AppTheming"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; export function defaultValueValidation( value: any, @@ -388,6 +390,14 @@ class InputWidget extends BaseInputWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + handleFocusChange = (focusState: boolean) => { super.handleFocusChange(focusState); }; @@ -552,6 +562,7 @@ class InputWidget extends BaseInputWidget { iconAlign={this.props.iconAlign} iconName={this.props.iconName} inputType={this.props.inputType} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isInvalid={isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/JSONFormWidget/component/Form.tsx b/app/client/src/widgets/JSONFormWidget/component/Form.tsx index 49ab524329..65beda4101 100644 --- a/app/client/src/widgets/JSONFormWidget/component/Form.tsx +++ b/app/client/src/widgets/JSONFormWidget/component/Form.tsx @@ -114,30 +114,36 @@ const RESET_OPTIONS = { keepErrors: true, }; -function Form({ - backgroundColor, - children, - disabledWhenInvalid, - fixedFooter, - getFormData, - hideFooter, - isSubmitting, - isWidgetMounting, - onFormValidityUpdate, - onSubmit, - registerResetObserver, - resetButtonLabel, - resetButtonStyles, - schema, - scrollContents, - showReset, - stretchBodyVertically, - submitButtonLabel, - submitButtonStyles, - title, - unregisterResetObserver, - updateFormData, -}: FormProps) { +function Form( + { + backgroundColor, + children, + disabledWhenInvalid, + fixedFooter, + getFormData, + hideFooter, + isSubmitting, + isWidgetMounting, + onFormValidityUpdate, + onSubmit, + registerResetObserver, + resetButtonLabel, + resetButtonStyles, + schema, + scrollContents, + showReset, + stretchBodyVertically, + submitButtonLabel, + submitButtonStyles, + title, + unregisterResetObserver, + updateFormData, + }: FormProps, + ref: + | ((instance: HTMLDivElement | null) => void) + | React.MutableRefObject + | null, +) { const valuesRef = useRef({}); const methods = useForm(); const { formState, reset, watch } = methods; @@ -150,6 +156,7 @@ function Form({ >({ activeClassName: FOOTER_SCROLL_ACTIVE_CLASS_NAME, fixedFooter, + ref: ref as React.MutableRefObject, }); const onReset = ( @@ -256,7 +263,7 @@ function Form({ } scrollContents={scrollContents} > ({ ); } -export default Form; +export default React.forwardRef>(Form); diff --git a/app/client/src/widgets/JSONFormWidget/component/index.tsx b/app/client/src/widgets/JSONFormWidget/component/index.tsx index 2a25046440..43336eb437 100644 --- a/app/client/src/widgets/JSONFormWidget/component/index.tsx +++ b/app/client/src/widgets/JSONFormWidget/component/index.tsx @@ -89,26 +89,32 @@ function InfoMessage({ children }: { children: React.ReactNode }) { ); } -function JSONFormComponent({ - backgroundColor, - executeAction, - fieldLimitExceeded, - getFormData, - isSubmitting, - isWidgetMounting, - onFormValidityUpdate, - registerResetObserver, - renderMode, - resetButtonLabel, - schema, - setMetaInternalFieldState, - submitButtonLabel, - unregisterResetObserver, - updateFormData, - updateWidgetMetaProperty, - updateWidgetProperty, - ...rest -}: JSONFormComponentProps) { +function JSONFormComponent( + { + backgroundColor, + executeAction, + fieldLimitExceeded, + getFormData, + isSubmitting, + isWidgetMounting, + onFormValidityUpdate, + registerResetObserver, + renderMode, + resetButtonLabel, + schema, + setMetaInternalFieldState, + submitButtonLabel, + unregisterResetObserver, + updateFormData, + updateWidgetMetaProperty, + updateWidgetProperty, + ...rest + }: JSONFormComponentProps, + ref: + | ((instance: HTMLDivElement | null) => void) + | React.MutableRefObject + | null, +) { const isSchemaEmpty = isEmpty(schema); const styleProps = pick(rest, [ "borderColor", @@ -179,6 +185,7 @@ function JSONFormComponent({ isWidgetMounting={isWidgetMounting} onFormValidityUpdate={onFormValidityUpdate} onSubmit={rest.onSubmit} + ref={ref} registerResetObserver={registerResetObserver} resetButtonLabel={resetButtonLabel} resetButtonStyles={rest.resetButtonStyles} @@ -199,4 +206,4 @@ function JSONFormComponent({ ); } -export default React.memo(JSONFormComponent); +export default React.memo(React.forwardRef(JSONFormComponent)); diff --git a/app/client/src/widgets/JSONFormWidget/component/useFixedFooter.ts b/app/client/src/widgets/JSONFormWidget/component/useFixedFooter.ts index 9055bb02f6..6fe9ed3350 100644 --- a/app/client/src/widgets/JSONFormWidget/component/useFixedFooter.ts +++ b/app/client/src/widgets/JSONFormWidget/component/useFixedFooter.ts @@ -4,6 +4,7 @@ import { useLayoutEffect, useRef } from "react"; type UseFixedFooterProps = { fixedFooter: boolean; activeClassName: string; + ref: React.MutableRefObject; }; const ERROR_MARGIN = 2; @@ -21,10 +22,10 @@ const hasOverflowingContent = (element: HTMLElement) => { const THROTTLE_TIMEOUT = 50; function useFixedFooter< - TBodyElement extends HTMLElement = HTMLDivElement, + HTMLDivElement extends HTMLElement, TFooterElement extends HTMLElement = HTMLDivElement ->({ activeClassName, fixedFooter }: UseFixedFooterProps) { - const bodyRef = useRef(null); +>({ activeClassName, fixedFooter, ref }: UseFixedFooterProps) { + const bodyRef = ref; const footerRef = useRef(null); const isOverflowing = bodyRef.current diff --git a/app/client/src/widgets/JSONFormWidget/index.ts b/app/client/src/widgets/JSONFormWidget/index.ts index 1b0a187087..a9caf80d3c 100644 --- a/app/client/src/widgets/JSONFormWidget/index.ts +++ b/app/client/src/widgets/JSONFormWidget/index.ts @@ -3,6 +3,7 @@ import { Colors } from "constants/Colors"; import Widget, { JSONFormWidgetProps } from "./widget"; import { ButtonVariantTypes } from "components/constants"; import { BlueprintOperationTypes } from "widgets/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; const SUBMIT_BUTTON_DEFAULT_STYLES = { buttonVariant: ButtonVariantTypes.PRIMARY, @@ -13,6 +14,13 @@ const RESET_BUTTON_DEFAULT_STYLES = { }; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 1, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "JSON Form", iconSVG: IconSVG, @@ -80,6 +88,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/JSONFormWidget/widget/index.tsx b/app/client/src/widgets/JSONFormWidget/widget/index.tsx index b81f5d8915..bc0aaa6c68 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/index.tsx +++ b/app/client/src/widgets/JSONFormWidget/widget/index.tsx @@ -29,6 +29,7 @@ import { import { ButtonStyleProps } from "widgets/ButtonWidget/component"; import { BoxShadow } from "components/designSystems/appsmith/WidgetStyleContainer"; import { convertSchemaItemToFormData } from "../helper"; +import { ButtonStyles, ChildStylesheet, Stylesheet } from "entities/AppTheming"; export interface JSONFormWidgetProps extends WidgetProps { autoGenerateForm?: boolean; @@ -92,6 +93,7 @@ class JSONFormWidget extends BaseWidget< this.isWidgetMounting = true; this.actionQueue = []; } + formRef = React.createRef(); state = { resetObserverCallback: noop, @@ -122,6 +124,103 @@ class JSONFormWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + + submitButtonStyles: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + + resetButtonStyles: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + + childStylesheet: { + ARRAY: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + cellBorderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + cellBoxShadow: "none", + }, + OBJECT: { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + cellBorderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + cellBoxShadow: "none", + }, + CHECKBOX: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }, + CURRENCY_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + DATEPICKER: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + EMAIL_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + MULTISELECT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + MULTILINE_TEXT_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + NUMBER_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + PASSWORD_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + PHONE_NUMBER_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + RADIO_GROUP: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + boxShadow: "none", + }, + SELECT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + SWITCH: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + boxShadow: "none", + }, + TEXT_INPUT: { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + }, + }; + } + static defaultProps = {}; componentDidMount() { @@ -130,6 +229,7 @@ class JSONFormWidget extends BaseWidget< } componentDidUpdate(prevProps: JSONFormWidgetProps) { + super.componentDidUpdate(prevProps); if ( isEmpty(this.props.formData) && isEmpty(this.props.fieldState) && @@ -388,6 +488,7 @@ class JSONFormWidget extends BaseWidget< isWidgetMounting={this.isWidgetMounting} onFormValidityUpdate={this.onFormValidityUpdate} onSubmit={this.onSubmit} + ref={this.formRef} registerResetObserver={this.registerResetObserver} renderMode={this.props.renderMode} resetButtonLabel={this.props.resetButtonLabel} diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts index 57270d3d75..770eff24f9 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts @@ -1,7 +1,7 @@ import { Alignment } from "@blueprintjs/core"; import generatePanelPropertyConfig from "./propertyConfig/generatePanelPropertyConfig"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { JSONFormWidgetProps } from "."; import { ROOT_SCHEMA_KEY } from "../constants"; diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.test.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.test.ts index 4549724c87..5ddc62a26d 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.test.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.test.ts @@ -383,10 +383,9 @@ describe(".getStylesheetValue", () => { ]; inputAndExpectedOutput.forEach(([input, expectedOutput]) => { - //@ts-expect-error: type mismatch const result = getStylesheetValue(props, input, { childStylesheet: schemaTestData.fieldThemeStylesheets, - }); + } as any); expect(result).toEqual(expectedOutput); }); diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.ts index 5fc44566f8..d173047e19 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/helper.ts @@ -14,7 +14,7 @@ import { import { getGrandParentPropertyPath, getParentPropertyPath } from "../helper"; import { JSONFormWidgetProps } from ".."; import { getFieldStylesheet } from "widgets/JSONFormWidget/helper"; -import { AppTheme } from "entities/AppTheming"; +import { ButtonStyles, ChildStylesheet, Stylesheet } from "entities/AppTheming"; import { processSchemaItemAutocomplete } from "components/propertyControls/JSONFormComputeControl"; export type HiddenFnParams = [JSONFormWidgetProps, string]; @@ -103,7 +103,7 @@ export const getSchemaItem = ( export const getStylesheetValue = ( props: JSONFormWidgetProps, propertyPath: string, - widgetStylesheet?: AppTheme["stylesheet"][string], + widgetStylesheet?: Stylesheet, ) => { return getSchemaItem(props, propertyPath).compute( (schemaItem, propertyName) => { diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts index 66241d3162..08129fb711 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts @@ -4,7 +4,7 @@ import { } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { get } from "lodash"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { ARRAY_ITEM_KEY, FIELD_EXPECTING_OPTIONS, diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/input.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/input.ts index b6415db5e9..b348620a01 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/input.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/input.ts @@ -1,4 +1,4 @@ -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { CurrencyDropdownOptions } from "widgets/CurrencyInputWidget/component/CurrencyCodeDropdown"; import { FieldType, INPUT_TYPES } from "widgets/JSONFormWidget/constants"; import { diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts index 575d9d9fd8..7e6e4c8a0a 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/multiSelect.ts @@ -10,7 +10,7 @@ import { ValidationResponse, ValidationTypes, } from "constants/WidgetValidation"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { JSONFormWidgetProps } from "../.."; export function defaultOptionValueValidation( diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts index 128e0181dc..b53107c0a5 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/radioGroup.ts @@ -3,7 +3,7 @@ import { ValidationTypes, } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { FieldType } from "widgets/JSONFormWidget/constants"; import { optionsCustomValidation } from "widgets/RadioGroupWidget/widget"; import { diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts index 400ad365e6..9ee970a4fa 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/select.ts @@ -11,7 +11,7 @@ import { ValidationTypes, } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export function defaultOptionValueValidation( inputValue: unknown, diff --git a/app/client/src/widgets/ListWidget/index.ts b/app/client/src/widgets/ListWidget/index.ts index 57eed2ad87..624811d2a4 100644 --- a/app/client/src/widgets/ListWidget/index.ts +++ b/app/client/src/widgets/ListWidget/index.ts @@ -3,6 +3,7 @@ import { combineDynamicBindings, getDynamicBindings, } from "utils/DynamicBindingUtils"; +import { RegisteredWidgetFeatures } from "utils/WidgetFeatures"; import { WidgetProps } from "widgets/BaseWidget"; import { BlueprintOperationTypes, @@ -122,6 +123,9 @@ export const CONFIG = { isDeletable: false, disallowCopy: true, disablePropertyPane: true, + disabledWidgetFeatures: [ + RegisteredWidgetFeatures.DYNAMIC_HEIGHT, + ], openParentPropertyPane: true, children: [], blueprint: { @@ -293,6 +297,11 @@ export const CONFIG = { propertyName: "template", propertyValue: template, }, + { + widgetId: container.widgetId, + propertyName: "dynamicHeight", + propertyValue: "FIXED", + }, ]; // add logBlackList to updateProperyMap for all children @@ -402,6 +411,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ListWidget/widget/index.tsx b/app/client/src/widgets/ListWidget/widget/index.tsx index 8479be9ddf..8df28ec616 100644 --- a/app/client/src/widgets/ListWidget/widget/index.tsx +++ b/app/client/src/widgets/ListWidget/widget/index.tsx @@ -42,6 +42,7 @@ import { entityDefinitions } from "utils/autocomplete/EntityDefinitions"; import { PrivateWidgets } from "entities/DataTree/dataTreeFactory"; import equal from "fast-deep-equal/es6"; import { klona } from "klona/lite"; +import { Stylesheet } from "entities/AppTheming"; const LIST_WIDGET_PAGINATION_HEIGHT = 36; @@ -72,6 +73,14 @@ class ListWidget extends BaseWidget, WidgetState> { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + componentDidMount() { if (this.props.serverSidePaginationEnabled && !this.props.pageNo) { this.props.updateWidgetMetaProperty("pageNo", 1); @@ -561,6 +570,8 @@ class ListWidget extends BaseWidget, WidgetState> { `list-widget-child-id-${itemIndex}-${widget.widgetName}`, ); + set(widget, `isAutoGeneratedWidget`, true); + if (this.props.renderMode === RenderModes.CANVAS) { set(widget, `resizeDisabled`, true); set(widget, `disablePropertyPane`, true); diff --git a/app/client/src/widgets/ListWidget/widget/propertyConfig.ts b/app/client/src/widgets/ListWidget/widget/propertyConfig.ts index 0d6f6ecfdf..085884376f 100644 --- a/app/client/src/widgets/ListWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/ListWidget/widget/propertyConfig.ts @@ -5,7 +5,7 @@ import { ListWidgetProps } from "../constants"; import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { EVAL_VALUE_PATH } from "utils/DynamicBindingUtils"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; export const PropertyPaneContentConfig = [ { diff --git a/app/client/src/widgets/MapChartWidget/index.ts b/app/client/src/widgets/MapChartWidget/index.ts index a032e41d1f..db63428497 100644 --- a/app/client/src/widgets/MapChartWidget/index.ts +++ b/app/client/src/widgets/MapChartWidget/index.ts @@ -43,6 +43,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MapChartWidget/widget/index.tsx b/app/client/src/widgets/MapChartWidget/widget/index.tsx index 169b5eb774..dfc06faba9 100644 --- a/app/client/src/widgets/MapChartWidget/widget/index.tsx +++ b/app/client/src/widgets/MapChartWidget/widget/index.tsx @@ -21,7 +21,8 @@ import { MapTypes, } from "../constants"; import { MapType } from "../component"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { Stylesheet } from "entities/AppTheming"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; const MapChartComponent = lazy(() => retryPromise(() => @@ -331,6 +332,14 @@ class MapChartWidget extends BaseWidget { return "MAP_CHART_WIDGET"; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + fontFamily: "{{appsmith.theme.fontFamily.appFont}}", + }; + } + handleDataPointClick = (evt: any) => { const { onDataPointClick } = this.props; diff --git a/app/client/src/widgets/MapWidget/component/index.tsx b/app/client/src/widgets/MapWidget/component/index.tsx index 583084d10a..d36a8bc84e 100644 --- a/app/client/src/widgets/MapWidget/component/index.tsx +++ b/app/client/src/widgets/MapWidget/component/index.tsx @@ -172,6 +172,7 @@ const MyMapComponent = withGoogleMap((props: any) => { fillOpacity: 1, strokeWeight: 0, scale: 1, + // @ts-expect-error: cannot find name google anchor: new google.maps.Point(12, 24), }} key={index} diff --git a/app/client/src/widgets/MapWidget/index.ts b/app/client/src/widgets/MapWidget/index.ts index d140ecfbbf..e35992c42c 100644 --- a/app/client/src/widgets/MapWidget/index.ts +++ b/app/client/src/widgets/MapWidget/index.ts @@ -29,6 +29,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MapWidget/widget/index.tsx b/app/client/src/widgets/MapWidget/widget/index.tsx index 93fe173a85..4a62b162bd 100644 --- a/app/client/src/widgets/MapWidget/widget/index.tsx +++ b/app/client/src/widgets/MapWidget/widget/index.tsx @@ -12,6 +12,7 @@ import { getBorderCSSShorthand } from "constants/DefaultTheme"; import { MarkerProps } from "../constants"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; +import { Stylesheet } from "entities/AppTheming"; const { google } = getAppsmithConfigs(); @@ -296,6 +297,13 @@ class MapWidget extends BaseWidget { return {}; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + updateCenter = (lat: number, long: number, title?: string) => { this.props.updateWidgetMetaProperty("center", { lat, long, title }); }; diff --git a/app/client/src/widgets/MenuButtonWidget/index.ts b/app/client/src/widgets/MenuButtonWidget/index.ts index 9997f75bdc..a67bd6a6b8 100644 --- a/app/client/src/widgets/MenuButtonWidget/index.ts +++ b/app/client/src/widgets/MenuButtonWidget/index.ts @@ -52,6 +52,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx index a40230763a..ee56a12c78 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx @@ -14,6 +14,7 @@ import { } from "components/constants"; import { IconName } from "@blueprintjs/icons"; import { MinimumPopupRows } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; export interface MenuButtonWidgetProps extends WidgetProps { label?: string; isDisabled?: boolean; @@ -435,6 +436,14 @@ class MenuButtonWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + menuColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + menuItemClickHandler = (onClick: string | undefined) => { if (onClick) { super.executeAction({ diff --git a/app/client/src/widgets/ModalWidget/component/index.tsx b/app/client/src/widgets/ModalWidget/component/index.tsx index cc19b3a670..3f9b30c2d1 100644 --- a/app/client/src/widgets/ModalWidget/component/index.tsx +++ b/app/client/src/widgets/ModalWidget/component/index.tsx @@ -133,6 +133,7 @@ export type ModalComponentProps = { widgetName: string; backgroundColor: string; borderRadius: string; + isDynamicHeightEnabled: boolean; }; /* eslint-disable react/display-name */ @@ -220,6 +221,10 @@ export default function ModalComponent(props: ModalComponentProps) { }); }; + const isVerticalResizeEnabled = useMemo(() => { + return !props.isDynamicHeightEnabled && enableResize; + }, [props.isDynamicHeightEnabled, enableResize]); + const getResizableContent = () => { //id for Content is required for Copy Paste inside the modal return ( @@ -227,7 +232,8 @@ export default function ModalComponent(props: ModalComponentProps) { allowResize componentHeight={props.height || 0} componentWidth={props.width || 0} - enable={enableResize} + enableHorizontalResize={enableResize} + enableVerticalResize={isVerticalResizeEnabled} handles={handles} isColliding={() => false} onStart={onResizeStart} diff --git a/app/client/src/widgets/ModalWidget/index.ts b/app/client/src/widgets/ModalWidget/index.ts index 7d87606862..dd009b9ed6 100644 --- a/app/client/src/widgets/ModalWidget/index.ts +++ b/app/client/src/widgets/ModalWidget/index.ts @@ -20,12 +20,19 @@ export const CONFIG = { iconSVG: IconSVG, needsMeta: true, isCanvas: true, + features: { + dynamicHeight: { + sectionIndex: 0, + active: true, + }, + }, searchTags: ["dialog", "popup", "notification"], defaults: { rows: 24, columns: 24, width: 456, height: GridDefaults.DEFAULT_GRID_ROW_HEIGHT * 24, + minDynamicHeight: 24, canEscapeKeyClose: true, animateLoading: true, // detachFromLayout is set true for widgets that are not bound to the widgets within the layout. @@ -179,6 +186,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ModalWidget/widget/index.tsx b/app/client/src/widgets/ModalWidget/widget/index.tsx index ceef967282..e533340314 100644 --- a/app/client/src/widgets/ModalWidget/widget/index.tsx +++ b/app/client/src/widgets/ModalWidget/widget/index.tsx @@ -15,7 +15,9 @@ import { AppState } from "@appsmith/reducers"; import { getCanvasWidth, snipingModeSelector } from "selectors/editorSelectors"; import { deselectModalWidgetAction } from "actions/widgetSelectionActions"; import { ValidationTypes } from "constants/WidgetValidation"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import { CanvasWidgetsStructureReduxState } from "reducers/entityReducers/canvasWidgetsStructureReducer"; +import { Stylesheet } from "entities/AppTheming"; const minSize = 100; @@ -108,6 +110,13 @@ export class ModalWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static defaultProps = { isOpen: true, canEscapeKeyClose: false, @@ -158,6 +167,12 @@ export class ModalWidget extends BaseWidget { width: Math.max(minSize, this.getModalWidth(dimensions.width)), }; + if ( + newDimensions.height !== this.props.height && + isAutoHeightEnabledForWidget(this.props) + ) + return; + const canvasWidgetId = this.props.children && this.props.children.length > 0 ? this.props.children[0]?.widgetId @@ -227,6 +242,7 @@ export class ModalWidget extends BaseWidget { className={`t--modal-widget ${generateClassName(this.props.widgetId)}`} enableResize={isResizeEnabled} height={this.props.height} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isEditMode={isEditMode} isOpen={!!this.props.isVisible} maxWidth={this.getMaxModalWidth()} @@ -249,6 +265,14 @@ export class ModalWidget extends BaseWidget { let children = this.getChildren(); children = this.makeModalSelectable(children); children = this.showWidgetName(children, true); + if (isAutoHeightEnabledForWidget(this.props, true)) { + children = this.addAutoHeightOverlay(children, { + width: "100%", + height: "100%", + left: 0, + top: 0, + }); + } return this.makeModalComponent(children, true); } diff --git a/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx index 5f9c4b684e..08d7b84102 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/component/index.tsx @@ -23,10 +23,11 @@ import styled from "styled-components"; import { RenderMode, TextSize } from "constants/WidgetConstants"; import { Alignment, Button, Classes, InputGroup } from "@blueprintjs/core"; import { labelMargin, WidgetContainerDiff } from "widgets/WidgetUtils"; -import { Icon, LabelWithTooltip } from "design-system"; +import { Icon } from "design-system"; import { Colors } from "constants/Colors"; import { LabelPosition } from "components/constants"; import useDropdown from "widgets/useDropdown"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; export interface TreeSelectProps extends Required< @@ -49,6 +50,7 @@ export interface TreeSelectProps compactMode: boolean; dropDownWidth: number; width: number; + isDynamicHeightEnabled?: boolean; isValid: boolean; borderRadius: string; boxShadow?: string; @@ -110,6 +112,7 @@ function MultiTreeSelectComponent({ dropDownWidth, expandAll, filterText, + isDynamicHeightEnabled, isFilterable, isValid, labelAlignment, @@ -132,8 +135,7 @@ function MultiTreeSelectComponent({ const [key, setKey] = useState(Math.random()); const [filter, setFilter] = useState(filterText ?? ""); - const _menu = useRef(null); - + const _menu = useRef(null); const labelRef = useRef(null); const inputRef = useRef(null); @@ -210,7 +212,6 @@ function MultiTreeSelectComponent({ ); const onClear = useCallback(() => onChange([], []), []); - const onDropdownVisibleChange = (open: boolean) => { onOpen(open); // clear the search input on closing the widget @@ -244,6 +245,7 @@ function MultiTreeSelectComponent({ disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={loading} position={labelPosition} ref={labelRef} diff --git a/app/client/src/widgets/MultiSelectTreeWidget/index.ts b/app/client/src/widgets/MultiSelectTreeWidget/index.ts index ced09d8edf..0d63b8e101 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/index.ts +++ b/app/client/src/widgets/MultiSelectTreeWidget/index.ts @@ -1,9 +1,17 @@ import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Multi TreeSelect", iconSVG: IconSVG, @@ -54,6 +62,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx index 057ac56436..917029362d 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx @@ -12,11 +12,13 @@ import { DefaultValueType } from "rc-tree-select/lib/interface"; import { Layers } from "constants/Layers"; import { CheckedStrategy } from "rc-tree-select/lib/utils/strategyUtil"; import { GRID_DENSITY_MIGRATION_V1, MinimumPopupRows } from "widgets/constants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import MultiTreeSelectComponent from "../component"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import derivedProperties from "./parseDerivedProperties"; +import { Stylesheet } from "entities/AppTheming"; function defaultOptionValueValidation(value: unknown): ValidationResponse { let values: string[] = []; @@ -168,6 +170,7 @@ class MultiSelectTreeWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -482,6 +485,14 @@ class MultiSelectTreeWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + componentDidUpdate(prevProps: MultiSelectTreeWidgetProps): void { if ( xor(this.props.defaultOptionValue, prevProps.defaultOptionValue).length > @@ -517,6 +528,7 @@ class MultiSelectTreeWidget extends BaseWidget< zIndex: Layers.dropdownModalWidget, }} expandAll={this.props.expandAll} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isFilterable isValid={!isInvalid} labelAlignment={this.props.labelAlignment} diff --git a/app/client/src/widgets/MultiSelectWidget/component/index.tsx b/app/client/src/widgets/MultiSelectWidget/component/index.tsx index 1e35d5b92f..a623578a45 100644 --- a/app/client/src/widgets/MultiSelectWidget/component/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/component/index.tsx @@ -13,12 +13,13 @@ import { TextSize, } from "constants/WidgetConstants"; import debounce from "lodash/debounce"; -import { Icon, LabelWithTooltip } from "design-system"; +import { Icon } from "design-system"; import { Alignment, Classes } from "@blueprintjs/core"; import { WidgetContainerDiff } from "widgets/WidgetUtils"; import _ from "lodash"; import { Colors } from "constants/Colors"; import { LabelPosition } from "components/constants"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; const menuItemSelectedIcon = (props: { isSelected: boolean }) => { return ; @@ -54,6 +55,7 @@ export interface MultiSelectProps borderRadius: string; boxShadow?: string; accentColor: string; + isDynamicHeightEnabled?: boolean; } const DEBOUNCE_TIMEOUT = 800; @@ -64,6 +66,7 @@ function MultiSelectComponent({ disabled, dropdownStyle, dropDownWidth, + isDynamicHeightEnabled, isValid, labelAlignment, labelPosition, @@ -186,6 +189,7 @@ function MultiSelectComponent({ disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={loading} position={labelPosition} text={labelText} diff --git a/app/client/src/widgets/MultiSelectWidget/index.ts b/app/client/src/widgets/MultiSelectWidget/index.ts index 8dad21d3c0..02105067b5 100644 --- a/app/client/src/widgets/MultiSelectWidget/index.ts +++ b/app/client/src/widgets/MultiSelectWidget/index.ts @@ -37,6 +37,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx index c922809d91..496aa0ede2 100644 --- a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx @@ -11,11 +11,12 @@ import { import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import MultiSelectComponent from "../component"; import { Layers } from "constants/Layers"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; import { DraftValueType } from "rc-select/lib/Select"; +import { Stylesheet } from "entities/AppTheming"; function defaultOptionValueValidation(value: unknown): ValidationResponse { let values: string[] = []; @@ -210,6 +211,7 @@ class MultiSelectWidget extends BaseWidget< { label: "Top", value: LabelPosition.Top }, { label: "Auto", value: LabelPosition.Auto }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -409,6 +411,14 @@ class MultiSelectWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + 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/component/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx index 41905d88e2..5ede1c7d31 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx @@ -21,8 +21,9 @@ import { labelMargin, WidgetContainerDiff } from "widgets/WidgetUtils"; import { Colors } from "constants/Colors"; import { LabelPosition } from "components/constants"; import { uniqBy } from "lodash"; -import { Icon, LabelWithTooltip } from "design-system"; +import { Icon } from "design-system"; import useDropdown from "widgets/useDropdown"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; const menuItemSelectedIcon = (props: { isSelected: boolean }) => { return ; @@ -61,6 +62,7 @@ export interface MultiSelectProps onFocus?: (e: React.FocusEvent) => void; onBlur?: (e: React.FocusEvent) => void; renderMode?: RenderMode; + isDynamicHeightEnabled?: boolean; } const DEBOUNCE_TIMEOUT = 1000; @@ -75,6 +77,7 @@ function MultiSelectComponent({ dropdownStyle, dropDownWidth, filterText, + isDynamicHeightEnabled, isFilterable, isValid, labelAlignment, @@ -304,6 +307,7 @@ function MultiSelectComponent({ disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={loading} position={labelPosition} ref={labelRef} diff --git a/app/client/src/widgets/MultiSelectWidgetV2/index.ts b/app/client/src/widgets/MultiSelectWidgetV2/index.ts index fd4ed2d79e..bebb5c9d4b 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/index.ts +++ b/app/client/src/widgets/MultiSelectWidgetV2/index.ts @@ -2,8 +2,16 @@ import Widget from "./widget"; import IconSVG from "./icon.svg"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; +import { DynamicHeight } from "utils/WidgetFeatures"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 4, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "MultiSelect", iconSVG: IconSVG, @@ -39,6 +47,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx index 41405abf71..2d1812878c 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx @@ -16,7 +16,9 @@ import { Layers } from "constants/Layers"; import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { Stylesheet } from "entities/AppTheming"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; export function defaultOptionValueValidation( value: unknown, @@ -276,6 +278,7 @@ class MultiSelectWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -572,6 +575,14 @@ class MultiSelectWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getDerivedPropertiesMap() { return { value: `{{this.selectedOptionValues}}`, @@ -650,6 +661,7 @@ class MultiSelectWidget extends BaseWidget< zIndex: Layers.dropdownModalWidget, }} filterText={this.props.filterText} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isFilterable={this.props.isFilterable} isValid={!isInvalid} labelAlignment={this.props.labelAlignment} diff --git a/app/client/src/widgets/NumberSliderWidget/component/Slider.tsx b/app/client/src/widgets/NumberSliderWidget/component/Slider.tsx index a7c2ba6dd3..be6b695a68 100644 --- a/app/client/src/widgets/NumberSliderWidget/component/Slider.tsx +++ b/app/client/src/widgets/NumberSliderWidget/component/Slider.tsx @@ -51,6 +51,9 @@ export interface SliderComponentProps /** If true label will be not be hidden when user stops dragging */ tooltipAlwaysOn: boolean; + /** helpText for the label tooltip */ + labelTooltip?: string; + /** Disables slider */ disabled?: boolean; @@ -95,6 +98,7 @@ const SliderComponent = (props: SliderComponentProps) => { labelText, labelTextColor, labelTextSize, + labelTooltip, labelWidth, loading, marks, @@ -207,6 +211,7 @@ const SliderComponent = (props: SliderComponentProps) => { disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + helpText={labelTooltip} loading={loading} position={labelPosition} text={labelText} diff --git a/app/client/src/widgets/NumberSliderWidget/index.ts b/app/client/src/widgets/NumberSliderWidget/index.ts index f3c7f27583..d7a9be5aa1 100644 --- a/app/client/src/widgets/NumberSliderWidget/index.ts +++ b/app/client/src/widgets/NumberSliderWidget/index.ts @@ -25,7 +25,7 @@ export const CONFIG = { isDisabled: false, tooltipAlwaysOn: false, rows: 8, - columns: 38, + columns: 40, widgetName: "NumberSlider", shouldScroll: false, shouldTruncate: false, @@ -44,6 +44,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/NumberSliderWidget/validations.ts b/app/client/src/widgets/NumberSliderWidget/validations.ts index ceb64942c3..9865db0427 100644 --- a/app/client/src/widgets/NumberSliderWidget/validations.ts +++ b/app/client/src/widgets/NumberSliderWidget/validations.ts @@ -107,7 +107,7 @@ export function defaultValueValidation( return { isValid: false, parsed: undefined, - messages: ["This value must be greater than min value"], + messages: ["This value must be greater than or equal to the min value"], }; } @@ -115,7 +115,7 @@ export function defaultValueValidation( return { isValid: false, parsed: undefined, - messages: ["This value must be less than max value"], + messages: ["This value must be less than or equal to the max value"], }; } diff --git a/app/client/src/widgets/NumberSliderWidget/widget/index.tsx b/app/client/src/widgets/NumberSliderWidget/widget/index.tsx index 2e4cc9eb3c..1375be6c70 100644 --- a/app/client/src/widgets/NumberSliderWidget/widget/index.tsx +++ b/app/client/src/widgets/NumberSliderWidget/widget/index.tsx @@ -6,6 +6,7 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import SliderComponent, { SliderComponentProps } from "../component/Slider"; import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; +import { Stylesheet } from "entities/AppTheming"; export interface NumberSliderWidgetProps extends WidgetProps, @@ -38,6 +39,12 @@ class NumberSliderWidget extends BaseWidget< return styleConfig; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + }; + } + componentDidUpdate(prevProps: NumberSliderWidgetProps) { /** * If you change the defaultValue from the propertyPane @@ -52,7 +59,7 @@ class NumberSliderWidget extends BaseWidget< } } - static getDefaultPropertiesMap(): Record { + static getDefaultPropertiesMap(): Record { return { value: "defaultValue", }; @@ -103,6 +110,7 @@ class NumberSliderWidget extends BaseWidget< labelText={this.props.labelText} labelTextColor={this.props.labelTextColor} labelTextSize={this.props.labelTextSize} + labelTooltip={this.props.labelTooltip} labelWidth={this.getLabelWidth()} loading={this.props.isLoading} // If showMarks is off don't show marks at all diff --git a/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/contentConfig.ts index 34959094ff..2088b6f521 100644 --- a/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/contentConfig.ts @@ -1,7 +1,7 @@ import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; import { ValidationTypes } from "constants/WidgetValidation"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { NumberSliderWidgetProps } from ".."; import { defaultValueValidation, @@ -119,6 +119,7 @@ export default [ { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Left, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -169,6 +170,16 @@ export default [ { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { propertyName: "showMarksLabel", helpText: "Show the marks label below the slider", @@ -254,7 +265,7 @@ export default [ { propertyName: "tooltipAlwaysOn", helpText: "Keep showing the tooltip with value", - label: "Tooltip Always On", + label: "Show value always", controlType: "SWITCH", isJSConvertible: true, isBindProperty: true, diff --git a/app/client/src/widgets/PhoneInputWidget/component/index.tsx b/app/client/src/widgets/PhoneInputWidget/component/index.tsx index 1eb61c24fc..e93c12f755 100644 --- a/app/client/src/widgets/PhoneInputWidget/component/index.tsx +++ b/app/client/src/widgets/PhoneInputWidget/component/index.tsx @@ -70,6 +70,7 @@ class PhoneInputComponent extends React.PureComponent< inputHTMLType="TEL" inputType={InputTypes.PHONE_NUMBER} intent={this.props.intent} + isDynamicHeightEnabled={this.props.isDynamicHeightEnabled} isInvalid={this.props.isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/PhoneInputWidget/index.ts b/app/client/src/widgets/PhoneInputWidget/index.ts index df8aa05a2c..71c66829a2 100644 --- a/app/client/src/widgets/PhoneInputWidget/index.ts +++ b/app/client/src/widgets/PhoneInputWidget/index.ts @@ -3,8 +3,16 @@ import IconSVG from "./icon.svg"; import { CONFIG as BaseConfig } from "widgets/BaseInputWidget"; import { getDefaultISDCode } from "./component/ISDCodeDropdown"; import { LabelPosition } from "components/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Phone Input", iconSVG: IconSVG, @@ -27,6 +35,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx index 0fcd6f5489..1acc055347 100644 --- a/app/client/src/widgets/PhoneInputWidget/widget/index.tsx +++ b/app/client/src/widgets/PhoneInputWidget/widget/index.tsx @@ -16,7 +16,7 @@ import { getCountryCode, ISDCodeDropdownOptions, } from "../component/ISDCodeDropdown"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import _ from "lodash"; import BaseInputWidget from "widgets/BaseInputWidget"; import derivedProperties from "./parsedDerivedProperties"; @@ -30,6 +30,8 @@ import { import * as Sentry from "@sentry/react"; import log from "loglevel"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; export function defaultValueValidation( value: any, @@ -185,6 +187,14 @@ class PhoneInputWidget extends BaseInputWidget< }); } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + getFormattedPhoneNumber(value: string) { const countryCode = getCountryCode(this.props.dialCode); let formattedValue; @@ -337,6 +347,7 @@ class PhoneInputWidget extends BaseInputWidget< iconAlign={this.props.iconAlign} iconName={this.props.iconName} inputType={this.props.inputType} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isInvalid={isInvalid} isLoading={this.props.isLoading} label={this.props.label} diff --git a/app/client/src/widgets/ProgressBarWidget/index.ts b/app/client/src/widgets/ProgressBarWidget/index.ts index f171897526..8d72fa495e 100644 --- a/app/client/src/widgets/ProgressBarWidget/index.ts +++ b/app/client/src/widgets/ProgressBarWidget/index.ts @@ -27,6 +27,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx index 31ef4fc130..89f1c94551 100644 --- a/app/client/src/widgets/ProgressBarWidget/widget/index.tsx +++ b/app/client/src/widgets/ProgressBarWidget/widget/index.tsx @@ -8,6 +8,7 @@ import ProgressBarComponent from "../component"; import { ValidationTypes } from "constants/WidgetValidation"; import { Colors } from "constants/Colors"; import { BarType } from "../constants"; +import { Stylesheet } from "entities/AppTheming"; class ProgressBarWidget extends BaseWidget< ProgressBarWidgetProps, @@ -145,6 +146,13 @@ class ProgressBarWidget extends BaseWidget< return {}; } + static getStylesheetConfig(): Stylesheet { + return { + fillColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }; + } + getPageView() { return ( { static getPropertyPaneContentConfig() { @@ -153,6 +154,13 @@ class ProgressWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + fillColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + }; + } + static getDerivedPropertiesMap(): DerivedPropertiesMap { return {}; } diff --git a/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx b/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx index 0baf2b4d92..bf08f89a3e 100644 --- a/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx +++ b/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx @@ -10,7 +10,7 @@ import { Color } from "constants/Colors"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import TextComponent, { TextAlign } from "../component"; import { ContainerStyle } from "widgets/ContainerWidget/component"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { OverflowTypes } from "../constants"; import WidgetStyleContainer from "components/designSystems/appsmith/WidgetStyleContainer"; import { pick } from "lodash"; diff --git a/app/client/src/widgets/RadioGroupWidget/component/index.tsx b/app/client/src/widgets/RadioGroupWidget/component/index.tsx index 3494f0a333..7ffe291311 100644 --- a/app/client/src/widgets/RadioGroupWidget/component/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/component/index.tsx @@ -1,16 +1,15 @@ import React, { useCallback } from "react"; import styled from "styled-components"; import { ComponentProps } from "widgets/BaseComponent"; -import { RadioOption } from "../constants"; import { RadioGroup, Radio, Alignment, Classes } from "@blueprintjs/core"; import { TextSize } from "constants/WidgetConstants"; import { BlueprintRadioSwitchGroupTransform } from "constants/DefaultTheme"; import { LabelPosition } from "components/constants"; -import { - LabelWithTooltip, +import { RadioOption } from "../constants"; +import LabelWithTooltip, { labelLayoutStyles, LABEL_CONTAINER_CLASS, -} from "design-system"; +} from "widgets/components/LabelWithTooltip"; export interface RadioGroupContainerProps { compactMode: boolean; @@ -19,7 +18,9 @@ export interface RadioGroupContainerProps { export const RadioGroupContainer = styled.div` ${labelLayoutStyles} + & .${LABEL_CONTAINER_CLASS} { + align-self: center; ${({ labelPosition }) => labelPosition === LabelPosition.Left && "min-height: 30px"}; } @@ -33,11 +34,11 @@ export interface StyledRadioGroupProps { labelPosition?: LabelPosition; optionCount: number; accentColor: string; + isDynamicHeightEnabled?: boolean; } const StyledRadioGroup = styled(RadioGroup)` ${BlueprintRadioSwitchGroupTransform} - height: ${({ inline }) => (inline ? "32px" : "100%")}; .${Classes.CONTROL} { & input:checked ~ .${Classes.CONTROL_INDICATOR} { @@ -47,8 +48,12 @@ const StyledRadioGroup = styled(RadioGroup)` & input:disabled:checked ~ .${Classes.CONTROL_INDICATOR} { &:before { - opacity: 1; - background-image: radial-gradient(var( --wds-color-bg-disabled-strong), var( --wds-color-bg-disabled-strong) 28%, transparent 32%) + opacity: 1; + background-image: radial-gradient( + var(--wds-color-bg-disabled-strong), + var(--wds-color-bg-disabled-strong) 28%, + transparent 32% + ); } } } @@ -68,12 +73,14 @@ function RadioGroupComponent(props: RadioGroupComponentProps) { disabled, height, inline, + isDynamicHeightEnabled, labelAlignment, labelPosition, labelStyle, labelText, labelTextColor, labelTextSize, + labelTooltip, labelWidth, loading, onRadioSelectionChange, @@ -106,7 +113,9 @@ function RadioGroupComponent(props: RadioGroupComponentProps) { disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + helpText={labelTooltip} inline={inline} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={loading} optionCount={optionCount} position={labelPosition} @@ -121,6 +130,7 @@ function RadioGroupComponent(props: RadioGroupComponentProps) { disabled={disabled} height={height} inline={inline} + isDynamicHeightEnabled={isDynamicHeightEnabled} labelPosition={labelPosition} onChange={handleChange} optionCount={options.length} @@ -150,6 +160,7 @@ export interface RadioGroupComponentProps extends ComponentProps { selectedOptionValue: string; disabled: boolean; loading: boolean; + isDynamicHeightEnabled?: boolean; inline: boolean; alignment: Alignment; compactMode: boolean; @@ -160,6 +171,7 @@ export interface RadioGroupComponentProps extends ComponentProps { labelTextSize?: TextSize; labelStyle?: string; labelWidth?: number; + labelTooltip?: string; widgetId: string; height?: number; accentColor: string; diff --git a/app/client/src/widgets/RadioGroupWidget/index.ts b/app/client/src/widgets/RadioGroupWidget/index.ts index dc0b3c2942..865fbc5aba 100644 --- a/app/client/src/widgets/RadioGroupWidget/index.ts +++ b/app/client/src/widgets/RadioGroupWidget/index.ts @@ -8,6 +8,12 @@ export const CONFIG = { name: "Radio Group", iconSVG: IconSVG, needsMeta: true, + features: { + dynamicHeight: { + sectionIndex: 3, + active: true, + }, + }, searchTags: ["choice"], defaults: { rows: 6, @@ -37,6 +43,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx index 631c944e2c..8156eb032a 100644 --- a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx @@ -1,20 +1,21 @@ import React from "react"; import { Alignment } from "@blueprintjs/core"; import { isArray, compact, isNumber } from "lodash"; - import BaseWidget, { WidgetProps, WidgetState } from "../../BaseWidget"; import { TextSize, WidgetType } from "constants/WidgetConstants"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { - ValidationResponse, - ValidationTypes, -} from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { RadioOption } from "../constants"; import { LabelPosition } from "components/constants"; import RadioGroupComponent from "../component"; +import { Stylesheet } from "entities/AppTheming"; +import { + ValidationResponse, + ValidationTypes, +} from "constants/WidgetValidation"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; /** * Validation rules: @@ -225,6 +226,7 @@ class RadioGroupWidget extends BaseWidget { { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -291,6 +293,16 @@ class RadioGroupWidget extends BaseWidget { { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { helpText: "Controls the visibility of the widget", propertyName: "isVisible", @@ -497,6 +509,13 @@ class RadioGroupWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + boxShadow: "none", + }; + } + componentDidUpdate(prevProps: RadioGroupWidgetProps): void { if ( this.props.defaultOptionValue !== prevProps.defaultOptionValue && @@ -535,6 +554,7 @@ class RadioGroupWidget extends BaseWidget { disabled={isDisabled} height={componentHeight} inline={Boolean(isInline)} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} key={widgetId} labelAlignment={labelAlignment} labelPosition={labelPosition} @@ -542,6 +562,7 @@ class RadioGroupWidget extends BaseWidget { labelText={label} labelTextColor={labelTextColor} labelTextSize={labelTextSize} + labelTooltip={this.props.labelTooltip} labelWidth={this.getLabelWidth()} loading={isLoading} onRadioSelectionChange={this.onRadioSelectionChange} diff --git a/app/client/src/widgets/RangeSliderWidget/component/RangeSlider.tsx b/app/client/src/widgets/RangeSliderWidget/component/RangeSlider.tsx index 223fbc431a..8c1d014dd2 100644 --- a/app/client/src/widgets/RangeSliderWidget/component/RangeSlider.tsx +++ b/app/client/src/widgets/RangeSliderWidget/component/RangeSlider.tsx @@ -64,6 +64,9 @@ export interface RangeSliderComponentProps /** If true label will be not be hidden when user stops dragging */ tooltipAlwaysOn: boolean; + /** helpText for the label tooltip */ + labelTooltip?: string; + /** Disables slider */ disabled?: boolean; @@ -106,6 +109,7 @@ const RangeSliderComponent = (props: RangeSliderComponentProps) => { labelText, labelTextColor, labelTextSize, + labelTooltip, labelWidth, loading, marks, @@ -314,6 +318,7 @@ const RangeSliderComponent = (props: RangeSliderComponentProps) => { disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + helpText={labelTooltip} loading={loading} position={labelPosition} text={labelText} diff --git a/app/client/src/widgets/RangeSliderWidget/index.ts b/app/client/src/widgets/RangeSliderWidget/index.ts index a8d0b2e22b..494c14cf05 100644 --- a/app/client/src/widgets/RangeSliderWidget/index.ts +++ b/app/client/src/widgets/RangeSliderWidget/index.ts @@ -31,7 +31,7 @@ export const CONFIG = { labelWidth: 8, labelTextSize: "0.875rem", rows: 8, - columns: 38, + columns: 40, widgetName: "RangeSlider", shouldScroll: false, shouldTruncate: false, @@ -45,6 +45,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/RangeSliderWidget/validations.ts b/app/client/src/widgets/RangeSliderWidget/validations.ts index 693a52829b..71ac255598 100644 --- a/app/client/src/widgets/RangeSliderWidget/validations.ts +++ b/app/client/src/widgets/RangeSliderWidget/validations.ts @@ -168,7 +168,7 @@ export function startValueValidation( return { isValid: false, parsed: undefined, - messages: ["This value must be greater than min value"], + messages: ["This value must be greater than or equal to the min value"], }; } @@ -216,7 +216,7 @@ export function endValueValidation( return { isValid: false, parsed: undefined, - messages: ["This value must be less than max value"], + messages: ["This value must be less than or equal to the max value"], }; } diff --git a/app/client/src/widgets/RangeSliderWidget/widget/index.tsx b/app/client/src/widgets/RangeSliderWidget/widget/index.tsx index fff4658f61..7684fdfc47 100644 --- a/app/client/src/widgets/RangeSliderWidget/widget/index.tsx +++ b/app/client/src/widgets/RangeSliderWidget/widget/index.tsx @@ -9,6 +9,7 @@ import RangeSliderComponent, { import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; +import { Stylesheet } from "entities/AppTheming"; export interface RangeSliderWidgetProps extends WidgetProps, @@ -71,7 +72,7 @@ class RangeSliderWidget extends BaseWidget< } } - static getDefaultPropertiesMap(): Record { + static getDefaultPropertiesMap(): Record { return { start: "defaultStartValue", end: "defaultEndValue", @@ -86,6 +87,12 @@ class RangeSliderWidget extends BaseWidget< }; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + }; + } + onChangeEnd = ([start, end]: [number, number]) => { if (this.props.start !== start) { this.props.updateWidgetMetaProperty("start", start, { @@ -135,9 +142,10 @@ class RangeSliderWidget extends BaseWidget< labelText={this.props.labelText} labelTextColor={this.props.labelTextColor} labelTextSize={this.props.labelTextSize} + labelTooltip={this.props.labelTooltip} labelWidth={this.getLabelWidth()} - loading={this.props.isLoading} // If showMarks is off don't show marks at all + loading={this.props.isLoading} marks={this.props.showMarksLabel ? this.props.marks : []} max={this.props.max} min={this.props.min} diff --git a/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/contentConfig.ts index e46f69c6b5..87c9150f49 100644 --- a/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/contentConfig.ts @@ -1,4 +1,4 @@ -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { ValidationTypes } from "constants/WidgetValidation"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; @@ -161,6 +161,7 @@ export default [ { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Left, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -211,6 +212,16 @@ export default [ { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { propertyName: "showMarksLabel", helpText: "Show the marks label below the slider", @@ -296,7 +307,7 @@ export default [ { propertyName: "tooltipAlwaysOn", helpText: "Keep showing the label with value", - label: "Tooltip Always On", + label: "Show value always", controlType: "SWITCH", isJSConvertible: true, isBindProperty: true, diff --git a/app/client/src/widgets/RateWidget/index.ts b/app/client/src/widgets/RateWidget/index.ts index f7bca27d57..603505b344 100644 --- a/app/client/src/widgets/RateWidget/index.ts +++ b/app/client/src/widgets/RateWidget/index.ts @@ -3,6 +3,12 @@ import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 1, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Rating", iconSVG: IconSVG, @@ -31,6 +37,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/RateWidget/widget/index.tsx b/app/client/src/widgets/RateWidget/widget/index.tsx index 098dd67e05..902ccaf473 100644 --- a/app/client/src/widgets/RateWidget/widget/index.tsx +++ b/app/client/src/widgets/RateWidget/widget/index.tsx @@ -7,7 +7,8 @@ import RateComponent from "../component"; import { ValidationTypes } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { Stylesheet } from "entities/AppTheming"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; function validateDefaultRate(value: unknown, props: any, _: any) { try { @@ -270,6 +271,12 @@ class RateWidget extends BaseWidget { }; } + static getStylesheetConfig(): Stylesheet { + return { + activeColor: "{{appsmith.theme.colors.primaryColor}}", + }; + } + valueChangedHandler = (value: number) => { this.props.updateWidgetMetaProperty("rate", value, { triggerPropertyName: "onRateChanged", diff --git a/app/client/src/widgets/RichTextEditorWidget/component/index.tsx b/app/client/src/widgets/RichTextEditorWidget/component/index.tsx index 670f83a36f..2f10a1934c 100644 --- a/app/client/src/widgets/RichTextEditorWidget/component/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/component/index.tsx @@ -8,8 +8,9 @@ import { TextSize } from "constants/WidgetConstants"; // @ts-expect-error: loader types not available import cssVariables from "!!raw-loader!theme/wds.css"; -import { LabelWithTooltip, labelLayoutStyles } from "design-system"; +import { labelLayoutStyles } from "design-system"; import { isMacOs } from "utils/AppsmithUtils"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; const StyledRTEditor = styled.div<{ borderRadius: string; @@ -18,6 +19,7 @@ const StyledRTEditor = styled.div<{ labelPosition?: LabelPosition; isValid?: boolean; isDisabled?: boolean; + isDynamicHeightEnabled?: boolean; }>` && { width: 100%; @@ -211,12 +213,16 @@ const StyledRTEditor = styled.div<{ export const RichTextEditorInputWrapper = styled.div<{ isValid?: boolean; borderRadius: string; + isDynamicHeightEnabled?: boolean; }>` display: flex; width: 100%; min-width: 0; height: 100%; border-radius: ${({ borderRadius }) => borderRadius}; + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled ? "&& { height: auto; min-height: 192px; }" : ""}; `; export interface RichtextEditorComponentProps { @@ -227,6 +233,7 @@ export interface RichtextEditorComponentProps { isDisabled: boolean; isVisible?: boolean; compactMode: boolean; + isDynamicHeightEnabled: boolean; isToolbarHidden: boolean; borderRadius: string; boxShadow?: string; @@ -241,10 +248,11 @@ export interface RichtextEditorComponentProps { onValueChange: (valueAsString: string) => void; } -export function RichtextEditorComponent(props: RichtextEditorComponentProps) { +function RichtextEditorComponent(props: RichtextEditorComponentProps) { const { compactMode, isDisabled, + isDynamicHeightEnabled, labelAlignment, labelPosition, labelStyle, @@ -258,7 +266,7 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { const initialRender = useRef(true); const toolbarConfig = - "insertfile undo redo | formatselect | bold italic backcolor forecolor | lineheight | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | removeformat | table | print preview media | forecolor backcolor emoticons' |help"; + "insertfile undo redo | formatselect | bold italic underline backcolor forecolor | lineheight | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | removeformat | table | print preview media | emoticons' |help"; const handleEditorChange = useCallback( (newValue: string, editor: any) => { @@ -299,6 +307,7 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { compactMode={compactMode} data-testid="rte-container" isDisabled={props.isDisabled} + isDynamicHeightEnabled={isDynamicHeightEnabled} isValid={props.isValid} labelPosition={labelPosition} > @@ -311,6 +320,7 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { disabled={isDisabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} position={labelPosition} text={labelText} width={labelWidth} @@ -318,13 +328,14 @@ export function RichtextEditorComponent(props: RichtextEditorComponentProps) { )} content of the editor", rows: 20, @@ -33,6 +41,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx index d6867d73d6..ae9e304f4e 100644 --- a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx @@ -9,7 +9,10 @@ import { retryPromise } from "utils/AppsmithUtils"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; + import showdown from "showdown"; +import { Stylesheet } from "entities/AppTheming"; export enum RTEFormats { MARKDOWN = "markdown", @@ -88,6 +91,7 @@ class RichTextEditorWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -323,6 +327,13 @@ class RichTextEditorWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + static getMetaPropertiesMap(): Record { return { text: undefined, @@ -384,6 +395,7 @@ class RichTextEditorWidget extends BaseWidget< ) } isDisabled={this.props.isDisabled} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isMarkdown={this.props.inputType === RTEFormats.MARKDOWN} isToolbarHidden={!!this.props.isToolbarHidden} isValid={this.props.isValid} diff --git a/app/client/src/widgets/SelectWidget/component/index.styled.tsx b/app/client/src/widgets/SelectWidget/component/index.styled.tsx index 63a69ee3ad..9a25c6af7c 100644 --- a/app/client/src/widgets/SelectWidget/component/index.styled.tsx +++ b/app/client/src/widgets/SelectWidget/component/index.styled.tsx @@ -19,9 +19,9 @@ export const StyledDiv = styled.div` `; export const StyledControlGroup = styled(ControlGroup)<{ - compactMode: boolean; - labelPosition?: LabelPosition; - isDisabled?: boolean; + $compactMode: boolean; + $labelPosition?: LabelPosition; + $isDisabled?: boolean; }>` &&& > { span { @@ -38,12 +38,12 @@ export const StyledControlGroup = styled(ControlGroup)<{ fill: var(--wds-color-icon); path { - fill: ${({ isDisabled }) => - isDisabled + fill: ${({ $isDisabled }) => + $isDisabled ? "var(--wds-color-icon-disabled)" : "var(--wds-color-icon)"}; - stroke: ${({ isDisabled }) => - isDisabled + stroke: ${({ $isDisabled }) => + $isDisabled ? "var(--wds-color-icon-disabled)" : "var(--wds-color-icon)"} !important; } @@ -58,8 +58,8 @@ export const StyledControlGroup = styled(ControlGroup)<{ height: 20px; path { - fill: ${({ isDisabled }) => - isDisabled + fill: ${({ $isDisabled }) => + $isDisabled ? "var(--wds-color-icon-disabled)" : "var(--wds-color-icon)"}; } diff --git a/app/client/src/widgets/SelectWidget/component/index.tsx b/app/client/src/widgets/SelectWidget/component/index.tsx index 664b51b7ea..56d13d430e 100644 --- a/app/client/src/widgets/SelectWidget/component/index.tsx +++ b/app/client/src/widgets/SelectWidget/component/index.tsx @@ -21,8 +21,8 @@ import { import { WidgetContainerDiff } from "widgets/WidgetUtils"; import { LabelPosition } from "components/constants"; import SelectButton from "./SelectButton"; -import { LabelWithTooltip } from "design-system"; import { labelMargin } from "../../WidgetUtils"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; const DEBOUNCE_TIMEOUT = 800; const ITEM_SIZE = 40; @@ -259,6 +259,7 @@ class SelectComponent extends React.Component< boxShadow, compactMode, disabled, + isDynamicHeightEnabled, isLoading, labelAlignment, labelPosition, @@ -324,6 +325,7 @@ class SelectComponent extends React.Component< disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={isLoading} position={labelPosition} ref={this.labelRef} @@ -332,10 +334,10 @@ class SelectComponent extends React.Component< /> )} { { label: "Top", value: LabelPosition.Top }, { label: "Auto", value: LabelPosition.Auto }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -500,6 +503,14 @@ class SelectWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getDefaultPropertiesMap(): Record { return { value: "defaultOptionValue", @@ -571,6 +582,7 @@ class SelectWidget extends BaseWidget { filterText={this.props.filterText} hasError={isInvalid} height={componentHeight} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isFilterable={this.props.isFilterable} isLoading={this.props.isLoading} isValid={this.props.isValid} diff --git a/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx index 968c9410a6..c66408fd07 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/component/index.tsx @@ -22,10 +22,11 @@ import styled from "styled-components"; import { RenderMode, TextSize } from "constants/WidgetConstants"; import { Alignment, Button, Classes, InputGroup } from "@blueprintjs/core"; import { labelMargin, WidgetContainerDiff } from "widgets/WidgetUtils"; -import { Icon, LabelWithTooltip } from "design-system"; +import { Icon } from "design-system"; import { Colors } from "constants/Colors"; import { LabelPosition } from "components/constants"; import useDropdown from "widgets/useDropdown"; +import LabelWithTooltip from "widgets/components/LabelWithTooltip"; import { isNil } from "lodash"; export interface TreeSelectProps @@ -49,6 +50,7 @@ export interface TreeSelectProps dropDownWidth: number; width: number; isValid: boolean; + isDynamicHeightEnabled: boolean; borderRadius: string; boxShadow?: string; accentColor: string; @@ -109,6 +111,7 @@ function SingleSelectTreeComponent({ dropDownWidth, expandAll, filterText, + isDynamicHeightEnabled, isFilterable, isValid, labelAlignment, @@ -254,6 +257,7 @@ function SingleSelectTreeComponent({ disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + isDynamicHeightEnabled={isDynamicHeightEnabled} loading={loading} position={labelPosition} ref={labelRef} diff --git a/app/client/src/widgets/SingleSelectTreeWidget/index.ts b/app/client/src/widgets/SingleSelectTreeWidget/index.ts index 23afb6e49b..0216cfd0fb 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/index.ts +++ b/app/client/src/widgets/SingleSelectTreeWidget/index.ts @@ -1,9 +1,17 @@ import { Alignment } from "@blueprintjs/core"; import { LabelPosition } from "components/constants"; +import { DynamicHeight } from "utils/WidgetFeatures"; import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + defaultValue: DynamicHeight.FIXED, + active: true, + }, + }, type: Widget.getWidgetType(), name: "TreeSelect", searchTags: ["dropdown"], @@ -53,6 +61,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx index cd8cd29bcf..05a80d5f3b 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx @@ -10,12 +10,14 @@ import { import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; import { DefaultValueType } from "rc-tree-select/lib/interface"; import { Layers } from "constants/Layers"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { GRID_DENSITY_MIGRATION_V1, MinimumPopupRows } from "widgets/constants"; import SingleSelectTreeComponent from "../component"; import { LabelPosition } from "components/constants"; import { Alignment } from "@blueprintjs/core"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import derivedProperties from "./parseDerivedProperties"; +import { Stylesheet } from "entities/AppTheming"; function defaultOptionValueValidation(value: unknown): ValidationResponse { if (typeof value === "string") return { isValid: true, parsed: value.trim() }; @@ -153,6 +155,7 @@ class SingleSelectTreeWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -299,6 +302,14 @@ class SingleSelectTreeWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }; + } + static getPropertyPaneStyleConfig() { return [ { @@ -479,6 +490,7 @@ class SingleSelectTreeWidget extends BaseWidget< zIndex: Layers.dropdownModalWidget, }} expandAll={this.props.expandAll} + isDynamicHeightEnabled={isAutoHeightEnabledForWidget(this.props)} isFilterable isValid={!isInvalid} labelAlignment={this.props.labelAlignment} diff --git a/app/client/src/widgets/StatboxWidget/index.ts b/app/client/src/widgets/StatboxWidget/index.ts index 52dde8e217..1f9467b56b 100644 --- a/app/client/src/widgets/StatboxWidget/index.ts +++ b/app/client/src/widgets/StatboxWidget/index.ts @@ -5,6 +5,12 @@ import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 0, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Stats Box", iconSVG: IconSVG, @@ -18,6 +24,7 @@ export const CONFIG = { backgroundColor: "white", borderWidth: "1", borderColor: Colors.GREY_5, + minDynamicHeight: 14, children: [], blueprint: { view: [ @@ -111,6 +118,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/StatboxWidget/widget/index.tsx b/app/client/src/widgets/StatboxWidget/widget/index.tsx index e9a1bb0f2b..7f50804a71 100644 --- a/app/client/src/widgets/StatboxWidget/widget/index.tsx +++ b/app/client/src/widgets/StatboxWidget/widget/index.tsx @@ -2,6 +2,7 @@ import { WidgetType } from "constants/WidgetConstants"; import ContainerWidget from "widgets/ContainerWidget"; import { ValidationTypes } from "constants/WidgetValidation"; +import { Stylesheet } from "entities/AppTheming"; class StatboxWidget extends ContainerWidget { static getPropertyPaneContentConfig() { @@ -112,6 +113,13 @@ class StatboxWidget extends ContainerWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + }; + } + static getWidgetType(): WidgetType { return "STATBOX_WIDGET"; } diff --git a/app/client/src/widgets/SwitchGroupWidget/component/index.tsx b/app/client/src/widgets/SwitchGroupWidget/component/index.tsx index 4091e4b905..75bbb2e8a0 100644 --- a/app/client/src/widgets/SwitchGroupWidget/component/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/component/index.tsx @@ -1,16 +1,14 @@ import React from "react"; import styled from "styled-components"; import { Alignment } from "@blueprintjs/core"; - import { BlueprintRadioSwitchGroupTransform } from "constants/DefaultTheme"; import { LabelPosition } from "components/constants"; import { TextSize } from "constants/WidgetConstants"; -import { - LabelWithTooltip, +import { StyledSwitch } from "widgets/SwitchWidget/component"; +import LabelWithTooltip, { labelLayoutStyles, LABEL_CONTAINER_CLASS, -} from "design-system"; -import { StyledSwitch } from "widgets/SwitchWidget/component"; +} from "widgets/components/LabelWithTooltip"; import { ThemeProp } from "widgets/constants"; export interface SwitchGroupContainerProps { @@ -21,6 +19,7 @@ export interface SwitchGroupContainerProps { export const SwitchGroupContainer = styled.div` ${labelLayoutStyles} & .${LABEL_CONTAINER_CLASS} { + align-self: center; ${({ labelPosition }) => labelPosition === LabelPosition.Left && "min-height: 30px"}; } @@ -34,11 +33,12 @@ export interface InputContainerProps { labelPosition?: LabelPosition; optionCount: number; valid?: boolean; + isDynamicHeightEnabled?: boolean; } export const InputContainer = styled.div` - ${BlueprintRadioSwitchGroupTransform} - height: ${({ inline }) => (inline ? "32px" : "100%")}; + ${BlueprintRadioSwitchGroupTransform}; + border: 1px solid transparent; ${({ theme, valid }) => !valid && @@ -60,12 +60,14 @@ function SwitchGroupComponent(props: SwitchGroupComponentProps) { disabled, height, inline, + isDynamicHeightEnabled, labelAlignment, labelPosition, labelStyle, labelText, labelTextColor, labelTextSize, + labelTooltip, labelWidth, onChange, options, @@ -90,7 +92,9 @@ function SwitchGroupComponent(props: SwitchGroupComponentProps) { disabled={disabled} fontSize={labelTextSize} fontStyle={labelStyle} + helpText={labelTooltip} inline={inline} + isDynamicHeightEnabled={isDynamicHeightEnabled} optionCount={optionCount} position={labelPosition} text={labelText} @@ -102,6 +106,7 @@ function SwitchGroupComponent(props: SwitchGroupComponentProps) { compactMode={compactMode} height={height} inline={inline} + isDynamicHeightEnabled={isDynamicHeightEnabled} labelPosition={labelPosition} optionCount={optionCount} valid={valid} @@ -110,7 +115,7 @@ function SwitchGroupComponent(props: SwitchGroupComponentProps) { options.length > 0 && options.map((option: OptionProps) => ( React.FormEventHandler; required: boolean; @@ -142,6 +148,7 @@ export interface SwitchGroupComponentProps { labelTextSize?: TextSize; labelStyle?: string; labelWidth?: number; + labelTooltip?: string; widgetId: string; height: number; accentColor: string; diff --git a/app/client/src/widgets/SwitchGroupWidget/index.ts b/app/client/src/widgets/SwitchGroupWidget/index.ts index 5088b1e791..c57a97ddea 100644 --- a/app/client/src/widgets/SwitchGroupWidget/index.ts +++ b/app/client/src/widgets/SwitchGroupWidget/index.ts @@ -4,6 +4,12 @@ import IconSVG from "./icon.svg"; import Widget from "./widget"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 3, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Switch Group", // The display name which will be made in uppercase and show in the widgets panel ( can have spaces ) iconSVG: IconSVG, @@ -39,6 +45,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx index 3ca71c5092..7311bace94 100644 --- a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -1,17 +1,17 @@ import React from "react"; import { Alignment } from "@blueprintjs/core"; -import { xor } from "lodash"; - +import { isString, xor } from "lodash"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { ValidationTypes } from "constants/WidgetValidation"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; - -import SwitchGroupComponent, { OptionProps } from "../component"; import { LabelPosition } from "components/constants"; import { TextSize } from "constants/WidgetConstants"; import { GRID_DENSITY_MIGRATION_V1 } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; +import SwitchGroupComponent, { OptionProps } from "../component"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; class SwitchGroupWidget extends BaseWidget< SwitchGroupWidgetProps, @@ -109,6 +109,7 @@ class SwitchGroupWidget extends BaseWidget< { label: "Left", value: LabelPosition.Left }, { label: "Top", value: LabelPosition.Top }, ], + defaultValue: LabelPosition.Top, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -175,6 +176,16 @@ class SwitchGroupWidget extends BaseWidget< { sectionName: "General", children: [ + { + helpText: "Show help text or details about current input", + propertyName: "labelTooltip", + label: "Tooltip", + controlType: "INPUT_TEXT", + placeholderText: "Value must be atleast 6 chars", + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + }, { propertyName: "isVisible", helpText: "Controls the visibility of the widget", @@ -359,6 +370,12 @@ class SwitchGroupWidget extends BaseWidget< ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + }; + } + static getDefaultPropertiesMap(): Record { return { selectedValuesArray: "defaultSelectedValues", @@ -419,6 +436,7 @@ class SwitchGroupWidget extends BaseWidget< labelText, labelTextColor, labelTextSize, + labelTooltip, options, selectedValues, topRow, @@ -427,6 +445,15 @@ class SwitchGroupWidget extends BaseWidget< const { componentHeight } = this.getComponentDimensions(); + // TODO(abhinav): Not sure why we have to do this. + // Check with the App Viewers Pod + let _options = options; + if (isString(options)) { + try { + _options = JSON.parse(options as string); + } catch (e) {} + } + return ( ` width: 100%; display: inline-block; @@ -52,43 +54,43 @@ const SwitchLabel = styled.div<{ labelStyle?.includes(FontStyleTypes.ITALIC) ? "italic" : "normal" }; `} + + ${({ isDynamicHeightEnabled }) => + isDynamicHeightEnabled ? "&& { word-break: break-all; }" : ""}; `; export const StyledSwitch = styled(Switch)<{ - accentColor: string; + $accentColor: string; inline?: boolean; }>` - &.${Classes.CONTROL} { - margin: 0; - } - &.${Classes.CONTROL} { & input:checked ~ .${Classes.CONTROL_INDICATOR} { - background: ${({ accentColor }) => `${accentColor}`} !important; - border: 1px solid ${({ accentColor }) => `${accentColor}`} !important; + background: ${({ $accentColor }) => `${$accentColor}`} !important; + border: 1px solid ${({ $accentColor }) => `${$accentColor}`} !important; } &:hover input:checked:not(:disabled) ~ .bp3-control-indicator { - background: ${({ accentColor }) => - `${darkenColor(accentColor)}`} !important; - border: 1px solid ${({ accentColor }) => - `${darkenColor(accentColor)}`} !important; + background: ${({ $accentColor }) => + `${darkenColor($accentColor)}`} !important; + border: 1px solid ${({ $accentColor }) => + `${darkenColor($accentColor)}`} !important; } } &.${Classes.SWITCH} { ${({ inline }) => (!!inline ? "" : "width: 100%;")} & input:not(:disabled):active:checked ~ .${Classes.CONTROL_INDICATOR} { - background: ${({ accentColor }) => `${accentColor}`} !important; + background: ${({ $accentColor }) => `${$accentColor}`} !important; } } `; -export default function SwitchComponent({ +function SwitchComponent({ accentColor, alignWidget = AlignWidgetTypes.LEFT, inputRef, isDisabled, + isDynamicHeightEnabled, isLoading, isSwitchedOn, label, @@ -97,18 +99,14 @@ export default function SwitchComponent({ labelTextColor, labelTextSize, onChange, -}: SwitchComponentProps) { - /** - * When the label position is left align switch to the right - * When the label position is right align switch to the left - */ +}: SwitchComponentProps): JSX.Element { const switchAlignClass = labelPosition === LabelPosition.Right ? "left" : "right"; return ( ); } + +export default SwitchComponent; diff --git a/app/client/src/widgets/SwitchWidget/index.ts b/app/client/src/widgets/SwitchWidget/index.ts index 0a7db42f33..2893e5d170 100644 --- a/app/client/src/widgets/SwitchWidget/index.ts +++ b/app/client/src/widgets/SwitchWidget/index.ts @@ -4,6 +4,12 @@ import { LabelPosition } from "components/constants"; import { AlignWidgetTypes } from "widgets/constants"; export const CONFIG = { + features: { + dynamicHeight: { + sectionIndex: 1, + active: true, + }, + }, type: Widget.getWidgetType(), name: "Switch", iconSVG: IconSVG, @@ -28,6 +34,7 @@ export const CONFIG = { config: Widget.getPropertyPaneConfig(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/SwitchWidget/widget/index.tsx b/app/client/src/widgets/SwitchWidget/widget/index.tsx index a61f7e85cb..5f1213667c 100644 --- a/app/client/src/widgets/SwitchWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchWidget/widget/index.tsx @@ -7,8 +7,12 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; + import { LabelPosition } from "components/constants"; import { AlignWidgetTypes } from "widgets/constants"; +import { Stylesheet } from "entities/AppTheming"; +import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; + class SwitchWidget extends BaseWidget { static getPropertyPaneContentConfig() { return [ @@ -35,6 +39,7 @@ class SwitchWidget extends BaseWidget { { label: "Left", value: LabelPosition.Left }, { label: "Right", value: LabelPosition.Right }, ], + defaultValue: LabelPosition.Left, isBindProperty: false, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT }, @@ -227,12 +232,20 @@ class SwitchWidget extends BaseWidget { ]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + boxShadow: "none", + }; + } + getPageView() { return ( props.data, [props.data]); const columnString = JSON.stringify({ columns: props.columns, diff --git a/app/client/src/widgets/TableWidget/component/index.tsx b/app/client/src/widgets/TableWidget/component/index.tsx index 08f6c73db0..3a57604613 100644 --- a/app/client/src/widgets/TableWidget/component/index.tsx +++ b/app/client/src/widgets/TableWidget/component/index.tsx @@ -347,3 +347,5 @@ export default React.memo(ReactTableComponent, (prev, next) => { JSON.stringify(prev.columns) === JSON.stringify(next.columns) ); }); + +ReactTableComponent.displayName = "ReactTableComponent"; diff --git a/app/client/src/widgets/TableWidget/index.ts b/app/client/src/widgets/TableWidget/index.ts index b8e205c767..c656ca46ce 100644 --- a/app/client/src/widgets/TableWidget/index.ts +++ b/app/client/src/widgets/TableWidget/index.ts @@ -213,6 +213,7 @@ export const CONFIG = { default: Widget.getDefaultPropertiesMap(), meta: Widget.getMetaPropertiesMap(), config: Widget.getPropertyPaneConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), }, }; diff --git a/app/client/src/widgets/TableWidget/widget/helpers.ts b/app/client/src/widgets/TableWidget/widget/helpers.ts index 180a70d12f..8297dfa94b 100644 --- a/app/client/src/widgets/TableWidget/widget/helpers.ts +++ b/app/client/src/widgets/TableWidget/widget/helpers.ts @@ -1,10 +1,10 @@ -import { AppTheme } from "entities/AppTheming"; import { TableWidgetProps } from "../constants"; import { get } from "lodash"; import { combineDynamicBindings, getDynamicBindings, } from "utils/DynamicBindingUtils"; +import { Stylesheet } from "entities/AppTheming"; /** * this is a getter function to get stylesheet value of the property from the config @@ -17,7 +17,7 @@ import { export const getStylesheetValue = ( props: TableWidgetProps, propertyPath: string, - widgetStylesheet?: AppTheme["stylesheet"][string], + widgetStylesheet?: Stylesheet, ) => { const propertyName = propertyPath.split(".").slice(-1)[0]; const columnName = propertyPath.split(".").slice(-2)[0]; @@ -37,7 +37,7 @@ export const getStylesheetValue = ( export const getPrimaryColumnStylesheetValue = ( props: TableWidgetProps, propertyPath: string, - widgetStylesheet?: AppTheme["stylesheet"][string], + widgetStylesheet?: Stylesheet, ) => { const propertyName = propertyPath.split(".").slice(-1)[0]; const columnName = propertyPath.split(".").slice(-2)[0]; diff --git a/app/client/src/widgets/TableWidget/widget/index.tsx b/app/client/src/widgets/TableWidget/widget/index.tsx index ca4b5a45e0..8fd30dcb17 100644 --- a/app/client/src/widgets/TableWidget/widget/index.tsx +++ b/app/client/src/widgets/TableWidget/widget/index.tsx @@ -53,6 +53,7 @@ import { getCellProperties } from "./getTableColumns"; import { Colors } from "constants/Colors"; import { borderRadiusUtility, boxShadowMigration } from "widgets/WidgetUtils"; import { ButtonVariantTypes } from "components/constants"; +import { Stylesheet } from "entities/AppTheming"; const ReactTableComponent = lazy(() => retryPromise(() => import("../component")), @@ -74,6 +75,31 @@ class TableWidget extends BaseWidget { return tablePropertyPaneConfig; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + childStylesheet: { + button: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + menuButton: { + menuColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + iconButton: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + }, + }; + } + static getMetaPropertiesMap(): Record { return { pageNo: 1, diff --git a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts index 5e0f74b977..f4e362221a 100644 --- a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts @@ -2,7 +2,7 @@ import { get } from "lodash"; import { TableWidgetProps } from "../constants"; import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { ButtonVariantTypes } from "components/constants"; import { diff --git a/app/client/src/widgets/TableWidgetV2/index.ts b/app/client/src/widgets/TableWidgetV2/index.ts index f288de09a6..c8830a73cd 100644 --- a/app/client/src/widgets/TableWidgetV2/index.ts +++ b/app/client/src/widgets/TableWidgetV2/index.ts @@ -234,6 +234,7 @@ export const CONFIG = { meta: Widget.getMetaPropertiesMap(), contentConfig: Widget.getPropertyPaneContentConfig(), styleConfig: Widget.getPropertyPaneStyleConfig(), + stylesheetConfig: Widget.getStylesheetConfig(), loadingProperties: Widget.getLoadingProperties(), }, }; diff --git a/app/client/src/widgets/TableWidgetV2/widget/derived.js b/app/client/src/widgets/TableWidgetV2/widget/derived.js index b6004c5938..cbcd0189e0 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/derived.js +++ b/app/client/src/widgets/TableWidgetV2/widget/derived.js @@ -550,6 +550,54 @@ export default { return finalTableData; }, // + getUpdatedRow: (props, moment, _) => { + let index = -1; + const parsedUpdatedRowIndex = parseInt(props.updatedRowIndex); + + if (!_.isNaN(parsedUpdatedRowIndex)) { + index = parsedUpdatedRowIndex; + } + + const rows = props.filteredTableData || props.processedTableData || []; + const primaryColumns = props.primaryColumns; + let updatedRow; + + if (index > -1) { + const row = rows.find((row) => row.__originalIndex__ === index); + updatedRow = { ...row }; + } else { + /* + * If updatedRowIndex is not a valid index, updatedRow should + * have proper row structure with empty string values + */ + updatedRow = {}; + if (rows && rows[0]) { + Object.keys(rows[0]).forEach((key) => { + updatedRow[key] = ""; + }); + } + } + + const nonDataColumnTypes = [ + "editActions", + "button", + "iconButton", + "menuButton", + ]; + const nonDataColumnAliases = primaryColumns + ? Object.values(primaryColumns) + .filter((column) => nonDataColumnTypes.includes(column.columnType)) + .map((column) => column.alias) + : []; + + const keysToBeOmitted = [ + "__originalIndex__", + "__primaryKey__", + ...nonDataColumnAliases, + ]; + return _.omit(updatedRow, keysToBeOmitted); + }, + // getUpdatedRows: (props, moment, _) => { const primaryColumns = props.primaryColumns; const nonDataColumnTypes = [ @@ -773,4 +821,17 @@ export default { return validationMap; }, // + getTableHeaders: (props, moment, _) => { + const columns = props.primaryColumns + ? Object.values(props.primaryColumns) + : []; + return columns + .sort((a, b) => a.index - b.index) + .map((column) => ({ + id: column?.id, + label: column?.label, + isVisible: column?.isVisible, + })); + }, + // }; diff --git a/app/client/src/widgets/TableWidgetV2/widget/derived.test.js b/app/client/src/widgets/TableWidgetV2/widget/derived.test.js index 8fbee5499f..c1a4061c6d 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/derived.test.js +++ b/app/client/src/widgets/TableWidgetV2/widget/derived.test.js @@ -2069,1051 +2069,1449 @@ describe("getPageOffset -", () => { }); }); +describe("validate getUpdatedRow", () => { + it("should check that valid updated row index returns the valid value", () => { + const { getUpdatedRow } = derivedProperty; + const input1 = { + updatedRowIndex: 1, + processedTableData: [ + { id: 1234, name: "Jim Doe", extra: "", __originalIndex__: 0 }, + { id: 234, name: "Jane Doe", extra: "Extra2", __originalIndex__: 2 }, + { id: 123, name: "John Doe1", extra: "Extra1", __originalIndex__: 1 }, + ], + }; + const input2 = { + updatedRowIndex: 0, + processedTableData: [ + { id: 1, name: "Lorem Ipsum", extra: "", __originalIndex__: 0 }, + { id: 234, name: "Jane Doe", extra: "Extra2", __originalIndex__: 2 }, + { id: 123, name: "John Doe", extra: "Extra1", __originalIndex__: 1 }, + ], + } + expect(getUpdatedRow(input1, moment, _)).toStrictEqual({ + id: 123, + name: "John Doe1", + extra: "Extra1", + }); + expect(getUpdatedRow(input2, moment, _)).toStrictEqual({ + id: 1, + name: "Lorem Ipsum", + extra: "", + }); + }); + + it("should check that it returns empty values when updateRowIndex is invalid or -1", () => { + const { getUpdatedRow } = derivedProperty; + const input1 = { + updatedRowIndex: -1, + processedTableData: [ + { id: 1, name: "Lorem Ipsum", extra: "", __originalIndex__: 0 }, + { id: 234, name: "Jane Doe", extra: "Extra2", __originalIndex__: 2 }, + { id: 123, name: "John Doe", extra: "Extra1", __originalIndex__: 1 }, + ], + }; + const input2 = { + updatedRowIndex: "dummyIndex", + processedTableData: [ + { id: 1, name: "Lorem Ipsum", extra: "", __originalIndex__: 0 }, + { id: 234, name: "Jane Doe", extra: "Extra2", __originalIndex__: 2 }, + { id: 123, name: "John Doe", extra: "Extra1", __originalIndex__: 1 }, + ], + }; + + const input3 = { + updatedRowIndex: undefined, + processedTableData: [ + { id: 1, name: "Lorem Ipsum", extra: "", __originalIndex__: 0 }, + { id: 234, name: "Jane Doe", extra: "Extra2", __originalIndex__: 2 }, + { id: 123, name: "John Doe", extra: "Extra1", __originalIndex__: 1 }, + ], + }; + expect(getUpdatedRow(input1, moment, _)).toStrictEqual({ + id: "", + name: "", + extra: "", + }); + + expect(getUpdatedRow(input2, moment, _)).toStrictEqual({ + id: "", + name: "", + extra: "", + }); + + expect(getUpdatedRow(input3, moment, _)).toStrictEqual({ + id: "", + name: "", + extra: "", + }); + }); + + it("should check that it removes non data columns", () => { + const { getUpdatedRow } = derivedProperty; + const input = { + updatedRowIndex: 1, + processedTableData: sampleProcessedTableData, + primaryColumns: samplePrimaryColumns, + }; + + expect(getUpdatedRow(input, moment, _)).toStrictEqual({ + step: "#2", + task: "Create a query fetch_users with the Mock DB", + status: "--", + }); + }); +}) describe("getEditableCellValidity", () => { const { getEditableCellValidity } = derivedProperty; it("should test that its returns empty object when editableCell is empty and isAddRowInProgess is false", () => { expect( - getEditableCellValidity({ - editableCell: {}, - isAddRowInProgress: false, - }, null, _), + getEditableCellValidity( + { + editableCell: {}, + isAddRowInProgress: false, + }, + null, + _, + ), ).toEqual({}); }); describe("should test that it validates the editableColumn against all the validation properties", () => { it("should return true for editable column when validation is empty", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "" + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: {}, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: {} - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "123" + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "123", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: {}, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: {} - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return true for editable column when isColumnEditableCellRequired is off and there is no value", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "" + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: undefined, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: undefined, + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: null, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: null, + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return true for editable column when isColumnEditableCellValid is true", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: null, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: null, + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: true, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable column when isColumnEditableCellValid is false", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "test", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "test", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: false, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable column when regex is matching", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + regex: "^#1$", + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - regex: "^#1$" - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "test", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "test", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + regex: "^test$", + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - regex: "^test$" - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable column when regex is not matching", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "test", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "test", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + regex: "^#1$", + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - regex: "^#1$" - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + regex: "^test$", + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - regex: "^test$" - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return false for editable column when isColumnEditableCellRequired is true and there is no value", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable column when isColumnEditableCellRequired and there is value", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "test", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "test", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return true for editable column when value is above min", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: 1, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: 1, + }, + primaryColumns: { + step: { + columnType: "number", + alias: "step", + validation: { + min: 0, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "number", - alias: "step", - validation: { - min: 0 - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable column when value is below min", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: -1, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: -1, + }, + primaryColumns: { + step: { + columnType: "number", + alias: "step", + validation: { + min: 0, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "number", - alias: "step", - validation: { - min: 0 - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable column when value is below max", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: 2, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: 2, + }, + primaryColumns: { + step: { + columnType: "number", + alias: "step", + validation: { + max: 5, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "number", - alias: "step", - validation: { - max: 5 - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable column when value is above max", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: 6, + getEditableCellValidity( + { + editableCell: { + column: "step", + value: 6, + }, + primaryColumns: { + step: { + columnType: "number", + alias: "step", + validation: { + max: 5, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "number", - alias: "step", - validation: { - max: 5 - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable column when value is matching all the validation criteria", () => { expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: false, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: false, - regex: "^#1$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#1$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#1$", + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#1$", - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#2$", + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#2$", - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - editableCell: { - column: "step", - value: "#1", + getEditableCellValidity( + { + editableCell: { + column: "step", + value: "#1", + }, + primaryColumns: { + step: { + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#2$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#2$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); }); describe("should test that it validates the new row against all the validation properties", () => { it("should check that only editable columns are present in the validation object", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: {} + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - columnType: "text", - alias: "task", - validation: {} - } - } - }, null, _), - ).toEqual({step: true}); + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: {}, + }, + task: { + columnType: "text", + alias: "task", + validation: {}, + }, + }, + }, + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - columnType: "text", - alias: "step", - validation: {} + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - isEditable: true, - columnType: "text", - alias: "task", - validation: {} - } - } - }, null, _), - ).toEqual({task: true}); + primaryColumns: { + step: { + columnType: "text", + alias: "step", + validation: {}, + }, + task: { + isEditable: true, + columnType: "text", + alias: "task", + validation: {}, + }, + }, + }, + null, + _, + ), + ).toEqual({ task: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: {} + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - isEditable: true, - columnType: "text", - alias: "task", - validation: {} - } - } - }, null, _), - ).toEqual({step: true, task: true}); + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: {}, + }, + task: { + isEditable: true, + columnType: "text", + alias: "task", + validation: {}, + }, + }, + }, + null, + _, + ), + ).toEqual({ step: true, task: true }); }); - + it("should return true for editable columns when validation is empty", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: {} + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - isEditable: true, - columnType: "text", - alias: "task", - validation: {} - } - } - }, null, _), - ).toEqual({step: true, task: true}); + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: {}, + }, + task: { + isEditable: true, + columnType: "text", + alias: "task", + validation: {}, + }, + }, + }, + null, + _, + ), + ).toEqual({ step: true, task: true }); }); it("should return true for editable columns when isColumnEditableCellRequired is off and there is no value", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": undefined, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: undefined, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": null, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: null, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return true for editable columns when isColumnEditableCellValid is true", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": null, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: null, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: true, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable columns when isColumnEditableCellValid is false", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "test", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "test", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable columns when regex is matching", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + regex: "^#1$", + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - regex: "^#1$" - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "test", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "test", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + regex: "^test$", + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - regex: "^test$" - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable columns when regex is not matching", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "test", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "test", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + regex: "^#1$", + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - regex: "^#1$" - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + regex: "^test$", + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - regex: "^test$" - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return false for editable columns when isColumnEditableCellRequired is true and there is no value", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable columns when isColumnEditableCellRequired and there is value", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "test", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "test", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return true for editable columns when value is above min", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": 1, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: 1, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "number", + alias: "step", + validation: { + min: 0, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "number", - alias: "step", - validation: { - min: 0 - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable columns when value is below min", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": -1, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: -1, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "number", + alias: "step", + validation: { + min: 0, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "number", - alias: "step", - validation: { - min: 0 - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable columns when value is below max", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": 2, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: 2, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "number", + alias: "step", + validation: { + max: 5, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "number", - alias: "step", - validation: { - max: 5 - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); }); it("should return false for editable columns when value is above max", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": 6, + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: 6, + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "number", + alias: "step", + validation: { + max: 5, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "number", - alias: "step", - validation: { - max: 5 - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should return true for editable columns when value is matching all the validation criteria", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: false, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: false, - regex: "^#1$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#1$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#1$", + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#1$", - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: true}); + null, + _, + ), + ).toEqual({ step: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#2$", + isColumnEditableCellRequired: true, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#2$", - isColumnEditableCellRequired: true - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + }, + primaryColumns: { + step: { + isEditable: true, + alias: "step", + columnType: "text", + validation: { + isColumnEditableCellValid: true, + regex: "^#2$", + isColumnEditableCellRequired: false, + }, + }, + }, }, - primaryColumns: { - step: { - isEditable: true, - alias: "step", - columnType: "text", - validation: { - isColumnEditableCellValid: true, - regex: "^#2$", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: false}); + null, + _, + ), + ).toEqual({ step: false }); }); it("should check that more than one column is validated at the same time", () => { expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: false, - regex: "^#1$", - isColumnEditableCellRequired: false - } + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - isEditable: true, - columnType: "text", - alias: "task", - validation: { - isColumnEditableCellValid: true, - regex: "test", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: false, task: true}); + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: false, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + task: { + isEditable: true, + columnType: "text", + alias: "task", + validation: { + isColumnEditableCellValid: true, + regex: "test", + isColumnEditableCellRequired: false, + }, + }, + }, + }, + null, + _, + ), + ).toEqual({ step: false, task: true }); expect( - getEditableCellValidity({ - isAddRowInProgress: true, - editableCell: {}, - newRow: { - "step": "#1", - "task": "test" - }, - primaryColumns: { - step: { - isEditable: true, - columnType: "text", - alias: "step", - validation: { - isColumnEditableCellValid: true, - regex: "^#1$", - isColumnEditableCellRequired: false - } + getEditableCellValidity( + { + isAddRowInProgress: true, + editableCell: {}, + newRow: { + step: "#1", + task: "test", }, - task: { - isEditable: true, - columnType: "text", - alias: "task", - validation: { - isColumnEditableCellValid: true, - regex: "test", - isColumnEditableCellRequired: false - } - } - } - }, null, _), - ).toEqual({step: true, task: true}); + primaryColumns: { + step: { + isEditable: true, + columnType: "text", + alias: "step", + validation: { + isColumnEditableCellValid: true, + regex: "^#1$", + isColumnEditableCellRequired: false, + }, + }, + task: { + isEditable: true, + columnType: "text", + alias: "task", + validation: { + isColumnEditableCellValid: true, + regex: "test", + isColumnEditableCellRequired: false, + }, + }, + }, + }, + null, + _, + ), + ).toEqual({ step: true, task: true }); }); }); }); + +describe("Validate tableHeaders function", () => { + const { getTableHeaders } = derivedProperty; + + it("should test that it returns empty array when primaryColumns is undefined", () => { + expect( + getTableHeaders({ + primaryColumns: undefined, + }), + ).toEqual([]); + }); + + it("should test that it returns expected array when primaryColumns value is undefined", () => { + expect( + getTableHeaders({ + primaryColumns: { + "some value": undefined, + }, + }), + ).toEqual([ + { + id: undefined, + label: undefined, + isVisible: undefined, + }, + ]); + }); + + it("should test that it returns expected array when primaryColumns data is undefined", () => { + expect( + getTableHeaders({ + primaryColumns: { + "some value": { + id: "some value", + label: undefined, + isVisible: true, + }, + }, + }), + ).toEqual([ + { + id: "some value", + label: undefined, + isVisible: true, + }, + ]); + }); + + it("should test that it returns expected array with the same length as that of primaryColumns", () => { + expect( + getTableHeaders({ + primaryColumns: { + "some value": { + id: "some value", + label: "some value", + isVisible: true, + }, + "some other value": { + id: "some other value", + label: "some other value", + isVisible: true, + }, + }, + }).length, + ).toEqual(2); + }); + + it("should test that it returns expected array sorted", () => { + expect( + getTableHeaders({ + primaryColumns: { + "value 02": { + id: "value 02", + label: "value 02", + isVisible: true, + index: "2", + }, + "value 03": { + id: "value 03", + label: "value 03", + isVisible: true, + index: "3", + }, + "value 01": { + id: "value 01", + label: "value 01", + isVisible: true, + index: "1", + }, + }, + }), + ).toEqual([ + { + id: "value 01", + label: "value 01", + isVisible: true, + }, + { + id: "value 02", + label: "value 02", + isVisible: true, + }, + { + id: "value 03", + label: "value 03", + isVisible: true, + }, + ]); + }); +}); diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index ef64706a17..d8b0516b23 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -84,6 +84,7 @@ import { CheckboxCell } from "../component/cellComponents/CheckboxCell"; import { SwitchCell } from "../component/cellComponents/SwitchCell"; import { SelectCell } from "../component/cellComponents/SelectCell"; import { CellWrapper } from "../component/TableStyledWrappers"; +import { Stylesheet } from "entities/AppTheming"; const ReactTableComponent = lazy(() => retryPromise(() => import("../component")), @@ -121,6 +122,7 @@ class TableWidgetV2 extends BaseWidget { order: null, }, transientTableData: {}, + updatedRowIndex: -1, editableCell: defaultEditableCell, columnEditableCellValue: {}, selectColumnFilterText: {}, @@ -142,9 +144,10 @@ class TableWidgetV2 extends BaseWidget { filteredTableData: `{{(()=>{ ${derivedProperties.getFilteredTableData}})()}}`, updatedRows: `{{(()=>{ ${derivedProperties.getUpdatedRows}})()}}`, updatedRowIndices: `{{(()=>{ ${derivedProperties.getUpdatedRowIndices}})()}}`, - updatedRow: `{{this.triggeredRow}}`, + updatedRow: `{{(()=>{ ${derivedProperties.getUpdatedRow}})()}}`, pageOffset: `{{(()=>{${derivedProperties.getPageOffset}})()}}`, isEditableCellsValid: `{{(()=>{ ${derivedProperties.getEditableCellValidity}})()}}`, + tableHeaders: `{{(()=>{${derivedProperties.getTableHeaders}})()}}`, }; } @@ -160,6 +163,38 @@ class TableWidgetV2 extends BaseWidget { return [/\.tableData$/]; } + static getStylesheetConfig(): Stylesheet { + return { + accentColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}", + childStylesheet: { + button: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + menuButton: { + menuColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + iconButton: { + buttonColor: "{{appsmith.theme.colors.primaryColor}}", + borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + boxShadow: "none", + }, + editActions: { + saveButtonColor: "{{appsmith.theme.colors.primaryColor}}", + saveBorderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}", + discardButtonColor: "{{appsmith.theme.colors.primaryColor}}", + discardBorderRadius: + "{{appsmith.theme.borderRadius.appBorderRadius}}", + }, + }, + }; + } + /* * Function to get the table columns with appropriate render functions * based on columnType @@ -595,6 +630,9 @@ class TableWidgetV2 extends BaseWidget { */ if (isTableDataModified) { this.props.updateWidgetMetaProperty("transientTableData", {}); + // reset updatedRowIndex whenever transientTableData is flushed. + this.props.updateWidgetMetaProperty("updatedRowIndex", -1); + this.clearEditableCell(true); this.props.updateWidgetMetaProperty("selectColumnFilterText", {}); } @@ -1208,6 +1246,8 @@ class TableWidgetV2 extends BaseWidget { ...transientData, }, }); + + this.props.updateWidgetMetaProperty("updatedRowIndex", __originalIndex__); }; removeRowFromTransientTableData = (index: number) => { @@ -1221,6 +1261,7 @@ class TableWidgetV2 extends BaseWidget { newTransientTableData, ); } + this.props.updateWidgetMetaProperty("updatedRowIndex", -1); }; getRowOriginalIndex = (index: number) => { diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/ColumnControl.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/ColumnControl.ts index 15a88b8616..15d8956358 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/ColumnControl.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/ColumnControl.ts @@ -15,7 +15,7 @@ import { updateNumberColumnTypeTextAlignment, updateThemeStylesheetsInColumns, } from "../../propertyUtils"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { isColumnTypeEditable } from "../../utilities"; import { composePropertyUpdateHook } from "widgets/WidgetUtils"; diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts index 2629f2a5d9..6828f21bde 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts @@ -13,7 +13,7 @@ import { updateNumberColumnTypeTextAlignment, updateThemeStylesheetsInColumns, } from "../../propertyUtils"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { composePropertyUpdateHook } from "widgets/WidgetUtils"; export default { diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts index fa67ed2865..c857f8d6e6 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/contentConfig.ts @@ -1,6 +1,6 @@ import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; import { InlineEditingSaveOptions, TableWidgetProps, diff --git a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts index cf5ca20323..5b8f8c8ff4 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts @@ -16,7 +16,6 @@ import { ORIGINAL_INDEX_KEY, } from "../constants"; import { SelectColumnOptionsValidations } from "./propertyUtils"; -import { AppTheme } from "entities/AppTheming"; import { TableWidgetProps } from "../constants"; import { get } from "lodash"; import { getNextEntityName } from "utils/AppsmithUtils"; @@ -27,6 +26,7 @@ import { import { ButtonVariantTypes } from "components/constants"; import { dateFormatOptions } from "widgets/constants"; import moment from "moment"; +import { Stylesheet } from "entities/AppTheming"; type TableData = Array>; @@ -513,7 +513,7 @@ export const getSelectedRowBgColor = (accentColor: string) => { export const getStylesheetValue = ( props: TableWidgetProps, propertyPath: string, - widgetStylesheet?: AppTheme["stylesheet"][string], + widgetStylesheet?: Stylesheet, ) => { const propertyName = propertyPath.split(".").slice(-1)[0]; const columnName = propertyPath.split(".").slice(-2)[0]; diff --git a/app/client/src/widgets/TabsMigrator/widget/index.tsx b/app/client/src/widgets/TabsMigrator/widget/index.tsx index 2c03dff878..0b5193d620 100644 --- a/app/client/src/widgets/TabsMigrator/widget/index.tsx +++ b/app/client/src/widgets/TabsMigrator/widget/index.tsx @@ -10,7 +10,7 @@ import { cloneDeep, get } from "lodash"; import { ValidationTypes } from "constants/WidgetValidation"; import { generateReactKey } from "utils/generators"; import { EVAL_VALUE_PATH } from "utils/DynamicBindingUtils"; -import { AutocompleteDataType } from "utils/autocomplete/TernServer"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; class TabsMigratorWidget extends BaseWidget< TabsWidgetProps, diff --git a/app/client/src/widgets/TabsWidget/component/index.tsx b/app/client/src/widgets/TabsWidget/component/index.tsx index ef48b23078..dba9d090cb 100644 --- a/app/client/src/widgets/TabsWidget/component/index.tsx +++ b/app/client/src/widgets/TabsWidget/component/index.tsx @@ -42,21 +42,6 @@ const TAB_CONTAINER_HEIGHT = "44px"; const CHILDREN_WRAPPER_HEIGHT_WITH_TABS = `calc(100% - ${TAB_CONTAINER_HEIGHT})`; const CHILDREN_WRAPPER_HEIGHT_WITHOUT_TABS = "100%"; -// const scrollNavControlContainerBaseStyle = css` -// display: flex; -// position: absolute; -// top: 0; -// bottom: 0; -// z-index: 2; -// background: white; - -// button { -// z-index: 1; -// border-radius: 0px; -// border-bottom: ${(props) => `1px solid ${props.theme.colors.bodyBG}`}; -// } -// `; - const scrollContents = css` overflow-y: auto; position: absolute; @@ -111,39 +96,6 @@ export interface TabsContainerProps { isScrollable: boolean; } -// const TabsContainer = styled.div` -// position: absolute; -// top: 0; -// overflow-x: auto; -// overflow-y: hidden; -// display: flex; -// height: ${TAB_CONTAINER_HEIGHT}; -// background: ${(props) => props.theme.colors.builderBodyBG}; -// overflow: hidden; -// border-bottom: ${(props) => `1px solid ${props.theme.colors.bodyBG}`}; - -// overflow-x: scroll; -// &::-webkit-scrollbar { -// display: none; -// } -// /* Hide scrollbar for IE, Edge and Firefox */ -// -ms-overflow-style: none; /* IE and Edge */ -// scrollbar-width: none; /* Firefox */ - -// && { -// width: 100%; -// display: flex; -// justify-content: flex-start; -// align-items: flex-end; -// } -// `; - -// type TabProps = { -// selected?: boolean; -// onClick: (e: React.MouseEvent) => void; -// primaryColor: string; -// }; - const Container = styled.div` width: 100%; align-items: flex-end; @@ -194,19 +146,6 @@ export interface ScrollNavControlProps { className?: string; } -// function ScrollNavControl(props: ScrollNavControlProps) { -// const { className, disabled, icon, onClick } = props; -// return ( -//