From 283ed52354d07e348efbc79aa68a6ad81ddff640 Mon Sep 17 00:00:00 2001 From: Yukti Goyal Date: Mon, 3 Mar 2025 14:06:51 +0530 Subject: [PATCH 001/297] added create user cases --- .../platform/externalApi/apiUsers.cy.js | 383 ++++++++++++++++++ cypress-tests/cypress/support/utils/api.js | 24 ++ 2 files changed, 407 insertions(+) create mode 100644 cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js create mode 100644 cypress-tests/cypress/support/utils/api.js diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js new file mode 100644 index 0000000000..7e836be426 --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js @@ -0,0 +1,383 @@ +import { fake } from "Fixtures/fake"; +import { createUser, getUser, updateUser } from 'Support/utils/api'; +import { commonSelectors } from 'Selectors/common'; +import { searchUser, navigateToManageUsers, logout, navigateToManageGroups } from 'Support/utils/common'; + +describe("API Test", () => { + const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, ""); + const data = { + firstName: fake.firstName, + lastName: fake.lastName, + firstName1: fake.firstName, + lastName1: fake.lastName, + email: sanitize(fake.email), + email1: sanitize(fake.email), + workspaceName: sanitize(fake.lastName), + workspaceSlug: sanitize(fake.lastName), + workspaceName1: sanitize(fake.firstName), + workspaceSlug1: sanitize(fake.firstName) + }; + it("should create a new user and verify", () => { + cy.defaultWorkspaceLogin(); + /* const userData = { + name: `${data.firstName} ${data.lastName}`, + email: data.email, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "all_users" }] + } + ] + }; + + createUser(userData).then((response) => { + expect(response.status).to.eq(201); + cy.defaultWorkspaceLogin(); + navigateToManageUsers(); + searchUser(data.email); + cy.contains("td", data.email) + .parent() + .within(() => { + cy.get("td small").should("have.text", "active"); + }); + cy.logoutApi(); + cy.apiLogin(data.email, "password"); + cy.visit("/my-workspace"); + cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); + logout(); + + cy.defaultWorkspaceLogin(); + cy.getCookie("tj_auth_token").then((cookie) => { + cy.request({ + method: "GET", + url: `${Cypress.env('API_URL')}/users/all?page=1&searchText=${data.email}&status=`, + headers: { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }, + }).then((response) => { + expect(response.status).to.eq(200); + const userId = response.body.users[0].id; + + getUser(userId).then((response) => { + expect(response.status).to.eq(200); + expect(response.body).to.have.property("name", `${data.firstName} ${data.lastName}`); + expect(response.body).to.have.property("email", data.email); + }); + + const updatedUserData = { + name: `${data.lastName} ${data.firstName}`, + email: data.email2, + }; + + updateUser(userId, updatedUserData).then((response) => { + expect(response.status).to.eq(200); + navigateToManageUsers(); + searchUser(data.email2); + cy.contains("td", data.email2) + .parent() + .within(() => { + cy.get("td small").should("have.text", "active"); + }); + cy.logoutApi(); + cy.apiLogin(data.email2, "password"); + cy.visit("/my-workspace"); + cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); + logout(); + }); + }); + }); + });*/ + }); + + it("should handle negative cases", () => { + const invalidUserId = "1d8a92b1-4925-4fbf-tool-0jet45d98487"; + const invalidAuthToken = "Basic invalidAuthToken"; + + cy.request({ + method: "GET", + url: `${Cypress.env('API_URL')}/ext/user/${invalidUserId}`, + headers: { + Authorization: invalidAuthToken, + "Content-Type": "application/json", + }, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(403); + expect(response.body.message).to.eq("Unauthorized"); + }); + + cy.request({ + method: "POST", + url: `${Cypress.env('API_URL')}/ext/users`, + body: { + name: `${data.lastName} ${data.firstName}`, + email: `${data.email2}`, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "all_users" }], + }, + ], + }, + headers: { + Authorization: Cypress.env('AUTH_TOKEN'), + "Content-Type": "application/json", + }, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(422); + expect(response.body.message).to.eq("Already exists!"); + }); + + cy.request({ + method: "GET", + url: `${Cypress.env('API_URL')}/ext/user/nonExistingUserId`, + headers: { + Authorization: Cypress.env('AUTH_TOKEN'), + "Content-Type": "application/json", + }, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(422); + expect(response.body.message).to.contain("invalid input syntax for type uuid"); + }); + + cy.request({ + method: "POST", + url: `${Cypress.env('API_URL')}/ext/users`, + body: { + name: `${data.firstName} ${data.lastName}`, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "all_users" }], + }, + ], + }, + headers: { + Authorization: Cypress.env('AUTH_TOKEN'), + "Content-Type": "application/json", + }, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message).to.deep.equal(["email must be an email"]); + }); + + cy.request({ + method: "GET", + url: `${Cypress.env('API_URL')}/users/all`, + failOnStatusCode: false, + }).then((response) => { + expect(response.status).to.eq(401); + expect(response.body.message).to.eq("Unauthorized"); + }); + }); + + it.only("create user", () => { + + cy.defaultWorkspaceLogin(); + navigateToManageGroups(); + + const createGroup = (groupName) => { + cy.get(groupsSelector.createNewGroupButton).click(); + cy.clearAndType(groupsSelector.groupNameInput, groupName); + cy.get(groupsSelector.createGroupButton).click(); + } + ["group1", "group2"].forEach(createGroup); + + [ + { name: data.workspaceName, slug: data.workspaceSlug, group: "ws1group1" }, + { name: data.workspaceName1, slug: data.workspaceSlug1, group: "ws2group2" } + ].forEach(({ name, slug, group }) => { + cy.apiCreateWorkspace(name, slug); + cy.visit(slug); + navigateToManageGroups(); + createGroup(group); + }); + + + //create user with all valid details + const userData = { + name: `${data.firstName} ${data.lastName}`, + email: data.email, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [ + { name: "group1" }, + { name: "group2" } + ] + }, + { + name: data.workspaceName, + status: "active", + role: "builder", + groups: [{ name: "ws1group1" }] + }, + { + name: data.workspaceName1, + status: "archived", + role: "admin", + groups: [{ name: "ws2group2" }] + } + ] + }; + + // Added valid user and logged-in in the workpsace + createUser(userData).then((response) => { + expect(response.status).to.eq(201); + cy.defaultWorkspaceLogin(); + navigateToManageUsers(); + searchUser(data.email); + cy.contains("td", data.email) + .parent() + .within(() => { + cy.get("td small").should("have.text", "active"); + }); + + cy.get(commonSelectors.manageGroupsOption).click(); + cy.get(groupsSelector.groupLink("end-user")).click(); + cy.get(groupsSelector.usersLink).click(); + cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); + + cy.visit(data.workspaceSlug); + navigateToManageGroups(); + cy.get(groupsSelector.groupLink("builder")).click(); + cy.get(groupsSelector.usersLink).click(); + cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); + + cy.visit(data.workspaceSlug1); + navigateToManageGroups(); + cy.get(groupsSelector.groupLink("admin")).click(); + cy.get(groupsSelector.usersLink).click(); + cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); + + cy.logoutApi(); + + cy.apiLogin(data.email, "password"); + cy.visit("/my-workspace"); + cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); + logout(); + + //add user with invalid data and verify error + // const data = { + // firstName1: fake.firstName, + // lastName1: fake.lastName, + // }; + + cy.defaultWorkspaceLogin(); + userData = { + name: `${data.firstName} ${data.lastName}`, + email: data.email, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + } + ] + } + createUser(userData).then((response) => { + expect(response.status).to.eq(422); + expect(response.body.message).to.eq("Already exists!"); + }); + + userData = { + name: `${data.firstName1} ${data.lastName1}`, + email: "test@tooljet.com1", + password: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the test", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "group1" }] + } + ] + }; + + createUser(userData).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message).to.eq("email must be an email", + "password must be shorter than or equal to 100 characters"); + }) + + //create and add user in non existing group and non existing workspace + userData = { + name: `${data.firstName1} ${data.lastName1}`, + email: "test@tooljet.com", + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "Group1" }] + } + ] + }; + createUser(userData).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message).to.eq("Group permission id or name not found: id undefined, name Group1"); + }); + + userData = { + name: `${data.firstName1} ${data.lastName1}`, + email: "test@tooljet.com", + password: "password", + status: "active", + workspaces: [ + { + name: "testws", + status: "active" + } + ] + }; + createUser(userData).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message).to.eq("The workspaces id or name do not exist: id undefined, name testws"); + }); + + + + //conflict permission + userData = { + name: `${data.firstName1} ${data.lastName1}`, + email: `${data.email1}`, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [{ name: "builder groups" }] + } + ] + }; + navigateToManageGroups(); + createGroup("builder groups"); + cy.get(groupsSelector.groupLink("builder groups")).click(); + cy.get(groupsSelector.permissionsLink).click(); + cy.get(groupsSelector.appsCreateCheck).check(); + createUser(userData).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message).to.eq("End-users can only be granted permission to view apps. Kindly change the user role or custom group to continue."); + }) + }) + }) +}); \ No newline at end of file diff --git a/cypress-tests/cypress/support/utils/api.js b/cypress-tests/cypress/support/utils/api.js new file mode 100644 index 0000000000..3feb609b8e --- /dev/null +++ b/cypress-tests/cypress/support/utils/api.js @@ -0,0 +1,24 @@ +export const apiRequest = (method, url, body = {}, headers = {}) => { + return cy.request({ + method, + url, + body, + headers: { + Authorization: Cypress.env('AUTH_TOKEN'), + "Content-Type": "application/json", + ...headers, + }, + }); +}; + +export const createUser = (userData) => { + return apiRequest("POST", `${Cypress.env('API_URL')}/ext/users`, userData); +}; + +export const getUser = (userId) => { + return apiRequest("GET", `${Cypress.env('API_URL')}/ext/user/${userId}`); +}; + +export const updateUser = (userId, userData) => { + return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}`, userData); +}; From c4b5ce499ddee694b3d3bc3b542372f9af0775a3 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 13 Feb 2025 15:40:51 +0530 Subject: [PATCH 002/297] Added shortcuts to run and preview a query in query panel. --- .../AppBuilder/QueryPanel/QueryKeyHooks.jsx | 48 +++++++++++++++++++ .../src/AppBuilder/QueryPanel/QueryPanel.jsx | 5 +- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx diff --git a/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx b/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx new file mode 100644 index 0000000000..06d8958cab --- /dev/null +++ b/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; + +const QueryKeyHooks = ({ children, isExpanded }) => { + const runQuery = useStore((state) => state.queryPanel.runQuery); + const selectedQuery = useStore((state) => state.queryPanel.selectedQuery); + const moduleId = useModuleId(); + const previewQuery = useStore((state) => state.queryPanel.previewQuery); + const selectedDataSource = useStore((state) => state.queryPanel.selectedDataSource); + const queryName = selectedQuery?.name ?? ''; + + const previewButtonOnClick = () => { + const _options = { ...selectedQuery.options }; + const query = { + data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id, + pluginId: selectedDataSource.pluginId, + options: _options, + kind: selectedDataSource.kind, + name: queryName, + id: selectedQuery?.id, + }; + previewQuery(query, false, undefined, moduleId).catch(({ error, data }) => { + console.log(error, data); + }); + }; + + const shortcutRef = useHotkeys( + ['mod+enter', 'mod+shift+enter'], + (event, handler) => { + if (handler.mod && handler.keys[0] === 'enter') { + if (handler.shift) { + previewButtonOnClick(); + } else runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true); + } + }, + { enabled: isExpanded } + ); + + return ( +
+ {children} +
+ ); +}; + +export default QueryKeyHooks; diff --git a/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx b/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx index 827efe6d33..8bf5d2ea53 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryPanel.jsx @@ -11,6 +11,7 @@ import useStore from '@/AppBuilder/_stores/store'; import SectionCollapse from '@/_ui/Icon/solidIcons/SectionCollapse'; import SectionExpand from '@/_ui/Icon/solidIcons/SectionExpand'; import { shallow } from 'zustand/shallow'; +import QueryKeyHooks from './QueryKeyHooks'; const MemoizedQueryDataPane = memo(QueryDataPane); const MemoizedQueryManager = memo(QueryManager); @@ -192,14 +193,14 @@ export const QueryPanel = ({ darkMode }) => { }} > {isExpanded && ( -
+
-
+ )} From d083420616f7f3ce89cb7dbf5a96ada01f3b8682 Mon Sep 17 00:00:00 2001 From: Yukti Goyal Date: Fri, 7 Mar 2025 11:13:04 +0530 Subject: [PATCH 003/297] added few cases --- .../workspace/groups/permissions.cy.js | 2 - .../platform/externalApi/apiUsers.cy.js | 503 ++++++------------ cypress-tests/cypress/support/utils/api.js | 20 + 3 files changed, 174 insertions(+), 351 deletions(-) diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js index 8fefd2bb5c..9ce1736842 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js @@ -522,10 +522,8 @@ describe("Manage Groups", () => { commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion) ).click(); cy.exec("ls ./cypress/downloads/").then((result) => { - cy.log(result); const downloadedAppExportFileName = result.stdout.split("\n")[0]; exportedFilePath = `cypress/downloads/${downloadedAppExportFileName}`; - cy.log(exportedFilePath); cy.get(importSelectors.dropDownMenu).should("be.visible").click(); cy.get(importSelectors.importOptionInput).selectFile(exportedFilePath, { force: true, diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js index 7e836be426..41deec86c4 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js @@ -1,204 +1,78 @@ import { fake } from "Fixtures/fake"; -import { createUser, getUser, updateUser } from 'Support/utils/api'; +import { createUser, getAllUsers, getUser, updateUser, createGroup, validateUserInGroup } from 'Support/utils/api'; +import { groupsSelector } from "Selectors/manageGroups"; import { commonSelectors } from 'Selectors/common'; import { searchUser, navigateToManageUsers, logout, navigateToManageGroups } from 'Support/utils/common'; describe("API Test", () => { + const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, ""); + let userId; const data = { firstName: fake.firstName, lastName: fake.lastName, firstName1: fake.firstName, lastName1: fake.lastName, - email: sanitize(fake.email), - email1: sanitize(fake.email), + email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + email1: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), workspaceName: sanitize(fake.lastName), workspaceSlug: sanitize(fake.lastName), workspaceName1: sanitize(fake.firstName), - workspaceSlug1: sanitize(fake.firstName) + workspaceSlug1: sanitize(fake.firstName), + group1: sanitize(fake.firstName), + group2: sanitize(fake.firstName), + group3: sanitize(fake.firstName), + group4: sanitize(fake.firstName), + group5: sanitize(fake.firstName) }; - it("should create a new user and verify", () => { + + //user with all valid details + const userData = { + name: `${data.firstName} ${data.lastName}`, + email: data.email, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [ + { name: data.group1 }, + { name: data.group2 } + ] + }, + { + name: data.workspaceName, + status: "active", + role: "builder", + groups: [{ name: data.group3 }] + }, + { + name: data.workspaceName1, + status: "archived", + role: "admin", + groups: [{ name: data.group4 }] + } + ] + }; + + beforeEach(() => { cy.defaultWorkspaceLogin(); - /* const userData = { - name: `${data.firstName} ${data.lastName}`, - email: data.email, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "all_users" }] - } - ] - }; - - createUser(userData).then((response) => { - expect(response.status).to.eq(201); - cy.defaultWorkspaceLogin(); - navigateToManageUsers(); - searchUser(data.email); - cy.contains("td", data.email) - .parent() - .within(() => { - cy.get("td small").should("have.text", "active"); - }); - cy.logoutApi(); - cy.apiLogin(data.email, "password"); - cy.visit("/my-workspace"); - cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); - logout(); - - cy.defaultWorkspaceLogin(); - cy.getCookie("tj_auth_token").then((cookie) => { - cy.request({ - method: "GET", - url: `${Cypress.env('API_URL')}/users/all?page=1&searchText=${data.email}&status=`, - headers: { - "Tj-Workspace-Id": Cypress.env("workspaceId"), - Cookie: `tj_auth_token=${cookie.value}`, - }, - }).then((response) => { - expect(response.status).to.eq(200); - const userId = response.body.users[0].id; - - getUser(userId).then((response) => { - expect(response.status).to.eq(200); - expect(response.body).to.have.property("name", `${data.firstName} ${data.lastName}`); - expect(response.body).to.have.property("email", data.email); - }); - - const updatedUserData = { - name: `${data.lastName} ${data.firstName}`, - email: data.email2, - }; - - updateUser(userId, updatedUserData).then((response) => { - expect(response.status).to.eq(200); - navigateToManageUsers(); - searchUser(data.email2); - cy.contains("td", data.email2) - .parent() - .within(() => { - cy.get("td small").should("have.text", "active"); - }); - cy.logoutApi(); - cy.apiLogin(data.email2, "password"); - cy.visit("/my-workspace"); - cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); - logout(); - }); - }); - }); - });*/ }); - it("should handle negative cases", () => { - const invalidUserId = "1d8a92b1-4925-4fbf-tool-0jet45d98487"; - const invalidAuthToken = "Basic invalidAuthToken"; - - cy.request({ - method: "GET", - url: `${Cypress.env('API_URL')}/ext/user/${invalidUserId}`, - headers: { - Authorization: invalidAuthToken, - "Content-Type": "application/json", - }, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(403); - expect(response.body.message).to.eq("Unauthorized"); - }); - - cy.request({ - method: "POST", - url: `${Cypress.env('API_URL')}/ext/users`, - body: { - name: `${data.lastName} ${data.firstName}`, - email: `${data.email2}`, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "all_users" }], - }, - ], - }, - headers: { - Authorization: Cypress.env('AUTH_TOKEN'), - "Content-Type": "application/json", - }, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(422); - expect(response.body.message).to.eq("Already exists!"); - }); - - cy.request({ - method: "GET", - url: `${Cypress.env('API_URL')}/ext/user/nonExistingUserId`, - headers: { - Authorization: Cypress.env('AUTH_TOKEN'), - "Content-Type": "application/json", - }, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(422); - expect(response.body.message).to.contain("invalid input syntax for type uuid"); - }); - - cy.request({ - method: "POST", - url: `${Cypress.env('API_URL')}/ext/users`, - body: { - name: `${data.firstName} ${data.lastName}`, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "all_users" }], - }, - ], - }, - headers: { - Authorization: Cypress.env('AUTH_TOKEN'), - "Content-Type": "application/json", - }, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(400); - expect(response.body.message).to.deep.equal(["email must be an email"]); - }); - - cy.request({ - method: "GET", - url: `${Cypress.env('API_URL')}/users/all`, - failOnStatusCode: false, - }).then((response) => { - expect(response.status).to.eq(401); - expect(response.body.message).to.eq("Unauthorized"); - }); - }); - - it.only("create user", () => { - - cy.defaultWorkspaceLogin(); + it("Create user with valid details", () => { + // create multiple groups in different workspaces navigateToManageGroups(); + [data.group1, data.group2, data.group5].forEach(createGroup); - const createGroup = (groupName) => { - cy.get(groupsSelector.createNewGroupButton).click(); - cy.clearAndType(groupsSelector.groupNameInput, groupName); - cy.get(groupsSelector.createGroupButton).click(); - } - ["group1", "group2"].forEach(createGroup); + //builder group + cy.get(groupsSelector.groupLink(data.group5)).click(); + cy.get(groupsSelector.permissionsLink).click(); + cy.get(groupsSelector.appsCreateCheck).check(); [ - { name: data.workspaceName, slug: data.workspaceSlug, group: "ws1group1" }, - { name: data.workspaceName1, slug: data.workspaceSlug1, group: "ws2group2" } + { name: data.workspaceName, slug: data.workspaceSlug, group: data.group3 }, + { name: data.workspaceName1, slug: data.workspaceSlug1, group: data.group4 } ].forEach(({ name, slug, group }) => { cy.apiCreateWorkspace(name, slug); cy.visit(slug); @@ -206,178 +80,109 @@ describe("API Test", () => { createGroup(group); }); - - //create user with all valid details - const userData = { - name: `${data.firstName} ${data.lastName}`, - email: data.email, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [ - { name: "group1" }, - { name: "group2" } - ] - }, - { - name: data.workspaceName, - status: "active", - role: "builder", - groups: [{ name: "ws1group1" }] - }, - { - name: data.workspaceName1, - status: "archived", - role: "admin", - groups: [{ name: "ws2group2" }] - } - ] - }; - // Added valid user and logged-in in the workpsace + cy.visit("/my-workspace"); + cy.wait(500); createUser(userData).then((response) => { expect(response.status).to.eq(201); - cy.defaultWorkspaceLogin(); - navigateToManageUsers(); - searchUser(data.email); - cy.contains("td", data.email) - .parent() - .within(() => { - cy.get("td small").should("have.text", "active"); - }); - - cy.get(commonSelectors.manageGroupsOption).click(); - cy.get(groupsSelector.groupLink("end-user")).click(); - cy.get(groupsSelector.usersLink).click(); - cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); - - cy.visit(data.workspaceSlug); - navigateToManageGroups(); - cy.get(groupsSelector.groupLink("builder")).click(); - cy.get(groupsSelector.usersLink).click(); - cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); - - cy.visit(data.workspaceSlug1); - navigateToManageGroups(); - cy.get(groupsSelector.groupLink("admin")).click(); - cy.get(groupsSelector.usersLink).click(); - cy.get(`[data-cy="${data.email}-user-row"]`).should("exist"); - - cy.logoutApi(); - - cy.apiLogin(data.email, "password"); - cy.visit("/my-workspace"); - cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); - logout(); - - //add user with invalid data and verify error - // const data = { - // firstName1: fake.firstName, - // lastName1: fake.lastName, - // }; - - cy.defaultWorkspaceLogin(); - userData = { - name: `${data.firstName} ${data.lastName}`, - email: data.email, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - } - ] - } - createUser(userData).then((response) => { - expect(response.status).to.eq(422); - expect(response.body.message).to.eq("Already exists!"); - }); - - userData = { - name: `${data.firstName1} ${data.lastName1}`, - email: "test@tooljet.com1", - password: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the test", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "group1" }] - } - ] - }; - - createUser(userData).then((response) => { - expect(response.status).to.eq(400); - expect(response.body.message).to.eq("email must be an email", - "password must be shorter than or equal to 100 characters"); - }) - - //create and add user in non existing group and non existing workspace - userData = { - name: `${data.firstName1} ${data.lastName1}`, - email: "test@tooljet.com", - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "Group1" }] - } - ] - }; - createUser(userData).then((response) => { - expect(response.status).to.eq(400); - expect(response.body.message).to.eq("Group permission id or name not found: id undefined, name Group1"); - }); - - userData = { - name: `${data.firstName1} ${data.lastName1}`, - email: "test@tooljet.com", - password: "password", - status: "active", - workspaces: [ - { - name: "testws", - status: "active" - } - ] - }; - createUser(userData).then((response) => { - expect(response.status).to.eq(400); - expect(response.body.message).to.eq("The workspaces id or name do not exist: id undefined, name testws"); - }); - - - - //conflict permission - userData = { - name: `${data.firstName1} ${data.lastName1}`, - email: `${data.email1}`, - password: "password", - status: "active", - workspaces: [ - { - name: "My workspace", - status: "active", - groups: [{ name: "builder groups" }] - } - ] - }; - navigateToManageGroups(); - createGroup("builder groups"); - cy.get(groupsSelector.groupLink("builder groups")).click(); - cy.get(groupsSelector.permissionsLink).click(); - cy.get(groupsSelector.appsCreateCheck).check(); - createUser(userData).then((response) => { - expect(response.status).to.eq(400); - expect(response.body.message).to.eq("End-users can only be granted permission to view apps. Kindly change the user role or custom group to continue."); - }) + userId = response.body.id; + /* navigateToManageUsers(); + searchUser(data.email); + cy.contains("td", data.email) + .parent() + .within(() => { + cy.get("td small").should("have.text", "active"); + }); + + validateUserInGroup(data.email, "my-workspace", "end-user"); + validateUserInGroup(data.email, data.workspaceSlug, "builder"); + validateUserInGroup(data.email, data.workspaceSlug1, "admin", false); + cy.apiLogout(); + + cy.apiLogin(data.email, "password"); + cy.visit("/my-workspace"); + cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); + logout();*/ }) }) -}); \ No newline at end of file + + it.skip('Handles user creation errors', () => { + const invalidUserData = [ + { // Duplicate user + data: { ...userData }, + expectedStatus: 422, + expectedMessage: 'Already exists!' + }, + { // Invalid email and long password + data: { + ...userData, + name: `${data.firstName1} ${data.lastName1}`, + email: 'invalid-email', + password: 'a'.repeat(101) + }, + expectedStatus: 400, + expectedMessages: ['email must be an email', 'password must be shorter than or equal to 100 characters'] + }, + { // Non-existing group + data: { + ...userData, + name: `${data.firstName1} ${data.lastName1}`, + email: `${data.email1}`, + workspaces: [{ name: 'My workspace', status: 'active', groups: [{ name: 'NonExistingGroup' }] }] + }, + expectedStatus: 400, + expectedMessage: 'Group permission id or name not found:' + }, + { // Non-existing workspace + data: { + ...userData, + name: `${data.firstName1} ${data.lastName1}`, + email: `${data.email1}`, + workspaces: [{ name: 'NonExistingWorkspace', status: 'active' }] + }, + expectedStatus: 400, + expectedMessage: 'The workspaces id or name do not exist:' + } + ]; + + invalidUserData.forEach(({ data, expectedStatus, expectedMessages, expectedMessage }) => { + createUser(data).then((response) => { + expect(response.status).to.eq(expectedStatus); + if (expectedMessages) { + expectedMessages.forEach(msg => expect(response.body.message).to.include(msg)); + } else { + expect(response.body.message).to.include(expectedMessage); + } + }); + }); + //Conflict permission + const enduserData = { + ...userData, + name: `${data.firstName1} ${data.lastName1}`, + email: `${data.email1}`, + workspaces: [{ name: 'My workspace', status: 'active', groups: [{ name: data.group5 }] }] + } + createUser(enduserData).then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message.title).to.include("Conflicting permissions"); + }) + }); + + it("Get all users and by user id", () => { + navigateToManageUsers(); + let number = 0; + cy.get('[data-cy="title-users-page"]').invoke('text').then((text) => { + number = parseInt(text.match(/\d+/)[0], 10); + cy.log('Number of users:', number); + }); + getAllUsers().then((response) => { + expect(response.status).to.eq(200); + expect(response.body.length).to.eq(number); + }); + + getUser(userId).then((response) => { + expect(response.status).to.eq(200); + expect(response.body.name).to.eq(`${data.firstName} ${data.lastName}`); + }); + }); +}); diff --git a/cypress-tests/cypress/support/utils/api.js b/cypress-tests/cypress/support/utils/api.js index 3feb609b8e..8532b89bb2 100644 --- a/cypress-tests/cypress/support/utils/api.js +++ b/cypress-tests/cypress/support/utils/api.js @@ -1,3 +1,5 @@ +import { groupsSelector } from "Selectors/manageGroups"; +import { navigateToManageGroups } from 'Support/utils/common'; export const apiRequest = (method, url, body = {}, headers = {}) => { return cy.request({ method, @@ -8,6 +10,7 @@ export const apiRequest = (method, url, body = {}, headers = {}) => { "Content-Type": "application/json", ...headers, }, + failOnStatusCode: false }); }; @@ -19,6 +22,23 @@ export const getUser = (userId) => { return apiRequest("GET", `${Cypress.env('API_URL')}/ext/user/${userId}`); }; +export const getAllUsers = () => { + return apiRequest("GET", `${Cypress.env('API_URL')}/ext/users`); +}; + export const updateUser = (userId, userData) => { return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}`, userData); }; +export const createGroup = (groupName) => { + cy.get(groupsSelector.createNewGroupButton).click(); + cy.clearAndType(groupsSelector.groupNameInput, groupName); + cy.get(groupsSelector.createGroupButton).click(); +} +export const validateUserInGroup = (email, workspaceSlug, groupName, shouldExist = true) => { + if (workspaceSlug) cy.visit(workspaceSlug); + navigateToManageGroups(); + cy.get(groupsSelector.groupLink(groupName)).click(); + cy.get(groupsSelector.usersLink).click(); + const userRow = `[data-cy="${email}-user-row"]`; + cy.get(userRow).should(shouldExist ? "exist" : "not.exist"); +}; \ No newline at end of file From f234384f2528be4e08e05839fd5d1bbd8564a7af Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 7 Mar 2025 15:34:31 +0530 Subject: [PATCH 004/297] Fix: shortcut can be used throughtout the editor and added custom hook in codeeditor for query panel shortcuts. --- .../CodeEditor/MultiLineCodeEditor.jsx | 12 +++- .../CodeEditor/SingleLineCodeEditor.jsx | 10 +++- .../CodeEditor/useQueryPanelKeyHooks.js | 58 +++++++++++++++++++ .../AppBuilder/QueryPanel/QueryKeyHooks.jsx | 36 +++--------- .../_stores/slices/queryPanelSlice.js | 18 ++++++ 5 files changed, 103 insertions(+), 31 deletions(-) create mode 100644 frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index 033d266e03..44259ce3a8 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -21,6 +21,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { search, searchKeymap, searchPanelOpen } from '@codemirror/search'; import { handleSearchPanel, SearchBtn } from './SearchBox'; +import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; const langSupport = Object.freeze({ javascript: javascript(), @@ -64,6 +65,8 @@ const MultiLineCodeEditor = (props) => { const [editorView, setEditorView] = React.useState(null); + const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline'); + const handleOnBlur = () => { if (!delayOnChange) return onChange(currentValueRef.current); setTimeout(() => { @@ -85,6 +88,7 @@ const MultiLineCodeEditor = (props) => { highlightActiveLine: false, autocompletion: hideSuggestion ?? true, highlightActiveLineGutter: false, + defaultKeymap: false, completionKeymap: true, searchKeymap: false, }; @@ -187,7 +191,12 @@ const MultiLineCodeEditor = (props) => { }; } - const customKeyMaps = [...defaultKeymap, ...completionKeymap, ...searchKeymap]; + const customKeyMaps = [ + ...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter + ...completionKeymap, + ...searchKeymap, + ]; + const customTabKeymap = keymap.of([ { key: 'Tab', @@ -208,6 +217,7 @@ const MultiLineCodeEditor = (props) => { return true; }, }, + ...queryPanelKeybindings, ]); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index 1243f26f43..09489a6e2f 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -22,6 +22,7 @@ import CodeHinter from './CodeHinter'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; +import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { const { initialValue, onChange, enablePreview = true, portalProps } = restProps; @@ -170,6 +171,8 @@ const EditorInput = ({ onInputChange, }) => { const getSuggestions = useStore((state) => state.getSuggestions, shallow); + const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline'); + function autoCompleteExtensionConfig(context) { const hints = getSuggestions(); let word = context.matchBefore(/\w*/); @@ -229,7 +232,10 @@ const EditorInput = ({ maxRenderedOptions: 10, }); - const customKeyMaps = [...defaultKeymap, ...completionKeymap]; + const customKeyMaps = [ + ...defaultKeymap.filter((keyBinding) => keyBinding.key !== 'Mod-Enter'), // Remove default keybinding for Mod-Enter + ...completionKeymap, + ]; const customTabKeymap = keymap.of([ { key: 'Tab', @@ -251,6 +257,7 @@ const EditorInput = ({ } }, }, + ...queryPanelKeybindings, ]); const handleOnChange = React.useCallback((val) => { @@ -395,6 +402,7 @@ const EditorInput = ({ foldGutter: false, highlightActiveLine: false, autocompletion: true, + defaultKeymap: false, completionKeymap: true, searchKeymap: false, }} diff --git a/frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js b/frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js new file mode 100644 index 0000000000..1a41a7f19b --- /dev/null +++ b/frontend/src/AppBuilder/CodeEditor/useQueryPanelKeyHooks.js @@ -0,0 +1,58 @@ +import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import useStore from '@/AppBuilder/_stores/store'; +import { useCallback, useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; + +export const useQueryPanelKeyHooks = (onChange, value, type) => { + const queryPanelHeight = useStore((state) => state.queryPanel.queryPanelHeight); + const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut); + const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut); + const moduleId = useModuleId(); + const location = useLocation(); + const { pathname } = location; + + const [queryPanelKeybindings, setQueryPanelKeybindings] = useState([]); + + const handleRunQuery = useCallback( + (view) => { + const isEditor = pathname.includes('/apps/'); + if (queryPanelHeight !== 0 && isEditor) { + onChange(type === 'multiline' ? value.current : value); + runQueryOnShortcut(); + } + return true; + }, + [queryPanelHeight, onChange, runQueryOnShortcut, value] + ); + + const handlePreviewQuery = useCallback( + (view) => { + const isEditor = pathname.includes('/apps/'); + if (queryPanelHeight !== 0 && isEditor) { + onChange(type === 'multiline' ? value.current : value); + previewQueryOnShortcut(moduleId); + } + return true; + }, + [queryPanelHeight, moduleId, onChange, previewQueryOnShortcut, value] + ); + + useEffect(() => { + setQueryPanelKeybindings([ + { + key: 'Mod-Enter', + preventDefault: true, + run: handleRunQuery, + }, + { + key: 'Mod-Shift-Enter', + preventDefault: true, + run: handlePreviewQuery, + }, + ]); + }, [handleRunQuery, handlePreviewQuery]); + + return { + queryPanelKeybindings, + }; +}; diff --git a/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx b/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx index 06d8958cab..13e29d5531 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryKeyHooks.jsx @@ -4,45 +4,23 @@ import { useHotkeys } from 'react-hotkeys-hook'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; const QueryKeyHooks = ({ children, isExpanded }) => { - const runQuery = useStore((state) => state.queryPanel.runQuery); - const selectedQuery = useStore((state) => state.queryPanel.selectedQuery); + const runQueryOnShortcut = useStore((state) => state.queryPanel.runQueryOnShortcut); + const previewQueryOnShortcut = useStore((state) => state.queryPanel.previewQueryOnShortcut); const moduleId = useModuleId(); - const previewQuery = useStore((state) => state.queryPanel.previewQuery); - const selectedDataSource = useStore((state) => state.queryPanel.selectedDataSource); - const queryName = selectedQuery?.name ?? ''; - const previewButtonOnClick = () => { - const _options = { ...selectedQuery.options }; - const query = { - data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id, - pluginId: selectedDataSource.pluginId, - options: _options, - kind: selectedDataSource.kind, - name: queryName, - id: selectedQuery?.id, - }; - previewQuery(query, false, undefined, moduleId).catch(({ error, data }) => { - console.log(error, data); - }); - }; - - const shortcutRef = useHotkeys( + useHotkeys( ['mod+enter', 'mod+shift+enter'], (event, handler) => { if (handler.mod && handler.keys[0] === 'enter') { if (handler.shift) { - previewButtonOnClick(); - } else runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true); + previewQueryOnShortcut(moduleId); + } else runQueryOnShortcut(); } }, - { enabled: isExpanded } + { enabled: isExpanded, enableOnFormTags: ['input'] } ); - return ( -
- {children} -
- ); + return
{children}
; }; export default QueryKeyHooks; diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index fd49d2a5e4..bc695d3c9b 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -1028,5 +1028,23 @@ export const createQueryPanelSlice = (set, get) => ({ isQuerySelected: (queryId) => { return get().queryPanel.selectedQuery?.id === queryId; }, + runQueryOnShortcut: () => { + const { queryPanel } = get(); + const { runQuery, selectedQuery } = queryPanel; + runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true); + }, + previewQueryOnShortcut: (moduleId = 'canvas') => { + const { queryPanel } = get(); + const { previewQuery, selectedQuery, selectedDataSource } = queryPanel; + const query = { + data_source_id: selectedDataSource.id === 'null' ? null : selectedDataSource.id, + pluginId: selectedDataSource.pluginId, + options: { ...selectedQuery?.options }, + kind: selectedDataSource.kind, + name: selectedQuery?.name ?? '', + id: selectedQuery?.id, + }; + previewQuery(query, false, undefined, moduleId); + }, }, }); From 2da6fdde4a49bf3f2fe593c62ad61badd36ae0f2 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 11 Mar 2025 14:18:32 +0530 Subject: [PATCH 005/297] Added icons for shortcuts in the run and preview button. --- .../QueryManager/Components/QueryManagerHeader.jsx | 13 +++++++++---- frontend/src/_styles/theme.scss | 8 ++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 75cbe9cef7..15eef7c6a6 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -252,7 +252,7 @@ const RunButton = ({ buttonLoadingState }) => { > {isInDraft && } @@ -299,6 +303,7 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => { {t('editor.queryManager.preview', 'Preview')} + ⌘↑↩ ); }; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 2d1f28be3d..f5f2131c59 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -8054,6 +8054,10 @@ tbody { min-width: 22px; } + .query-manager-btn-shortcut { + color: var(--text-disabled) !important; + } + &:hover { background-color: $color-light-indigo-04; color: $color-light-indigo-10; @@ -8142,6 +8146,10 @@ tbody { } + .query-manager-btn-shortcut { + color: var(--text-disabled) !important; + } + &:hover { border: 1px solid $color-light-slate-08; color: $color-light-slate-11; From 41cff2484022aa220a12c6d866a1452645b24c90 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 11 Mar 2025 17:29:15 +0530 Subject: [PATCH 006/297] Replaced run and preview buttons with design system button. --- .../Components/QueryManagerHeader.jsx | 47 +++++++------------ frontend/src/_styles/theme.scss | 12 ++--- frontend/src/_ui/Icon/solidIcons/Play01.jsx | 19 ++++++++ frontend/src/_ui/Icon/solidIcons/index.js | 3 ++ frontend/src/components/ui/Button/Button.jsx | 14 +++++- 5 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 frontend/src/_ui/Icon/solidIcons/Play01.jsx diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 15eef7c6a6..6055c0538a 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -1,7 +1,5 @@ import React, { useState, forwardRef, useRef, useEffect } from 'react'; import RenameIcon from '../Icons/RenameIcon'; -import Eye1 from '@/_ui/Icon/solidIcons/Eye1'; -import Play from '@/_ui/Icon/solidIcons/Play'; import cx from 'classnames'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; @@ -13,6 +11,7 @@ import { decodeEntities } from '@/_helpers/utils'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; import useStore from '@/AppBuilder/_stores/store'; import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext'; +import { Button as ButtonComponent } from '@/components/ui/Button/Button'; export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => { const moduleId = useModuleId(); @@ -250,29 +249,22 @@ const RunButton = ({ buttonLoadingState }) => { 'data-tooltip-content': 'Connect a data source to run', })} > - + Run ⌘↩ + {isInDraft && } ); @@ -291,19 +283,16 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => { const { t } = useTranslation(); return ( - + Preview ⌘↑↩ + ); }; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index f5f2131c59..0eedde77da 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -8054,10 +8054,6 @@ tbody { min-width: 22px; } - .query-manager-btn-shortcut { - color: var(--text-disabled) !important; - } - &:hover { background-color: $color-light-indigo-04; color: $color-light-indigo-10; @@ -8146,10 +8142,6 @@ tbody { } - .query-manager-btn-shortcut { - color: var(--text-disabled) !important; - } - &:hover { border: 1px solid $color-light-slate-08; color: $color-light-slate-11; @@ -8257,6 +8249,10 @@ tbody { } } +.query-manager-btn-shortcut { + color: var(--text-disabled) !important; +} + .font-weight-500 { font-weight: 500; } diff --git a/frontend/src/_ui/Icon/solidIcons/Play01.jsx b/frontend/src/_ui/Icon/solidIcons/Play01.jsx new file mode 100644 index 0000000000..42d3c88835 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Play01.jsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const Play01 = ({ fill = '#6A727C', width = '24', className = '', viewBox = '0 0 24 24' }) => ( + + + +); + +export default Play01; diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 34a2410e1d..aa0c307fec 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -87,6 +87,7 @@ import Pin from './Pin.jsx'; import Unpin from './Unpin.jsx'; import AlignRight from './AlignRight'; import Play from './Play.jsx'; +import Play01 from './Play01.jsx'; import Plus from './Plus.jsx'; import Plus01 from './Plus01.jsx'; import Reload from './Reload.jsx'; @@ -692,6 +693,8 @@ const Icon = (props) => { return ; case 'ai-crown': return ; + case 'play01': + return ; default: return ; } diff --git a/frontend/src/components/ui/Button/Button.jsx b/frontend/src/components/ui/Button/Button.jsx index 03919b8e93..6f95221805 100644 --- a/frontend/src/components/ui/Button/Button.jsx +++ b/frontend/src/components/ui/Button/Button.jsx @@ -133,10 +133,20 @@ const Button = forwardRef( const iconFillColor = !defaultButtonFillColour.includes(fill) && fill ? fill : getDefaultIconFillColor(variant); const Comp = asChild ? Slot : 'Button'; const leadingIconElement = leadingIcon && ( - +
+ +
); const trailingIconElement = trailingIcon && ( - +
+ +
); return ( From a3db1f4b3b57ca0ee3baa8ef2b8c254779e00463 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 18 Mar 2025 14:45:19 +0530 Subject: [PATCH 007/297] Added tooltips to run and preview buttons. --- .../Components/QueryManagerHeader.jsx | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx index 6055c0538a..5edd5421d4 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/QueryManagerHeader.jsx @@ -5,7 +5,7 @@ import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; import { shallow } from 'zustand/shallow'; -import { Tooltip } from 'react-tooltip'; +import { ToolTip } from '@/_components'; import { Button } from 'react-bootstrap'; import { decodeEntities } from '@/_helpers/utils'; import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers'; @@ -243,29 +243,21 @@ const RunButton = ({ buttonLoadingState }) => { ); return ( - - runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)} - leadingIcon="play01" - disabled={isInDraft} - isLoading={isLoading} - className="!tw-w-[88px]" - data-cy="query-run-button" - {...(isInDraft && { - 'data-tooltip-id': 'query-header-btn-run', - 'data-tooltip-content': 'Publish the query to run', - })} - > - Run ⌘↩ - - {isInDraft && } + + + runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)} + leadingIcon="play01" + disabled={isInDraft} + isLoading={isLoading} + className="!tw-w-[88px]" + data-cy="query-run-button" + > + Run ⌘↩ + + ); }; @@ -283,16 +275,18 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => { const { t } = useTranslation(); return ( - - Preview ⌘↑↩ - + + + Preview + + ); }; From f13d3053260af848e19287e2aa07457475d53aa7 Mon Sep 17 00:00:00 2001 From: Yukti Goyal Date: Wed, 19 Mar 2025 20:25:44 +0530 Subject: [PATCH 008/297] Added import,export and user onboard cases --- cypress-tests/cypress/commands/apiCommands.js | 1 + .../platform/externalApi/apiUsers.cy.js | 264 +++- .../externalApi/appImportAndExportAPI.cy.js | 160 +++ .../fixtures/templates/import_named_file.json | 1198 +++++++++++++++++ .../templates/import_unnamed_file.json | 1197 ++++++++++++++++ cypress-tests/cypress/support/utils/api.js | 28 + .../cypress/support/utils/manageGroups.js | 5 +- 7 files changed, 2817 insertions(+), 36 deletions(-) create mode 100644 cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js create mode 100644 cypress-tests/cypress/fixtures/templates/import_named_file.json create mode 100644 cypress-tests/cypress/fixtures/templates/import_unnamed_file.json diff --git a/cypress-tests/cypress/commands/apiCommands.js b/cypress-tests/cypress/commands/apiCommands.js index 0a0dc58e3e..f5ed132944 100644 --- a/cypress-tests/cypress/commands/apiCommands.js +++ b/cypress-tests/cypress/commands/apiCommands.js @@ -166,6 +166,7 @@ Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => { { log: false } ).then((response) => { expect(response.status).to.equal(201); + return response; }); }); }); diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js index 41deec86c4..b95970d349 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/apiUsers.cy.js @@ -1,20 +1,26 @@ import { fake } from "Fixtures/fake"; -import { createUser, getAllUsers, getUser, updateUser, createGroup, validateUserInGroup } from 'Support/utils/api'; +import { + createUser, getAllUsers, getUser, updateUser, createGroup, validateUserInGroup, updateUserRole, + getAllWorkspaces, replaceUserWorkspace, replaceUserWorkspacesRelations +} from 'Support/utils/api'; import { groupsSelector } from "Selectors/manageGroups"; import { commonSelectors } from 'Selectors/common'; import { searchUser, navigateToManageUsers, logout, navigateToManageGroups } from 'Support/utils/common'; - describe("API Test", () => { const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, ""); let userId; + let workspaceId; const data = { firstName: fake.firstName, lastName: fake.lastName, firstName1: fake.firstName, lastName1: fake.lastName, + firstName2: fake.firstName, + lastName2: fake.lastName, email: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), email1: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), + email2: fake.email.toLowerCase().replaceAll("[^A-Za-z]", ""), workspaceName: sanitize(fake.lastName), workspaceSlug: sanitize(fake.lastName), workspaceName1: sanitize(fake.firstName), @@ -23,7 +29,8 @@ describe("API Test", () => { group2: sanitize(fake.firstName), group3: sanitize(fake.firstName), group4: sanitize(fake.firstName), - group5: sanitize(fake.firstName) + group5: sanitize(fake.firstName), + appName: fake.companyName }; //user with all valid details @@ -86,27 +93,50 @@ describe("API Test", () => { createUser(userData).then((response) => { expect(response.status).to.eq(201); userId = response.body.id; - /* navigateToManageUsers(); - searchUser(data.email); - cy.contains("td", data.email) - .parent() - .within(() => { - cy.get("td small").should("have.text", "active"); - }); - - validateUserInGroup(data.email, "my-workspace", "end-user"); - validateUserInGroup(data.email, data.workspaceSlug, "builder"); - validateUserInGroup(data.email, data.workspaceSlug1, "admin", false); - cy.apiLogout(); - - cy.apiLogin(data.email, "password"); - cy.visit("/my-workspace"); - cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); - logout();*/ - }) - }) + workspaceId = response.body.workspaces[0].id; + navigateToManageUsers(); + searchUser(data.email); + cy.contains("td", data.email) + .parent() + .within(() => { + cy.get("td small").should("have.text", "active"); + }); - it.skip('Handles user creation errors', () => { + validateUserInGroup(data.email, "my-workspace", "end-user"); + validateUserInGroup(data.email, data.workspaceSlug, "builder"); + validateUserInGroup(data.email, data.workspaceSlug1, "admin", false); + cy.apiLogout(); + + cy.apiLogin(data.email, "password"); + cy.visit("/my-workspace"); + cy.get(commonSelectors.workspaceName).should("have.text", "My workspace"); + logout(); + + //Retrieve all users, a specific user by ID, and all workspaces + cy.defaultWorkspaceLogin(); + navigateToManageUsers(); + let number = 0; + cy.get('[data-cy="title-users-page"]').invoke('text').then((text) => { + number = parseInt(text.match(/\d+/)[0], 10); + }); + + getAllUsers().then((response) => { + expect(response.status).to.eq(200); + //expect(response.body.length).to.eq(number); //error due to removal of user from instance + }); + + getUser(userId).then((response) => { + expect(response.status).to.eq(200); + expect(response.body.name).to.eq(`${data.firstName} ${data.lastName}`); + }); + + getAllWorkspaces().then((response) => { + expect(response.status).to.eq(200); + }); + }); + }); + + it('Handles user creation errors', () => { const invalidUserData = [ { // Duplicate user data: { ...userData }, @@ -168,21 +198,185 @@ describe("API Test", () => { }) }); - it("Get all users and by user id", () => { - navigateToManageUsers(); - let number = 0; - cy.get('[data-cy="title-users-page"]').invoke('text').then((text) => { - number = parseInt(text.match(/\d+/)[0], 10); - cy.log('Number of users:', number); - }); - getAllUsers().then((response) => { + it("Update user details and workspaces relations", () => { + const updatedUserData = { + name: `${data.firstName1} ${data.lastName1}`, + email: data.email1, + password: "updatedpassword" + } + updateUser(userId, updatedUserData).then((response) => { expect(response.status).to.eq(200); - expect(response.body.length).to.eq(number); - }); + }) + cy.apiLogout(); + cy.apiLogin(updatedUserData.email, updatedUserData.password); + cy.apiLogout(); - getUser(userId).then((response) => { + // Replace user workspaces relations + cy.apiLogin(); + validateUserInGroup(updatedUserData.email, "my-workspace", data.group2); + validateUserInGroup(updatedUserData.email, data.workspaceSlug, data.group3); + cy.visit(data.workspaceSlug1); + navigateToManageUsers(); + searchUser(updatedUserData.email); + cy.contains("td", updatedUserData.email); + + replaceUserWorkspacesRelations(userId, [ + { name: "My workspace", status: "active", role: "end-user", groups: [{ name: data.group1 }] }, + { name: data.workspaceName, status: "active", role: "builder", groups: [] } + ]).then((response) => { expect(response.status).to.eq(200); - expect(response.body.name).to.eq(`${data.firstName} ${data.lastName}`); + }); + navigateToManageUsers(); + validateUserInGroup(updatedUserData.email, "my-workspace", data.group2, false); + validateUserInGroup(updatedUserData.email, data.workspaceSlug, data.group3, false); + + cy.visit(data.workspaceSlug1); + navigateToManageUsers(); + searchUser(updatedUserData.email); + cy.get('[data-cy="text-no-result-found"]').contains("No result found"); + replaceUserWorkspacesRelations(userId, []).then((response) => { + expect(response.status).to.eq(200); + }); + cy.visit("my-workspace"); + navigateToManageUsers(); + searchUser(updatedUserData.email); + cy.get('[data-cy="text-no-result-found"]').contains("No result found"); + }); + + it("update user role", () => { + const userData2 = { + name: `${data.firstName} ${data.lastName}`, + email: data.email, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active" + } + ] + } + let userId1; + let workspaceId1; + createUser(userData2).then((response) => { + expect(response.status).to.eq(201); + userId1 = response.body.id; + workspaceId1 = response.body.workspaces[0].id; + //update role to builder and validate user in builder's group + updateUserRole(workspaceId1, { newRole: "builder", userId: userId1 }) + .then((response) => { + expect(response.status).to.eq(200); + }); + validateUserInGroup(userData2.email, "my-workspace", "builder"); + + //update role to end-user and validate user is removed from builder's group + updateUserRole(workspaceId1, { newRole: "end-user", userId: userId1 }) + .then((response) => { + expect(response.status).to.eq(200); + }); + validateUserInGroup(userData2.email, "my-workspace", data.group5, false); + + // update role to builders and validate app's owner role can't be updated + updateUserRole(workspaceId1, { newRole: "builder", userId: userId1 }) + .then((response) => { + expect(response.status).to.eq(200); + }); + cy.apiLogout(); + cy.apiLogin(userData2.email, userData2.password); + cy.apiCreateApp(data.appName); + cy.apiLogout(); + cy.defaultWorkspaceLogin(); + updateUserRole(workspaceId1, { newRole: "end-user", userId: userId1 }) + .then((response) => { + expect(response.status).to.eq(400); + expect(response.body.message.title).to.include("Can not change user role"); + }); + + }); + }); + const userData3 = { + name: `${data.firstName2} ${data.lastName2}`, + email: data.email2, + password: "password", + status: "active", + workspaces: [ + { + name: "My workspace", + status: "active", + groups: [ + { name: data.group1 }, + { name: data.group2 } + ] + }, + { + name: data.workspaceName, + status: "active", + role: "builder", + groups: [{ name: data.group3 }] + }, + { + name: data.workspaceName1, + status: "archived", + role: "admin", + groups: [{ name: data.group4 }] + } + ] + }; + it("Replace user workspace", () => { + let userId1, workspaceId1; + createUser(userData3).then((response) => { + expect(response.status).to.eq(201); + userId1 = response.body.id; + workspaceId1 = response.body.workspaces[0].id; + + // Helper function to replace user workspace and validate response + const replaceAndValidate = (payload, expectedStatus = 200) => { + return replaceUserWorkspace(userId1, workspaceId1, payload).then((response) => { + expect(response.status).to.eq(expectedStatus); + }); + }; + + // No change if empty request body + replaceAndValidate({}).then(() => { + validateUserInGroup(userData3.email, "my-workspace", data.group1); + validateUserInGroup(userData3.email, "my-workspace", data.group2); + }); + + // Archive the user and verify status + replaceAndValidate({ status: "archived" }).then(() => { + navigateToManageUsers(); + searchUser(userData3.email); + cy.contains("td", userData3.email) + .parent() + .within(() => { + cy.get("td small").should("have.text", "archived"); + }); + }); + + // Reactivate user and validate groups + replaceAndValidate({ status: "active" }).then(() => { + validateUserInGroup(userData3.email, "my-workspace", data.group1); + validateUserInGroup(userData3.email, "my-workspace", data.group2); + }); + + // Update groups and validate removal + replaceAndValidate({ groups: [{ name: data.group1 }] }).then(() => { + validateUserInGroup(userData3.email, "my-workspace", data.group2, false); + }); + + //Empty group array, user removed from groups + replaceAndValidate({ groups: [] }).then(() => { + validateUserInGroup(userData3.email, "my-workspace", data.group1, false); + }); + + //Conflict permission + replaceAndValidate({ groups: [{ name: data.group5 }] }, 400); + + //Add user in groups and validate + replaceAndValidate({ groups: [{ name: data.group1 }, { name: data.group2 }] }); + validateUserInGroup(userData3.email, "my-workspace", data.group1); + validateUserInGroup(userData3.email, "my-workspace", data.group2); }); }); }); + diff --git a/cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js new file mode 100644 index 0000000000..f2e522b22a --- /dev/null +++ b/cypress-tests/cypress/e2e/happyPath/platform/externalApi/appImportAndExportAPI.cy.js @@ -0,0 +1,160 @@ +import { importApp, exportApp, allAppsDetails } from 'Support/utils/api'; +import { fake } from "Fixtures/fake"; + +describe("Export and Import API ", () => { + + const sanitize = (str) => str.toLowerCase().replace(/[^A-Za-z]/g, ""); + const data = { + workspaceName: sanitize(fake.lastName), + workspaceSlug: sanitize(fake.lastName), + } + + const fixtureFiles = { + requestData: "templates/import_unnamed_file.json", + requestData2: "templates/import_named_file.json", + requestData3: "templates/three-versions.json", + }; + let requestData, requestData2, requestData3; + + beforeEach(() => { + cy.defaultWorkspaceLogin(); + + const fixturePromises = Object.entries(fixtureFiles).map(([key, file]) => + cy.fixture(file).then((data) => ({ key, data })) + ); + + // Assign loaded data to respective variables + return Promise.all(fixturePromises).then((results) => { + results.forEach(({ key, data }) => { + ({ requestData, requestData2, requestData3 }[key] = data); + }); + }); + + }); + it("Import App API", () => { + const workspaceId = Cypress.env("workspaceId"); + + importApp(workspaceId, requestData).then((response) => { + expect(response.status).to.eq(201); + expect(response.body.message).to.include("App imported successfully into workspace"); + }); + + //Invalid access token and workspace + importApp(workspaceId, requestData, { + Authorization: "Basic xyz", + "Content-Type": "application/json" + }).then((response) => { + expect(response.status).to.eq(403); + }); + + importApp(workspaceId, requestData, { + Authorization: "", + "Content-Type": "application/json" + }).then((response) => { + expect(response.status).to.eq(403); + }); + + importApp(`${workspaceId}ee`, requestData).then((response) => { + expect(response.status).to.eq(400); + }); + + //Import named file + importApp(workspaceId, requestData2).then((response) => { + expect(response.status).to.eq(201); + expect(response.body.message).to.include("App imported successfully into workspace"); + }); + cy.reload(); + cy.get('[data-cy="app_json-title"]').should("exist"); + + //duplicate app + importApp(workspaceId, requestData2).then((response) => { + expect(response.status).to.eq(409); + expect(response.body.message).to.include("App with app_json already exists in the workspace"); + }); + cy.deleteApp("app_json"); + cy.get('[data-cy="app_json-title"]').should("not.exist"); + + //Import app in another workpsace + let newWorkspaceId; + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug).then((res) => { + newWorkspaceId = res.body.organization_id; + cy.visit(data.workspaceSlug); + + importApp(newWorkspaceId, requestData).then((response) => { + expect(response.status).to.eq(201); + expect(response.body.message).to.include("App imported successfully into workspace"); + }); + }); + }); + + it("Export App API", () => { + const workspaceId = Cypress.env("workspaceId"); + let appId; + importApp(workspaceId, requestData3).then((response) => { + expect(response.status).to.eq(201); + expect(response.body.message).to.include("App imported successfully into workspace"); + }).then(() => { + cy.get('[data-cy^="import-export-app"]') + .first() + .find('[data-cy="edit-button"]') + .click({ force: true }); + cy.skipWalkthrough(); + }); + + cy.get('[data-cy="left-sidebar-settings-button"]').click(); + cy.get('[data-cy="app-slug-input-field"]').invoke('val').then((value) => { + appId = value; + + //export last created version + exportApp(workspaceId, appId, "").then((response) => { + expect(response.status).to.eq(201); + expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(1); + expect(response.body.app[0].definition.appV2.appVersions[0].name).to.eq("v3"); + }); + //export specific versions + exportApp(workspaceId, appId, "?appVersion=v2").then((response) => { + expect(response.status).to.eq(201); + expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(1); + expect(response.body.app[0].definition.appV2.appVersions[0].name).to.eq("v2"); + }); + //export all versions + exportApp(workspaceId, appId, "?exportAllVersions=true").then((response) => { + expect(response.status).to.eq(201); + expect(response.body.app[0].definition.appV2.appVersions.length).to.eq(3); + }); + + //Invalid access token and workspace + /* exportApp(workspaceId, appId, "", { + Authorization: "", + "Content-Type": "application/json" + }).then((response) => { + expect(response.status).to.eq(403); + }); + + exportApp(workspaceId, appId, "", { + Authorization: "", + "Content-Type": "application/json" + }).then((response) => { + expect(response.status).to.eq(403); + }); + + exportApp(`${workspaceId}ee`, appId, "").then((response) => { + expect(response.status).to.eq(400); + }); + */ + //with and without TJDB -x.tooljet_database + exportApp(workspaceId, appId, "?exportTJDB=false").then((response) => { + expect(response.status).to.eq(201); + expect(response.body).not.to.have.property("tooljet_database"); + }); + exportApp(workspaceId, appId, "?exportTJDB=true").then((response) => { + expect(response.status).to.eq(201); + expect(response.body).to.have.property("tooljet_database"); + }); + }); + //All Apps details + allAppsDetails(workspaceId).then((response) => { + expect(response.status).to.eq(200); + }); + }); +}); \ No newline at end of file diff --git a/cypress-tests/cypress/fixtures/templates/import_named_file.json b/cypress-tests/cypress/fixtures/templates/import_named_file.json new file mode 100644 index 0000000000..0636a8b3b3 --- /dev/null +++ b/cypress-tests/cypress/fixtures/templates/import_named_file.json @@ -0,0 +1,1198 @@ +{ + "app": [ + { + "definition": { + "appV2": { + "type": "front-end", + "id": "8819afae-57b6-447d-93dd-6dc108169bfe", + "name": "AI powered code explainer", + "slug": "8819afae-57b6-447d-93dd-6dc108169bfe", + "isPublic": false, + "isMaintenanceOn": false, + "icon": "apps", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "currentVersionId": null, + "userId": "988bb9f5-e577-4065-8d3c-4fcf731ee15d", + "workflowApiToken": null, + "workflowEnabled": false, + "createdAt": "2025-02-27T07:28:52.129Z", + "creationMode": "DEFAULT", + "updatedAt": "2025-02-27T07:28:52.281Z", + "editingVersion": { + "id": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "name": "v1", + "definition": null, + "globalSettings": { + "hideHeader": true, + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "#edeff5", + "backgroundFxQuery": "", + "appMode": "auto" + }, + "pageSettings": { + "properties": { + "disableMenu": { + "value": "{{true}}", + "fxActive": false + } + } + }, + "showViewerNavigation": false, + "homePageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "appId": "8819afae-57b6-447d-93dd-6dc108169bfe", + "currentEnvironmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "promotedFrom": null, + "createdAt": "2025-02-27T07:28:52.144Z", + "updatedAt": "2025-02-27T07:28:52.274Z" + }, + "components": [ + { + "id": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "name": "container1", + "type": "Container", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": null, + "properties": {}, + "general": {}, + "styles": { + "backgroundColor": { + "value": "#ffffffff" + }, + "borderRadius": { + "value": "10" + }, + "borderColor": { + "value": "#ffffff00", + "fxActive": false + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "7367ab91-9541-4bd9-96f7-32da8bb61cf5", + "type": "desktop", + "top": 20, + "left": 1, + "width": 41, + "height": 70, + "componentId": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "9b1d3bec-c586-4f2b-acdf-09cea7addecc", + "name": "text1", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "properties": { + "text": { + "value": "B R A N D" + } + }, + "general": {}, + "styles": { + "textColor": { + "value": "#000", + "fxActive": false + }, + "textSize": { + "value": "{{24}}" + }, + "fontWeight": { + "value": "bold" + }, + "boxShadow": { + "value": "0px 0px 0px 0px #00000040" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "8c07e506-b1f5-4715-ab02-718fcce9295b", + "type": "desktop", + "top": 10, + "left": 1, + "width": 6, + "height": 40, + "componentId": "9b1d3bec-c586-4f2b-acdf-09cea7addecc", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "38100944-4325-49b7-8c70-de75cf5ce63d", + "name": "text2", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "properties": { + "text": { + "value": "
AI Code Explainer
" + } + }, + "general": {}, + "styles": { + "textColor": { + "value": "#000", + "fxActive": false + }, + "textSize": { + "value": "{{20}}" + }, + "textAlign": { + "value": "right" + }, + "boxShadow": { + "value": "0px 0px 0px 0px #00000040" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "129e63b6-9456-4cb7-94b0-7379743fec88", + "type": "desktop", + "top": 10, + "left": 25, + "width": 17, + "height": 40, + "componentId": "38100944-4325-49b7-8c70-de75cf5ce63d", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "name": "container2", + "type": "Container", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": null, + "properties": {}, + "general": {}, + "styles": { + "borderRadius": { + "value": "10" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "14b7706c-cebf-45dc-a555-3a60fdf01f3b", + "type": "desktop", + "top": 110, + "left": 1, + "width": 41, + "height": 620, + "componentId": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "ef46f35d-bf64-4ea0-8c9b-c525b87d6120", + "type": "mobile", + "top": 110, + "left": 1, + "width": 5, + "height": 200, + "componentId": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "name": "text3", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Code to be explained" + } + }, + "general": {}, + "styles": { + "textSize": { + "value": "24" + }, + "fontWeight": { + "value": "bold" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "cfc25680-87fe-45d1-8431-f009ba351ae2", + "type": "desktop", + "top": 20, + "left": 1, + "width": 20, + "height": 40, + "componentId": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "bbb13f33-25ee-4c97-8c08-7d7df99ab431", + "type": "mobile", + "top": 20, + "left": 9, + "width": 13.953488372093023, + "height": 40, + "componentId": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "name": "dropdown1", + "type": "DropDown", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "label": { + "value": "" + }, + "value": { + "value": "" + }, + "values": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.data.models.map(item => item.name)}}" + }, + "display_values": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.data.models.map(item => item.displayName)}}" + }, + "loadingState": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.isLoading}}", + "fxActive": true + }, + "placeholder": { + "value": "Select a model" + } + }, + "general": {}, + "styles": { + "borderRadius": { + "value": "5" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "91cae02e-6d00-48ad-9750-1ae74bc9fd7f", + "type": "mobile", + "top": 10, + "left": 27, + "width": 18.6046511627907, + "height": 30, + "componentId": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "bb56cbd1-57f7-4917-8a32-046a8e77ed33", + "type": "desktop", + "top": 480, + "left": 1, + "width": 20, + "height": 40, + "componentId": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "name": "textarea1", + "type": "TextArea", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "value": { + "value": "function addNumbers(a, b) {\n return a + b;\n}\n\nconst sum = addNumbers(5, 3);\nconsole.log(sum);" + }, + "placeholder": { + "value": "function addNumbers(a, b) {\n return a + b;\n}\n\nconst sum = addNumbers(5, 3);\nconsole.log(sum);" + } + }, + "general": {}, + "styles": { + "borderRadius": { + "value": "{{5}}" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "db411aca-2e7e-46ae-8bf6-75f664a636ea", + "type": "mobile", + "top": 100, + "left": 3, + "width": 13.953488372093023, + "height": 100, + "componentId": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "abdc0362-e37b-48d6-9cad-ccbdfcf6fd55", + "type": "desktop", + "top": 70, + "left": 1, + "width": 20, + "height": 270, + "componentId": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "name": "button1", + "type": "Button", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Generate explanation >>" + }, + "loadingState": { + "value": "{{false}}", + "fxActive": false + }, + "disabledState": { + "value": "{{components.dca350e6-c9f8-44aa-94d5-e6245cfb0ae2.value == undefined || queries.getCodeExplanation.isLoading}}", + "fxActive": true + } + }, + "general": {}, + "styles": { + "backgroundColor": { + "value": "#ffffff00" + }, + "textColor": { + "value": "#3e63ddff" + }, + "loaderColor": { + "value": "#3e63ddff" + }, + "borderRadius": { + "value": "{{5}}" + }, + "borderColor": { + "value": "#3e63ddff" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "c110426c-5994-4d04-901d-883aafb9d2eb", + "type": "mobile", + "top": 420, + "left": 7, + "width": 6.976744186046512, + "height": 30, + "componentId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "a6a6b92e-0984-4e21-87fc-462a307e06dd", + "type": "desktop", + "top": 550, + "left": 1, + "width": 20, + "height": 40, + "componentId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "name": "dropdown2", + "type": "DropDown", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "values": { + "value": "{{[\n \"\",\n \"C#\",\n \"C++\",\n \"Dart\",\n \"Elixir\",\n \"Erlang\",\n \"F#\",\n \"Go\",\n \"Groovy\",\n \"Haskell\",\n \"Java\",\n \"JavaScript\",\n \"Kotlin\",\n \"Lua\",\n \"MATLAB\",\n \"Objective-C\",\n \"Perl\",\n \"PHP\",\n \"Python\",\n \"R\",\n \"Ruby\",\n \"Rust\",\n \"Scala\",\n \"Shell\",\n \"SQL\",\n \"Swift\",\n \"TypeScript\"\n]}}" + }, + "display_values": { + "value": "{{[\n \"Any language\",\n \"C#\",\n \"C++\",\n \"Dart\",\n \"Elixir\",\n \"Erlang\",\n \"F#\",\n \"Go\",\n \"Groovy\",\n \"Haskell\",\n \"Java\",\n \"JavaScript\",\n \"Kotlin\",\n \"Lua\",\n \"MATLAB\",\n \"Objective-C\",\n \"Perl\",\n \"PHP\",\n \"Python\",\n \"R\",\n \"Ruby\",\n \"Rust\",\n \"Scala\",\n \"Shell\",\n \"SQL\",\n \"Swift\",\n \"TypeScript\"\n]}}" + }, + "value": { + "value": "" + }, + "placeholder": { + "value": "Select a language" + }, + "label": { + "value": "" + } + }, + "general": {}, + "styles": {}, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "553e50a3-c9d4-4ae7-9b8d-da129cb2f32d", + "type": "mobile", + "top": 420, + "left": 2, + "width": 8, + "height": 30, + "componentId": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "abeda4d6-0111-4b9a-bfb1-234c986ee777", + "type": "desktop", + "top": 390, + "left": 1, + "width": 20, + "height": 40, + "componentId": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "name": "text6", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Language" + } + }, + "general": {}, + "styles": { + "fontWeight": { + "value": "bold" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "3b6ce6c5-bb8b-4145-991b-b4dc659ac9ae", + "type": "desktop", + "top": 360, + "left": 1, + "width": 14, + "height": 30, + "componentId": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "d907a75f-162c-4e89-8bef-8ad4a6b10f6a", + "type": "mobile", + "top": 70, + "left": 4, + "width": 13.953488372093023, + "height": 40, + "componentId": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "052d73d1-c415-4720-8963-36c94ce54b19", + "name": "text7", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "{{`
${queries.getCodeExplanation.data.candidates ? queries.getCodeExplanation.data.candidates[0].content.parts[0].text : \"
  • Language: JavaScript
  • function addNumbers(a, b) {: Defines a function named addNumbers that takes two parameters a and b.
  • return a + b;: The function returns the sum of a and b.
  • }: Ends the function definition.
  • const sum = addNumbers(5, 3);: Calls the addNumbers function with arguments 5 and 3, and assigns the result to the constant sum.
  • console.log(sum);: Outputs the value of sum to the console, which is 8.
\"}
`}}" + }, + "textFormat": { + "value": "html" + }, + "loadingState": { + "fxActive": true, + "value": "{{queries.5774aa01-0931-4036-8bfa-4d12e0b6bc8b.isLoading}}" + } + }, + "general": {}, + "styles": { + "borderColor": { + "value": "#ddddddff" + }, + "borderRadius": { + "value": "5" + }, + "verticalAlignment": { + "value": "top" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "388a36e0-95ac-49f3-a97b-ad413efafb3a", + "type": "desktop", + "top": 70, + "left": 22, + "width": 20, + "height": 520, + "componentId": "052d73d1-c415-4720-8963-36c94ce54b19", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "1f2da6ab-3493-4ace-b5ae-ceadbd3e2fb0", + "type": "mobile", + "top": 290, + "left": 23, + "width": 6, + "height": 40, + "componentId": "052d73d1-c415-4720-8963-36c94ce54b19", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "name": "text8", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Gemini Model" + } + }, + "general": {}, + "styles": { + "fontWeight": { + "value": "bold" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "e41fd57d-29e6-49e2-b3ee-75b3769f6d27", + "type": "mobile", + "top": 70, + "left": 4, + "width": 13.953488372093023, + "height": 40, + "componentId": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "7a1d68a9-e9f5-496e-9947-3d6e12c55b0c", + "type": "desktop", + "top": 450, + "left": 1, + "width": 14, + "height": 30, + "componentId": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "521f0268-02d8-4571-9efe-030c9816bdea", + "name": "text9", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Explanation" + } + }, + "general": {}, + "styles": { + "textSize": { + "value": "24" + }, + "fontWeight": { + "value": "bold" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "81f9d6a1-90c4-422c-8602-1675a76f6de4", + "type": "desktop", + "top": 20, + "left": 22, + "width": 20, + "height": 40, + "componentId": "521f0268-02d8-4571-9efe-030c9816bdea", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "da2abfa1-769b-4784-87fb-5020e6610fed", + "type": "mobile", + "top": 20, + "left": 9, + "width": 13.953488372093023, + "height": 40, + "componentId": "521f0268-02d8-4571-9efe-030c9816bdea", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + } + ], + "pages": [ + { + "id": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "name": "Home", + "handle": "home", + "index": 1, + "disabled": false, + "hidden": false, + "icon": null, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.366Z", + "autoComputeLayout": true, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "pageGroupIndex": 1, + "pageGroupId": null, + "isPageGroup": false + } + ], + "events": [ + { + "id": "9417716e-b415-4532-aea4-8a2afa224f10", + "name": "onClick", + "index": 0, + "event": { + "eventId": "onClick", + "message": "Hello world!", + "queryId": "5774aa01-0931-4036-8bfa-4d12e0b6bc8b", + "actionId": "run-query", + "alertType": "info", + "queryName": "getCodeExplanation", + "parameters": {} + }, + "sourceId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "target": "component", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.260Z" + } + ], + "dataQueries": [ + { + "id": "5774aa01-0931-4036-8bfa-4d12e0b6bc8b", + "name": "getCodeExplanation", + "options": { + "method": "post", + "url": "https://generativelanguage.googleapis.com/v1beta/{{components.dropdown1.value}}:generateContent", + "url_params": [ + [ + "key", + "{{constants.GEMINI_API_KEY}}" + ], + [ + "", + "" + ] + ], + "headers": [ + [ + "Content-Type", + "application/json" + ], + [ + "", + "" + ] + ], + "body": [ + [ + "", + "" + ] + ], + "json_body": "{\n \"contents\": [\n {\n \"parts\": [\n {\n \"text\": \"{{components.textarea1.value.replaceAll('\\n','\\\\n')}} - Generate a point-wise line by line explanation of this code in html formatting only. Keep only the explanation, and nothing else. {{components.dropdown2.value ? `The code is in ${components.dropdown2.value} language.` : 'Also identify the language of the code.'}}\"\n }\n ]\n }\n ]\n}", + "body_toggle": true, + "transformationLanguage": "javascript", + "enableTransformation": false, + "arrayValuesChanged": false, + "transformation": "// write your code here\n// return value will be set as data and the original data will be available as rawData\nreturn data.filter(row => row.amount > 1000);\n " + }, + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "32ff6874-7da0-4b88-ae05-3c9cda4a07dc", + "name": "getGeminiModels", + "options": { + "method": "get", + "url": "https://generativelanguage.googleapis.com/v1beta/models?key={{constants.GEMINI_API_KEY}}", + "url_params": [ + [ + "", + "" + ] + ], + "headers": [ + [ + "", + "" + ] + ], + "body": [ + [ + "", + "" + ] + ], + "json_body": null, + "body_toggle": false, + "transformationLanguage": "javascript", + "enableTransformation": false, + "runOnPageLoad": true + }, + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.983Z" + } + ], + "dataSources": [ + { + "id": "489072da-3239-4bd5-91b9-dee5f5da5335", + "name": "restapidefault", + "kind": "restapi", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.153Z", + "updatedAt": "2025-02-27T07:28:52.153Z" + }, + { + "id": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "name": "runjsdefault", + "kind": "runjs", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.163Z", + "updatedAt": "2025-02-27T07:28:52.163Z" + }, + { + "id": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "name": "runpydefault", + "kind": "runpy", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.170Z", + "updatedAt": "2025-02-27T07:28:52.170Z" + }, + { + "id": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "name": "tooljetdbdefault", + "kind": "tooljetdb", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.176Z", + "updatedAt": "2025-02-27T07:28:52.176Z" + }, + { + "id": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "name": "workflowsdefault", + "kind": "workflows", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.183Z", + "updatedAt": "2025-02-27T07:28:52.183Z" + } + ], + "appVersions": [ + { + "id": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "name": "v1", + "definition": null, + "globalSettings": { + "hideHeader": true, + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "#edeff5", + "backgroundFxQuery": "", + "appMode": "auto" + }, + "pageSettings": { + "properties": { + "disableMenu": { + "value": "{{true}}", + "fxActive": false + } + } + }, + "showViewerNavigation": false, + "homePageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "appId": "8819afae-57b6-447d-93dd-6dc108169bfe", + "currentEnvironmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "promotedFrom": null, + "createdAt": "2025-02-27T07:28:52.144Z", + "updatedAt": "2025-02-27T07:28:52.274Z" + } + ], + "appEnvironments": [ + { + "id": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "development", + "isDefault": false, + "priority": 1, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + }, + { + "id": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "staging", + "isDefault": false, + "priority": 2, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + }, + { + "id": "eb006618-493c-48ac-8a4d-e80d208d29de", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "production", + "isDefault": true, + "priority": 3, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + } + ], + "dataSourceOptions": [ + { + "id": "98e3239b-e54b-4b59-992b-d9bbfb68d1e6", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "c997ee43-2f17-46aa-bc35-32af668d546b", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "01bbd244-59c9-4965-8024-401c8f1961fd", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "8a8096f7-6b6f-49ca-95de-596c5670c832", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "1e2dcc6d-8944-4b5b-abd4-72f7201bf657", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "24bfe83d-b2d3-4bf0-8730-1bd14d96cf7a", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "44600a77-04bf-4b30-8613-bd7a7bff6508", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "b70c436e-70e5-48d3-84d2-ca2c784c7425", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "7bd1351e-61b9-4ba6-9e57-8eca1a3a0df3", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "f8268c18-8453-48ac-acf7-46c2d0c75c75", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "cd59c697-6ac2-4594-b8f6-493676e8b3c7", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "7270ef0a-f6d6-498e-9959-4b646a30d5d1", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "fe4824d5-57fa-42dc-9812-4af634737898", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + }, + { + "id": "521a6d91-484b-45bc-af8f-aaceb0dc515c", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + }, + { + "id": "f5955bf2-c148-4544-acd9-a9a92a943e5b", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + } + ], + "schemaDetails": { + "multiPages": true, + "multiEnv": true, + "globalDataSources": true + } + } + } + } + ], + "tooljet_version": "3.5.3-ee-lts", + "appName": "app_json" +} \ No newline at end of file diff --git a/cypress-tests/cypress/fixtures/templates/import_unnamed_file.json b/cypress-tests/cypress/fixtures/templates/import_unnamed_file.json new file mode 100644 index 0000000000..93c2501a51 --- /dev/null +++ b/cypress-tests/cypress/fixtures/templates/import_unnamed_file.json @@ -0,0 +1,1197 @@ +{ + "app": [ + { + "definition": { + "appV2": { + "type": "front-end", + "id": "8819afae-57b6-447d-93dd-6dc108169bfe", + "name": "AI powered code explainer", + "slug": "8819afae-57b6-447d-93dd-6dc108169bfe", + "isPublic": false, + "isMaintenanceOn": false, + "icon": "apps", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "currentVersionId": null, + "userId": "988bb9f5-e577-4065-8d3c-4fcf731ee15d", + "workflowApiToken": null, + "workflowEnabled": false, + "createdAt": "2025-02-27T07:28:52.129Z", + "creationMode": "DEFAULT", + "updatedAt": "2025-02-27T07:28:52.281Z", + "editingVersion": { + "id": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "name": "v1", + "definition": null, + "globalSettings": { + "hideHeader": true, + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "#edeff5", + "backgroundFxQuery": "", + "appMode": "auto" + }, + "pageSettings": { + "properties": { + "disableMenu": { + "value": "{{true}}", + "fxActive": false + } + } + }, + "showViewerNavigation": false, + "homePageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "appId": "8819afae-57b6-447d-93dd-6dc108169bfe", + "currentEnvironmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "promotedFrom": null, + "createdAt": "2025-02-27T07:28:52.144Z", + "updatedAt": "2025-02-27T07:28:52.274Z" + }, + "components": [ + { + "id": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "name": "container1", + "type": "Container", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": null, + "properties": {}, + "general": {}, + "styles": { + "backgroundColor": { + "value": "#ffffffff" + }, + "borderRadius": { + "value": "10" + }, + "borderColor": { + "value": "#ffffff00", + "fxActive": false + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "7367ab91-9541-4bd9-96f7-32da8bb61cf5", + "type": "desktop", + "top": 20, + "left": 1, + "width": 41, + "height": 70, + "componentId": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "9b1d3bec-c586-4f2b-acdf-09cea7addecc", + "name": "text1", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "properties": { + "text": { + "value": "B R A N D" + } + }, + "general": {}, + "styles": { + "textColor": { + "value": "#000", + "fxActive": false + }, + "textSize": { + "value": "{{24}}" + }, + "fontWeight": { + "value": "bold" + }, + "boxShadow": { + "value": "0px 0px 0px 0px #00000040" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "8c07e506-b1f5-4715-ab02-718fcce9295b", + "type": "desktop", + "top": 10, + "left": 1, + "width": 6, + "height": 40, + "componentId": "9b1d3bec-c586-4f2b-acdf-09cea7addecc", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "38100944-4325-49b7-8c70-de75cf5ce63d", + "name": "text2", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "7bf37542-4eaa-42d8-9827-1cf1f1649791", + "properties": { + "text": { + "value": "
AI Code Explainer
" + } + }, + "general": {}, + "styles": { + "textColor": { + "value": "#000", + "fxActive": false + }, + "textSize": { + "value": "{{20}}" + }, + "textAlign": { + "value": "right" + }, + "boxShadow": { + "value": "0px 0px 0px 0px #00000040" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "129e63b6-9456-4cb7-94b0-7379743fec88", + "type": "desktop", + "top": 10, + "left": 25, + "width": 17, + "height": 40, + "componentId": "38100944-4325-49b7-8c70-de75cf5ce63d", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "name": "container2", + "type": "Container", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": null, + "properties": {}, + "general": {}, + "styles": { + "borderRadius": { + "value": "10" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "14b7706c-cebf-45dc-a555-3a60fdf01f3b", + "type": "desktop", + "top": 110, + "left": 1, + "width": 41, + "height": 620, + "componentId": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "ef46f35d-bf64-4ea0-8c9b-c525b87d6120", + "type": "mobile", + "top": 110, + "left": 1, + "width": 5, + "height": 200, + "componentId": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "name": "text3", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Code to be explained" + } + }, + "general": {}, + "styles": { + "textSize": { + "value": "24" + }, + "fontWeight": { + "value": "bold" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "cfc25680-87fe-45d1-8431-f009ba351ae2", + "type": "desktop", + "top": 20, + "left": 1, + "width": 20, + "height": 40, + "componentId": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "bbb13f33-25ee-4c97-8c08-7d7df99ab431", + "type": "mobile", + "top": 20, + "left": 9, + "width": 13.953488372093023, + "height": 40, + "componentId": "fbdab782-4a3b-4811-9f43-35f6dfae8735", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "name": "dropdown1", + "type": "DropDown", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "label": { + "value": "" + }, + "value": { + "value": "" + }, + "values": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.data.models.map(item => item.name)}}" + }, + "display_values": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.data.models.map(item => item.displayName)}}" + }, + "loadingState": { + "value": "{{queries.32ff6874-7da0-4b88-ae05-3c9cda4a07dc.isLoading}}", + "fxActive": true + }, + "placeholder": { + "value": "Select a model" + } + }, + "general": {}, + "styles": { + "borderRadius": { + "value": "5" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "91cae02e-6d00-48ad-9750-1ae74bc9fd7f", + "type": "mobile", + "top": 10, + "left": 27, + "width": 18.6046511627907, + "height": 30, + "componentId": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "bb56cbd1-57f7-4917-8a32-046a8e77ed33", + "type": "desktop", + "top": 480, + "left": 1, + "width": 20, + "height": 40, + "componentId": "dca350e6-c9f8-44aa-94d5-e6245cfb0ae2", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "name": "textarea1", + "type": "TextArea", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "value": { + "value": "function addNumbers(a, b) {\n return a + b;\n}\n\nconst sum = addNumbers(5, 3);\nconsole.log(sum);" + }, + "placeholder": { + "value": "function addNumbers(a, b) {\n return a + b;\n}\n\nconst sum = addNumbers(5, 3);\nconsole.log(sum);" + } + }, + "general": {}, + "styles": { + "borderRadius": { + "value": "{{5}}" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "db411aca-2e7e-46ae-8bf6-75f664a636ea", + "type": "mobile", + "top": 100, + "left": 3, + "width": 13.953488372093023, + "height": 100, + "componentId": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "abdc0362-e37b-48d6-9cad-ccbdfcf6fd55", + "type": "desktop", + "top": 70, + "left": 1, + "width": 20, + "height": 270, + "componentId": "98a7f66e-d446-4be5-b2e8-6bde808e9461", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "name": "button1", + "type": "Button", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Generate explanation >>" + }, + "loadingState": { + "value": "{{false}}", + "fxActive": false + }, + "disabledState": { + "value": "{{components.dca350e6-c9f8-44aa-94d5-e6245cfb0ae2.value == undefined || queries.getCodeExplanation.isLoading}}", + "fxActive": true + } + }, + "general": {}, + "styles": { + "backgroundColor": { + "value": "#ffffff00" + }, + "textColor": { + "value": "#3e63ddff" + }, + "loaderColor": { + "value": "#3e63ddff" + }, + "borderRadius": { + "value": "{{5}}" + }, + "borderColor": { + "value": "#3e63ddff" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "c110426c-5994-4d04-901d-883aafb9d2eb", + "type": "mobile", + "top": 420, + "left": 7, + "width": 6.976744186046512, + "height": 30, + "componentId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "a6a6b92e-0984-4e21-87fc-462a307e06dd", + "type": "desktop", + "top": 550, + "left": 1, + "width": 20, + "height": 40, + "componentId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "name": "dropdown2", + "type": "DropDown", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "values": { + "value": "{{[\n \"\",\n \"C#\",\n \"C++\",\n \"Dart\",\n \"Elixir\",\n \"Erlang\",\n \"F#\",\n \"Go\",\n \"Groovy\",\n \"Haskell\",\n \"Java\",\n \"JavaScript\",\n \"Kotlin\",\n \"Lua\",\n \"MATLAB\",\n \"Objective-C\",\n \"Perl\",\n \"PHP\",\n \"Python\",\n \"R\",\n \"Ruby\",\n \"Rust\",\n \"Scala\",\n \"Shell\",\n \"SQL\",\n \"Swift\",\n \"TypeScript\"\n]}}" + }, + "display_values": { + "value": "{{[\n \"Any language\",\n \"C#\",\n \"C++\",\n \"Dart\",\n \"Elixir\",\n \"Erlang\",\n \"F#\",\n \"Go\",\n \"Groovy\",\n \"Haskell\",\n \"Java\",\n \"JavaScript\",\n \"Kotlin\",\n \"Lua\",\n \"MATLAB\",\n \"Objective-C\",\n \"Perl\",\n \"PHP\",\n \"Python\",\n \"R\",\n \"Ruby\",\n \"Rust\",\n \"Scala\",\n \"Shell\",\n \"SQL\",\n \"Swift\",\n \"TypeScript\"\n]}}" + }, + "value": { + "value": "" + }, + "placeholder": { + "value": "Select a language" + }, + "label": { + "value": "" + } + }, + "general": {}, + "styles": {}, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "553e50a3-c9d4-4ae7-9b8d-da129cb2f32d", + "type": "mobile", + "top": 420, + "left": 2, + "width": 8, + "height": 30, + "componentId": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "abeda4d6-0111-4b9a-bfb1-234c986ee777", + "type": "desktop", + "top": 390, + "left": 1, + "width": 20, + "height": 40, + "componentId": "b56cb989-9b4f-4dcf-a6c4-70dcfe6aac1a", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "name": "text6", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Language" + } + }, + "general": {}, + "styles": { + "fontWeight": { + "value": "bold" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "3b6ce6c5-bb8b-4145-991b-b4dc659ac9ae", + "type": "desktop", + "top": 360, + "left": 1, + "width": 14, + "height": 30, + "componentId": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "d907a75f-162c-4e89-8bef-8ad4a6b10f6a", + "type": "mobile", + "top": 70, + "left": 4, + "width": 13.953488372093023, + "height": 40, + "componentId": "7112d3b6-f7d5-4da8-84ad-f2db9ab962d8", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "052d73d1-c415-4720-8963-36c94ce54b19", + "name": "text7", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "{{`
${queries.getCodeExplanation.data.candidates ? queries.getCodeExplanation.data.candidates[0].content.parts[0].text : \"
  • Language: JavaScript
  • function addNumbers(a, b) {: Defines a function named addNumbers that takes two parameters a and b.
  • return a + b;: The function returns the sum of a and b.
  • }: Ends the function definition.
  • const sum = addNumbers(5, 3);: Calls the addNumbers function with arguments 5 and 3, and assigns the result to the constant sum.
  • console.log(sum);: Outputs the value of sum to the console, which is 8.
\"}
`}}" + }, + "textFormat": { + "value": "html" + }, + "loadingState": { + "fxActive": true, + "value": "{{queries.5774aa01-0931-4036-8bfa-4d12e0b6bc8b.isLoading}}" + } + }, + "general": {}, + "styles": { + "borderColor": { + "value": "#ddddddff" + }, + "borderRadius": { + "value": "5" + }, + "verticalAlignment": { + "value": "top" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.267Z", + "layouts": [ + { + "id": "388a36e0-95ac-49f3-a97b-ad413efafb3a", + "type": "desktop", + "top": 70, + "left": 22, + "width": 20, + "height": 520, + "componentId": "052d73d1-c415-4720-8963-36c94ce54b19", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "1f2da6ab-3493-4ace-b5ae-ceadbd3e2fb0", + "type": "mobile", + "top": 290, + "left": 23, + "width": 6, + "height": 40, + "componentId": "052d73d1-c415-4720-8963-36c94ce54b19", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "name": "text8", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Gemini Model" + } + }, + "general": {}, + "styles": { + "fontWeight": { + "value": "bold" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "e41fd57d-29e6-49e2-b3ee-75b3769f6d27", + "type": "mobile", + "top": 70, + "left": 4, + "width": 13.953488372093023, + "height": 40, + "componentId": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "7a1d68a9-e9f5-496e-9947-3d6e12c55b0c", + "type": "desktop", + "top": 450, + "left": 1, + "width": 14, + "height": 30, + "componentId": "3c5278e8-8cc5-442a-bc70-19d4cd72cec7", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + }, + { + "id": "521f0268-02d8-4571-9efe-030c9816bdea", + "name": "text9", + "type": "Text", + "pageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "parent": "2727cf8a-1856-41cb-b716-c25afc2a15ad", + "properties": { + "text": { + "value": "Explanation" + } + }, + "general": {}, + "styles": { + "textSize": { + "value": "24" + }, + "fontWeight": { + "value": "bold" + }, + "isScrollRequired": { + "value": "disabled" + } + }, + "generalStyles": {}, + "displayPreferences": { + "showOnDesktop": { + "value": "{{true}}" + }, + "showOnMobile": { + "value": "{{false}}" + } + }, + "validation": {}, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z", + "layouts": [ + { + "id": "81f9d6a1-90c4-422c-8602-1675a76f6de4", + "type": "desktop", + "top": 20, + "left": 22, + "width": 20, + "height": 40, + "componentId": "521f0268-02d8-4571-9efe-030c9816bdea", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "da2abfa1-769b-4784-87fb-5020e6610fed", + "type": "mobile", + "top": 20, + "left": 9, + "width": 13.953488372093023, + "height": 40, + "componentId": "521f0268-02d8-4571-9efe-030c9816bdea", + "dimensionUnit": "count", + "updatedAt": "2025-02-27T07:28:52.148Z" + } + ] + } + ], + "pages": [ + { + "id": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "name": "Home", + "handle": "home", + "index": 1, + "disabled": false, + "hidden": false, + "icon": null, + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.366Z", + "autoComputeLayout": true, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "pageGroupIndex": 1, + "pageGroupId": null, + "isPageGroup": false + } + ], + "events": [ + { + "id": "9417716e-b415-4532-aea4-8a2afa224f10", + "name": "onClick", + "index": 0, + "event": { + "eventId": "onClick", + "message": "Hello world!", + "queryId": "5774aa01-0931-4036-8bfa-4d12e0b6bc8b", + "actionId": "run-query", + "alertType": "info", + "queryName": "getCodeExplanation", + "parameters": {} + }, + "sourceId": "37fbb9ed-5ac6-439b-82cb-35e276c89f49", + "target": "component", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.260Z" + } + ], + "dataQueries": [ + { + "id": "5774aa01-0931-4036-8bfa-4d12e0b6bc8b", + "name": "getCodeExplanation", + "options": { + "method": "post", + "url": "https://generativelanguage.googleapis.com/v1beta/{{components.dropdown1.value}}:generateContent", + "url_params": [ + [ + "key", + "{{constants.GEMINI_API_KEY}}" + ], + [ + "", + "" + ] + ], + "headers": [ + [ + "Content-Type", + "application/json" + ], + [ + "", + "" + ] + ], + "body": [ + [ + "", + "" + ] + ], + "json_body": "{\n \"contents\": [\n {\n \"parts\": [\n {\n \"text\": \"{{components.textarea1.value.replaceAll('\\n','\\\\n')}} - Generate a point-wise line by line explanation of this code in html formatting only. Keep only the explanation, and nothing else. {{components.dropdown2.value ? `The code is in ${components.dropdown2.value} language.` : 'Also identify the language of the code.'}}\"\n }\n ]\n }\n ]\n}", + "body_toggle": true, + "transformationLanguage": "javascript", + "enableTransformation": false, + "arrayValuesChanged": false, + "transformation": "// write your code here\n// return value will be set as data and the original data will be available as rawData\nreturn data.filter(row => row.amount > 1000);\n " + }, + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.148Z" + }, + { + "id": "32ff6874-7da0-4b88-ae05-3c9cda4a07dc", + "name": "getGeminiModels", + "options": { + "method": "get", + "url": "https://generativelanguage.googleapis.com/v1beta/models?key={{constants.GEMINI_API_KEY}}", + "url_params": [ + [ + "", + "" + ] + ], + "headers": [ + [ + "", + "" + ] + ], + "body": [ + [ + "", + "" + ] + ], + "json_body": null, + "body_toggle": false, + "transformationLanguage": "javascript", + "enableTransformation": false, + "runOnPageLoad": true + }, + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "createdAt": "2025-02-27T07:28:52.148Z", + "updatedAt": "2025-02-27T07:28:52.983Z" + } + ], + "dataSources": [ + { + "id": "489072da-3239-4bd5-91b9-dee5f5da5335", + "name": "restapidefault", + "kind": "restapi", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.153Z", + "updatedAt": "2025-02-27T07:28:52.153Z" + }, + { + "id": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "name": "runjsdefault", + "kind": "runjs", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.163Z", + "updatedAt": "2025-02-27T07:28:52.163Z" + }, + { + "id": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "name": "runpydefault", + "kind": "runpy", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.170Z", + "updatedAt": "2025-02-27T07:28:52.170Z" + }, + { + "id": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "name": "tooljetdbdefault", + "kind": "tooljetdb", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.176Z", + "updatedAt": "2025-02-27T07:28:52.176Z" + }, + { + "id": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "name": "workflowsdefault", + "kind": "workflows", + "type": "static", + "pluginId": null, + "appVersionId": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "organizationId": null, + "scope": "local", + "createdAt": "2025-02-27T07:28:52.183Z", + "updatedAt": "2025-02-27T07:28:52.183Z" + } + ], + "appVersions": [ + { + "id": "430dd7d7-1cd1-4c36-975f-229a1aa7dcb8", + "name": "v1", + "definition": null, + "globalSettings": { + "hideHeader": true, + "appInMaintenance": false, + "canvasMaxWidth": 100, + "canvasMaxWidthType": "%", + "canvasMaxHeight": 2400, + "canvasBackgroundColor": "#edeff5", + "backgroundFxQuery": "", + "appMode": "auto" + }, + "pageSettings": { + "properties": { + "disableMenu": { + "value": "{{true}}", + "fxActive": false + } + } + }, + "showViewerNavigation": false, + "homePageId": "93c0473f-6ada-4f1d-9c05-8a4775466aab", + "appId": "8819afae-57b6-447d-93dd-6dc108169bfe", + "currentEnvironmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "promotedFrom": null, + "createdAt": "2025-02-27T07:28:52.144Z", + "updatedAt": "2025-02-27T07:28:52.274Z" + } + ], + "appEnvironments": [ + { + "id": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "development", + "isDefault": false, + "priority": 1, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + }, + { + "id": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "staging", + "isDefault": false, + "priority": 2, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + }, + { + "id": "eb006618-493c-48ac-8a4d-e80d208d29de", + "organizationId": "a51da635-3a28-4b10-a6f4-7ba34e254987", + "name": "production", + "isDefault": true, + "priority": 3, + "enabled": true, + "createdAt": "2025-02-27T07:28:36.425Z", + "updatedAt": "2025-02-27T07:28:36.425Z" + } + ], + "dataSourceOptions": [ + { + "id": "98e3239b-e54b-4b59-992b-d9bbfb68d1e6", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "c997ee43-2f17-46aa-bc35-32af668d546b", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "01bbd244-59c9-4965-8024-401c8f1961fd", + "dataSourceId": "489072da-3239-4bd5-91b9-dee5f5da5335", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.159Z", + "updatedAt": "2025-02-27T07:28:52.159Z" + }, + { + "id": "8a8096f7-6b6f-49ca-95de-596c5670c832", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "1e2dcc6d-8944-4b5b-abd4-72f7201bf657", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "24bfe83d-b2d3-4bf0-8730-1bd14d96cf7a", + "dataSourceId": "c462a40f-c7fa-4d55-98fe-9bc445271cab", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.167Z", + "updatedAt": "2025-02-27T07:28:52.167Z" + }, + { + "id": "44600a77-04bf-4b30-8613-bd7a7bff6508", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "b70c436e-70e5-48d3-84d2-ca2c784c7425", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "7bd1351e-61b9-4ba6-9e57-8eca1a3a0df3", + "dataSourceId": "cc0b5feb-29ea-47c0-9a9a-62c0e0b89ccb", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.174Z", + "updatedAt": "2025-02-27T07:28:52.174Z" + }, + { + "id": "f8268c18-8453-48ac-acf7-46c2d0c75c75", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "cd59c697-6ac2-4594-b8f6-493676e8b3c7", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "7270ef0a-f6d6-498e-9959-4b646a30d5d1", + "dataSourceId": "907dde15-1ac2-4f53-ba72-8e4e1d066f0e", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.181Z", + "updatedAt": "2025-02-27T07:28:52.181Z" + }, + { + "id": "fe4824d5-57fa-42dc-9812-4af634737898", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "4efb81aa-756a-4a8f-a017-e167f0720b85", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + }, + { + "id": "521a6d91-484b-45bc-af8f-aaceb0dc515c", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "ed48a4ab-ef5c-47c0-b99c-453fec90b9ec", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + }, + { + "id": "f5955bf2-c148-4544-acd9-a9a92a943e5b", + "dataSourceId": "66ad4e35-f981-47a6-99cd-31e9e7bbd9b3", + "environmentId": "eb006618-493c-48ac-8a4d-e80d208d29de", + "options": null, + "createdAt": "2025-02-27T07:28:52.189Z", + "updatedAt": "2025-02-27T07:28:52.189Z" + } + ], + "schemaDetails": { + "multiPages": true, + "multiEnv": true, + "globalDataSources": true + } + } + } + } + ], + "tooljet_version": "3.5.3-ee-lts" +} \ No newline at end of file diff --git a/cypress-tests/cypress/support/utils/api.js b/cypress-tests/cypress/support/utils/api.js index 8532b89bb2..1ea37104f8 100644 --- a/cypress-tests/cypress/support/utils/api.js +++ b/cypress-tests/cypress/support/utils/api.js @@ -29,6 +29,34 @@ export const getAllUsers = () => { export const updateUser = (userId, userData) => { return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}`, userData); }; +export const updateUserRole = (workspaceId, userData) => { + return apiRequest("PUT", `${Cypress.env('API_URL')}/ext/update-user-role/workspace/${workspaceId}`, userData); +} + +export const replaceUserWorkspace = (userId, workspaceId, userData) => { + return apiRequest("PATCH", `${Cypress.env('API_URL')}/ext/user/${userId}/workspace/${workspaceId}`, userData); +} + +export const replaceUserWorkspacesRelations = (userId, userData) => { + return apiRequest("PUT", `${Cypress.env('API_URL')}/ext/user/${userId}/workspaces`, userData); +} + +export const getAllWorkspaces = () => { + return apiRequest("GET", `${Cypress.env('API_URL')}/ext/workspaces`); +} + +export const importApp = (workspaceId, appData, headers) => { + return apiRequest("POST", `${Cypress.env('API_URL')}/ext/import/workspace/${workspaceId}/apps`, appData, headers); +} + +export const exportApp = (workspaceId, appId, endpoint, headers) => { + return apiRequest("POST", `${Cypress.env('API_URL')}/ext/export/workspace/${workspaceId}/apps/${appId}${endpoint}`, headers); +} + +export const allAppsDetails = (workspaceIds) => { + return apiRequest("GET", `${Cypress.env('API_URL')}/ext/workspace/${workspaceIds}/apps`); +} + export const createGroup = (groupName) => { cy.get(groupsSelector.createNewGroupButton).click(); cy.clearAndType(groupsSelector.groupNameInput, groupName); diff --git a/cypress-tests/cypress/support/utils/manageGroups.js b/cypress-tests/cypress/support/utils/manageGroups.js index 2c114dfad8..2b7300d760 100644 --- a/cypress-tests/cypress/support/utils/manageGroups.js +++ b/cypress-tests/cypress/support/utils/manageGroups.js @@ -850,6 +850,9 @@ export const createGroupsAndAddUserInGroup = (groupName, email) => { commonSelectors.toastMessage, groupsText.groupCreatedToast ); + addUserInGroup(groupName, email); +}; +export const addUserInGroup = (groupName, email) => { cy.get(groupsSelector.groupLink(groupName)).click(); cy.clearAndType(groupsSelector.multiSelectSearchInput, email); cy.wait(2000); @@ -859,7 +862,7 @@ export const createGroupsAndAddUserInGroup = (groupName, email) => { commonSelectors.toastMessage, groupsText.userAddedToast ); -}; +} export const inviteUserBasedOnRole = (firstName, email, role = "end-user") => { fillUserInviteForm(firstName, email); From 2a4fb0055dc81bf5c6d1c97c460441557d3cf352 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Thu, 20 Mar 2025 14:16:29 +0530 Subject: [PATCH 009/297] control overflow chaining for code editor --- frontend/src/_styles/queryManager.scss | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/_styles/queryManager.scss b/frontend/src/_styles/queryManager.scss index 7b256c3812..057dbc7a2d 100644 --- a/frontend/src/_styles/queryManager.scss +++ b/frontend/src/_styles/queryManager.scss @@ -1250,6 +1250,7 @@ $border-radius: 4px; color: var(--slate12) !important; } } + &.data-source-exists { .cm-editor { border-radius: 0 4px 4px 0 !important; @@ -1834,6 +1835,7 @@ $border-radius: 4px; .cm-scroller { border-bottom-left-radius: 4px; + overscroll-behavior: auto !important; } } @@ -1853,18 +1855,19 @@ $border-radius: 4px; margin-left: 32px !important; } -.tjdb-codhinter-wrapper{ - .codehinter-input{ - .cm-editor{ +.tjdb-codhinter-wrapper { + .codehinter-input { + .cm-editor { height: 30px !important; min-height: 30px !important; border-radius: 0 !important; - border-right: 0 ; - } + border-right: 0; + } } } -.tjdb-limit-offset-codehinter{ - .cm-editor{ + +.tjdb-limit-offset-codehinter { + .cm-editor { height: 30px !important; min-height: 30px !important; } @@ -1894,7 +1897,7 @@ $border-radius: 4px; margin-right: auto; p { - display: flex; + display: flex; align-items: center; span { From d5b3708de00c299de8690ad6e4de93f62116a459 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Thu, 20 Mar 2025 21:38:15 +0530 Subject: [PATCH 010/297] feat: Makes header and footer resizable --- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 21 +++ .../Inspector/Components/Form.jsx | 19 --- .../AppBuilder/WidgetManager/widgets/form.js | 7 + .../Form/Components/HorizontalSlot.jsx | 78 ++++++++++ frontend/src/AppBuilder/Widgets/Form/Form.jsx | 137 ++++++++---------- .../src/AppBuilder/Widgets/Form/form.scss | 45 ++++++ .../src/AppBuilder/_hooks/useActiveSlot.js | 46 ++++++ frontend/src/AppBuilder/_hooks/useMoveable.js | 135 +++++++++++++++++ .../src/Editor/WidgetManager/configs/form.js | 7 + .../apps/services/widget-config/form.js | 7 + 10 files changed, 408 insertions(+), 94 deletions(-) create mode 100644 frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx create mode 100644 frontend/src/AppBuilder/_hooks/useActiveSlot.js create mode 100644 frontend/src/AppBuilder/_hooks/useMoveable.js diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index da179bc11d..b18dd9d311 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -415,6 +415,27 @@ export function hideGridLines() { document.getElementById('real-canvas')?.classList.add('hide-grid'); } +export function showGridLinesOnSlot(slotId) { + var canvasElm = document.getElementById(`canvas-${slotId}`); + + canvasElm.classList.remove('hide-grid'); + canvasElm.classList.add('show-grid'); + + document.getElementById('real-canvas')?.classList.add('hide-grid'); + document.getElementById('real-canvas')?.classList.remove('show-grid'); +} + +export function hideGridLinesOnSlot(slotId) { + var canvasElm = document.getElementById(`canvas-${slotId}`); + + + canvasElm.classList.remove('show-grid'); + canvasElm.classList.add('hide-grid'); + + document.getElementById('real-canvas')?.classList.remove('hide-grid'); + document.getElementById('real-canvas')?.classList.add('show-grid'); +} + // Track previously active elements for efficient cleanup let previousActiveWidgets = null; let previousActiveCanvas = null; diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx index b39924854e..e0178ddbeb 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx @@ -168,25 +168,6 @@ export const baseComponentProperties = ( }); } - items.push({ - title: `${i18next.t('widget.common.general', 'General')}`, - isOpen: true, - children: ( - <> - {renderElement( - component, - componentMeta, - layoutPropertyChanged, - dataQueries, - 'tooltip', - 'general', - currentState, - allComponents - )} - - ), - }); - items.push({ title: `${i18next.t('widget.common.devices', 'Devices')}`, isOpen: true, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js index 2d8eb7f0a8..c5194822b6 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js @@ -294,6 +294,13 @@ export const formConfig = { defaultValue: false, }, }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, }, events: { onSubmit: { displayName: 'On submit' }, diff --git a/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx b/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx new file mode 100644 index 0000000000..0e15e4a058 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx @@ -0,0 +1,78 @@ +import React, { useEffect } from 'react'; +import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container'; +import { showGridLinesOnSlot, hideGridLinesOnSlot } from '@/AppBuilder/AppCanvas/Grid/gridUtils'; +import { useResizable } from '@/AppBuilder/_hooks/useMoveable'; + +export const HorizontalSlot = React.memo( + ({ + id, + height = 0, + width, + darkMode, + isDisabled, + isActive, + slotName = 'header', // 'header' or 'footer' + slotStyle = {}, + onResize + }) => { + const parsedHeight = parseInt(height, 10); + + const { getRootProps, getHandleProps, getResizeState } = useResizable({ + initialHeight: parsedHeight, + initialWidth: '100%', // Now respects parent's width + minHeight: 40, + maxHeight: 400, + maxWidth: '100%', + stepHeight: 10, // Height will change in steps of 10px + onResize: () => {}, + onDragEnd: (values) => { + onResize(values); + }, + isReverseVerticalDrag: slotName === 'footer', // Reverse dragging for Footer + }); + + const { height: resizedHeight, isDragging } = getResizeState(); + + + + useEffect(() => { + if (isDragging) { + showGridLinesOnSlot(id); + } else { + hideGridLinesOnSlot(id); + } + }, [isDragging, id]); + + const canvasHeight = parseInt(resizedHeight, 10) / 10; + + return ( +
+
+ +
+
+ + {isDisabled && ( +
{}} + onDrop={(e) => e.stopPropagation()} + /> + )} +
+ ); + } +); diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index afeb4cf844..ffa2373a96 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -14,6 +14,9 @@ import { CONTAINER_FORM_CANVAS_PADDING, SUBCONTAINER_CANVAS_BORDER_WIDTH, } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import { HorizontalSlot } from './Components/HorizontalSlot'; +import { useActiveSlot } from '@/AppBuilder/_hooks/useActiveSlot'; + import './form.scss'; const getCanvasHeight = (height) => { @@ -35,6 +38,7 @@ export const Form = function Form(props) { properties, resetComponent = () => {}, dataCy, + onComponentClick, } = props; const childComponents = useStore((state) => state.getChildComponents(id), shallow); const { @@ -46,16 +50,7 @@ export const Form = function Form(props) { footerBackgroundColor, headerBackgroundColor, } = styles; - const { - buttonToSubmit, - loadingState, - advanced, - JSONSchema, - showHeader = false, - showFooter = false, - visibility, - disabledState, - } = properties; + const { buttonToSubmit, advanced, JSONSchema, showHeader = false, showFooter = false } = properties; const { isDisabled, isVisible, isLoading } = useExposeState( properties.loadingState, properties.visibility, @@ -76,16 +71,6 @@ export const Form = function Form(props) { flexDirection: 'column', }; - const formHeader = { - flexShrink: 0, - paddingBottom: '3px', - paddingTop: '7px', - paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, - paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, - backgroundColor: - ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, - }; - const formContent = { overflow: 'hidden auto', display: 'flex', @@ -96,13 +81,6 @@ export const Form = function Form(props) { paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, }; - const formFooter = { - flexShrink: 0, - padding: `${CONTAINER_FORM_CANVAS_PADDING}px`, - backgroundColor: - ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, - }; - const parentRef = useRef(null); const childDataRef = useRef({}); @@ -110,7 +88,6 @@ export const Form = function Form(props) { const [isValid, setValidation] = useState(true); const [uiComponents, setUIComponents] = useState([]); const mounted = useMounted(); - const canvasHeaderHeight = getCanvasHeight(headerHeight) / 10; const canvasFooterHeight = getCanvasHeight(footerHeight) / 10; useEffect(() => { @@ -287,6 +264,38 @@ export const Form = function Form(props) { setChildrenData(childDataRef.current); }; + const activeSlot = useActiveSlot(id); // Track the active slot for this widget + const setComponentProperty = useStore((state) => state.setComponentProperty, shallow); + const updateHeaderSizeInStore = ({ newHeight }) => { + const heightInPx = `${parseInt(newHeight, 10)}px`; + console.log('newHeight', newHeight); + setComponentProperty(id, `headerHeight`, heightInPx, 'properties', 'value', false); + }; + + const updateFooterSizeInStore = ({ newHeight }) => { + const heightInPx = `${parseInt(newHeight, 10)}px`; + console.log('newHeight', newHeight); + setComponentProperty(id, `footerHeight`, heightInPx, 'properties', 'value', false); + }; + const formFooter = { + flexShrink: 0, + paddingTop: '3px', + paddingBottom: '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + backgroundColor: + ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, + }; + const formHeader = { + flexShrink: 0, + paddingBottom: '3px', + paddingTop: '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + backgroundColor: + ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, + }; + return (
{showHeader && ( -
- - {isDisabled && ( -
{}} - onDrop={(e) => e.stopPropagation()} - /> - )} -
+ )} +
{isLoading ? (
@@ -382,30 +381,18 @@ export const Form = function Form(props) { )}
{showFooter && ( -
- - {isDisabled && ( - + )} ); diff --git a/frontend/src/AppBuilder/Widgets/Form/form.scss b/frontend/src/AppBuilder/Widgets/Form/form.scss index 530e837eb2..1758e1d0d1 100644 --- a/frontend/src/AppBuilder/Widgets/Form/form.scss +++ b/frontend/src/AppBuilder/Widgets/Form/form.scss @@ -1,3 +1,7 @@ +.jet-form-body { + background-color: inherit; +} + .wj-form-header { position: relative; &::after { @@ -38,3 +42,44 @@ box-sizing: content-box; padding: 4px 0; } + +.resizable-slot { + position: relative; + height: auto; + box-shadow: 0 0 0 1px transparent; /* Acts as a border */ + transition: box-shadow 0.15s ease-in-out; + + &:hover { + box-shadow: 0 0 0 1px var(--border-weak); + } + + &.active { + box-shadow: 0 0 0 1px var(--border-accent-strong); + } + + .resize-handle { + position: absolute; + bottom: -4px; + left: 50%; /* Center horizontally */ + transform: translateX(-50%); /* Ensure proper centering */ + width: 24px; + height: 8px; + border-radius: 4px; + background-color: var(--background-accent-strong); + cursor: ns-resize; + z-index: 1; + visibility: hidden; + transition: visibility 0.15s ease-in-out; + } + + &.active .resize-handle { + visibility: visible; + } +} +.only-bottom { +} + +.jet-form-footer .resize-handle { + top: -4px; + bottom: unset; +} diff --git a/frontend/src/AppBuilder/_hooks/useActiveSlot.js b/frontend/src/AppBuilder/_hooks/useActiveSlot.js new file mode 100644 index 0000000000..bc3269a7ca --- /dev/null +++ b/frontend/src/AppBuilder/_hooks/useActiveSlot.js @@ -0,0 +1,46 @@ +import { useState, useEffect } from 'react'; +import useStore from '@/AppBuilder/_stores/store'; +import { shallow } from 'zustand/shallow'; + +const useIsWidgetSelected = (id) => { + // Get selected components from store using shallow comparison + const selectedComponents = useStore((state) => state.selectedComponents, shallow); + + // Check if the only selected component is the provided `id` + return selectedComponents.length === 1 && selectedComponents[0] === id; +}; + + +export const useActiveSlot = (widgetId) => { + const [activeSlot, setActiveSlot] = useState(''); // Default to widget ID + const isSelected = useIsWidgetSelected(widgetId); // Check if widget is selected + useEffect(() => { + const handleClick = (event) => { + let target = event.target; + + // Traverse up to find a slot with an id + while (target && target !== document.body) { + if (target.id && target.id.startsWith('canvas-')) { + const slotId = target.id.replace(/^canvas-/, ''); // ✅ Strip "canvas-" + setActiveSlot(slotId); + return; + } + target = target.parentElement; + } + + // If no slot is found, reset to widget ID + setActiveSlot(widgetId); + }; + + // Attach single click if the widget is selected, otherwise listen for double-click + const eventType = isSelected ? 'click' : 'dblclick'; + + document.addEventListener(eventType, handleClick); + + return () => { + document.removeEventListener(eventType, handleClick); + }; + }, [widgetId, isSelected]); // Re-run when widgetId or selection state changes + + return activeSlot; +}; diff --git a/frontend/src/AppBuilder/_hooks/useMoveable.js b/frontend/src/AppBuilder/_hooks/useMoveable.js new file mode 100644 index 0000000000..ea2687e473 --- /dev/null +++ b/frontend/src/AppBuilder/_hooks/useMoveable.js @@ -0,0 +1,135 @@ +import { useRef, useState } from 'react'; + +const defaultProps = { + minHeight: 50, + maxHeight: 600, + minWidth: 50, + maxWidth: 600, + lockHorizontal: false, + lockVertical: false, + stepHeight: 10, // Default step size for height + stepWidth: 10, // Default step size for width + onResize: null, + onDragStart: null, + onDragEnd: null, + isReverseVerticalDrag: false, +}; + +export const useResizable = (options = {}) => { + const props = { ...defaultProps, ...options }; + const parentRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); // ✅ Track dragging state + + const [height, setHeight] = useState( + typeof props.initialHeight === 'string' ? props.initialHeight : `${props.initialHeight || 200}px` + ); + const [width, setWidth] = useState( + typeof props.initialWidth === 'string' ? props.initialWidth : `${props.initialWidth || 200}px` + ); + + const getRootProps = () => ({ + ref: parentRef, + style: { height, width }, + }); + + const getResizeState = () => ({ + height, + width, + isDragging, + }); + + const getHandleProps = () => { + const handleMouseDown = (e) => { + // Prevent right-click drag activation (button === 2) + if (e.button === 2) return; + + if (!parentRef.current) return; + e.stopPropagation(); + e.preventDefault(); + const startHeight = parseInt(parentRef.current.clientHeight); + const startWidth = parseInt(parentRef.current.clientWidth); + const parentWidth = parentRef.current.parentElement ? parentRef.current.parentElement.clientWidth : startWidth; + const startY = e.clientY; + const startX = e.clientX; + const isPercentage = typeof props.initialWidth === 'string' && props.initialWidth.includes('%'); + + setIsDragging(true); // ✅ Set dragging state to true + + if (props.onDragStart) { + props.onDragStart({ newHeight: startHeight, newWidth: startWidth }); + } + + const handleMouseMove = (moveEvent) => { + moveEvent.stopPropagation(); + moveEvent.preventDefault(); + let newHeight = startHeight; + let newWidth = startWidth; + + if (!props.lockVertical) { + const deltaY = props.isReverseVerticalDrag ? startY - moveEvent.clientY : moveEvent.clientY - startY; + newHeight = startHeight + deltaY; + newHeight = Math.max(props.minHeight, Math.min(props.maxHeight, newHeight)); + newHeight = Math.round(newHeight / props.stepHeight) * props.stepHeight; // Snap to stepHeight + } + + if (!props.lockHorizontal) { + newWidth = startWidth + (moveEvent.clientX - startX); + newWidth = Math.max(props.minWidth, Math.min(props.maxWidth, newWidth)); + newWidth = Math.round(newWidth / props.stepWidth) * props.stepWidth; // Snap to stepWidth + + if (isPercentage) { + newWidth = (newWidth / parentWidth) * 100; // Convert to percentage + newWidth = `${newWidth.toFixed(2)}%`; + } else { + newWidth = `${newWidth}px`; + } + } + + setHeight(`${newHeight}px`); + setWidth(newWidth); + + if (parentRef.current) { + parentRef.current.style.height = `${newHeight}px`; + parentRef.current.style.width = newWidth; + } + + if (props.onResize) { + props.onResize({ + newHeight, + newWidth, + heightDiff: newHeight - startHeight, + widthDiff: isPercentage + ? parseInt(newWidth) - (startWidth / parentWidth) * 100 + : parseInt(newWidth) - startWidth, + }); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); // ✅ Set dragging state to false + + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + + if (props.onDragEnd) { + // Get the updated height and width from the DOM instead of relying on state + const finalHeight = parentRef.current ? parseInt(parentRef.current.clientHeight) : parseInt(height); + const finalWidth = parentRef.current ? parseInt(parentRef.current.clientWidth) : parseInt(width); + + props.onDragEnd({ newHeight: finalHeight, newWidth: finalWidth }); + } + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + return { + onMouseDown: handleMouseDown, + }; + }; + + return { rootRef: parentRef, getRootProps, getHandleProps, getResizeState }; +}; + +export default useResizable; diff --git a/frontend/src/Editor/WidgetManager/configs/form.js b/frontend/src/Editor/WidgetManager/configs/form.js index 2d8eb7f0a8..c5194822b6 100644 --- a/frontend/src/Editor/WidgetManager/configs/form.js +++ b/frontend/src/Editor/WidgetManager/configs/form.js @@ -294,6 +294,13 @@ export const formConfig = { defaultValue: false, }, }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, }, events: { onSubmit: { displayName: 'On submit' }, diff --git a/server/src/modules/apps/services/widget-config/form.js b/server/src/modules/apps/services/widget-config/form.js index 2d8eb7f0a8..c5194822b6 100644 --- a/server/src/modules/apps/services/widget-config/form.js +++ b/server/src/modules/apps/services/widget-config/form.js @@ -294,6 +294,13 @@ export const formConfig = { defaultValue: false, }, }, + tooltip: { + type: 'code', + displayName: 'Tooltip', + validation: { schema: { type: 'string' } }, + section: 'additionalActions', + placeholder: 'Enter tooltip text', + }, }, events: { onSubmit: { displayName: 'On submit' }, From 390445d5e1461f84c5eae5826a6c8904c73663bb Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 25 Mar 2025 00:07:27 +0530 Subject: [PATCH 011/297] revert submodule commit --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 1cd7afea26..715a830c7a 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 1cd7afea262f3b72de08da83e544911571fc05b9 +Subproject commit 715a830c7a8d75efc7f77106292d9e4499005b69 diff --git a/server/ee b/server/ee index e2db6a5fa9..0eefbb71a1 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e2db6a5fa9f64a9795136b2874c8041dede4b480 +Subproject commit 0eefbb71a1d5288f49641af5efaaab25970f27d1 From 7a50c41103128ef312406d5b78e510528acada0d Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Tue, 25 Mar 2025 21:52:37 +0530 Subject: [PATCH 012/297] Fixes interaction bugs with slot resizing --- .../Form/Components/HorizontalSlot.jsx | 15 ++++--- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 10 +++-- .../src/AppBuilder/Widgets/Form/form.scss | 25 ++++++++++- .../src/AppBuilder/_hooks/useActiveSlot.js | 41 ++++++++++++++++--- 4 files changed, 75 insertions(+), 16 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx b/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx index 0e15e4a058..e892dc4f9f 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Components/HorizontalSlot.jsx @@ -13,15 +13,16 @@ export const HorizontalSlot = React.memo( isActive, slotName = 'header', // 'header' or 'footer' slotStyle = {}, - onResize + onResize, + maxHeight, }) => { const parsedHeight = parseInt(height, 10); const { getRootProps, getHandleProps, getResizeState } = useResizable({ initialHeight: parsedHeight, initialWidth: '100%', // Now respects parent's width - minHeight: 40, - maxHeight: 400, + minHeight: 10, + maxHeight: maxHeight || 400, maxWidth: '100%', stepHeight: 10, // Height will change in steps of 10px onResize: () => {}, @@ -33,8 +34,6 @@ export const HorizontalSlot = React.memo( const { height: resizedHeight, isDragging } = getResizeState(); - - useEffect(() => { if (isDragging) { showGridLinesOnSlot(id); @@ -47,7 +46,10 @@ export const HorizontalSlot = React.memo( return (
-
+
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index ffa2373a96..1328fc195d 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -268,21 +268,24 @@ export const Form = function Form(props) { const setComponentProperty = useStore((state) => state.setComponentProperty, shallow); const updateHeaderSizeInStore = ({ newHeight }) => { const heightInPx = `${parseInt(newHeight, 10)}px`; - console.log('newHeight', newHeight); setComponentProperty(id, `headerHeight`, heightInPx, 'properties', 'value', false); }; const updateFooterSizeInStore = ({ newHeight }) => { const heightInPx = `${parseInt(newHeight, 10)}px`; - console.log('newHeight', newHeight); setComponentProperty(id, `footerHeight`, heightInPx, 'properties', 'value', false); }; + + // debugger; + const headerMaxHeight = parseInt(height, 10) - parseInt(footerHeight, 10) - 100 - 10; + const footerMaxHeight = parseInt(height, 10) - parseInt(headerHeight, 10) - 100 - 10; const formFooter = { flexShrink: 0, paddingTop: '3px', paddingBottom: '7px', paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + maxHeight: `${footerMaxHeight}px`, backgroundColor: ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, }; @@ -292,13 +295,14 @@ export const Form = function Form(props) { paddingTop: '7px', paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + maxHeight: `${headerMaxHeight}px`, backgroundColor: ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, }; return (
{ export const useActiveSlot = (widgetId) => { const [activeSlot, setActiveSlot] = useState(''); // Default to widget ID const isSelected = useIsWidgetSelected(widgetId); // Check if widget is selected + useEffect(() => { - const handleClick = (event) => { + if (!isSelected) { + setActiveSlot(''); + } + }, [isSelected]); + + useEffect(() => { + const handleDoubleClick = (event) => { let target = event.target; // Traverse up to find a slot with an id @@ -31,16 +38,40 @@ export const useActiveSlot = (widgetId) => { // If no slot is found, reset to widget ID setActiveSlot(widgetId); }; + const handleSingleClick = (event) => { + let target = event.target; + + // Traverse up to find a valid main slot (not header/footer) + while (target && target !== document.body) { + if ( + target.id && + target.id.startsWith('canvas-') && + !target.id.endsWith('-header') && + !target.id.endsWith('-footer') + ) { + const slotId = target.id.replace(/^canvas-/, ''); // Strip "canvas-" + setActiveSlot(slotId); + return; + } + target = target.parentElement; + } + + // If no main slot is found, fallback to widget ID + setActiveSlot(widgetId); + }; // Attach single click if the widget is selected, otherwise listen for double-click - const eventType = isSelected ? 'click' : 'dblclick'; + // const eventType = isSelected ? 'click' : 'dblclick'; + const eventType = 'dblclick'; - document.addEventListener(eventType, handleClick); + document.addEventListener(eventType, handleDoubleClick); + document.addEventListener('click', handleSingleClick); return () => { - document.removeEventListener(eventType, handleClick); + document.removeEventListener(eventType, handleDoubleClick); + document.removeEventListener('click', handleSingleClick); }; - }, [widgetId, isSelected]); // Re-run when widgetId or selection state changes + }, [widgetId]); // Re-run when widgetId or selection state changes return activeSlot; }; From b40b37f5c3835aab55b4c512fd88afec12682a1b Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 26 Mar 2025 11:45:12 +0530 Subject: [PATCH 013/297] add padding --- .../WidgetManager/widgets/buttonGroup.js | 14 ++++++++++++++ .../AppBuilder/WidgetManager/widgets/checkbox.js | 15 +++++++++++++++ .../WidgetManager/widgets/colorPicker.js | 14 ++++++++++++++ .../src/AppBuilder/WidgetManager/widgets/icon.js | 15 +++++++++++++++ .../WidgetManager/widgets/rangeslider.js | 14 ++++++++++++++ .../WidgetManager/widgets/starrating.js | 14 ++++++++++++++ .../src/AppBuilder/WidgetManager/widgets/tags.js | 14 ++++++++++++++ .../WidgetManager/widgets/toggleswitchv2.js | 15 +++++++++++++++ .../Editor/WidgetManager/configs/buttonGroup.js | 14 ++++++++++++++ .../src/Editor/WidgetManager/configs/checkbox.js | 15 +++++++++++++++ .../Editor/WidgetManager/configs/colorPicker.js | 14 ++++++++++++++ frontend/src/Editor/WidgetManager/configs/icon.js | 15 +++++++++++++++ .../Editor/WidgetManager/configs/rangeslider.js | 14 ++++++++++++++ .../Editor/WidgetManager/configs/starrating.js | 14 ++++++++++++++ frontend/src/Editor/WidgetManager/configs/tags.js | 14 ++++++++++++++ .../WidgetManager/configs/toggleswitchv2.js | 15 +++++++++++++++ .../apps/services/widget-config/buttonGroup.js | 14 ++++++++++++++ .../apps/services/widget-config/checkbox.js | 15 +++++++++++++++ .../apps/services/widget-config/colorPicker.js | 14 ++++++++++++++ .../modules/apps/services/widget-config/icon.js | 15 +++++++++++++++ .../apps/services/widget-config/rangeslider.js | 14 ++++++++++++++ .../apps/services/widget-config/starrating.js | 14 ++++++++++++++ .../modules/apps/services/widget-config/tags.js | 14 ++++++++++++++ .../apps/services/widget-config/toggleswitchv2.js | 15 +++++++++++++++ 24 files changed, 345 insertions(+) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js index c0fa889dd5..d7f479624b 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/buttonGroup.js @@ -123,6 +123,19 @@ export const buttonGroupConfig = { defaultValue: '#007bff', }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selected: [1], @@ -148,6 +161,7 @@ export const buttonGroupConfig = { disabledState: { value: '{{false}}' }, selectedTextColor: { value: '#FFFFFF' }, selectedBackgroundColor: { value: '#4368E3' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js b/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js index c9b6424020..9f991be251 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/checkbox.js @@ -126,6 +126,20 @@ export const checkboxConfig = { ], accordian: 'label', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -189,6 +203,7 @@ export const checkboxConfig = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, validation: { mandatory: { value: '{{false}}' }, diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js b/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js index b2fddd7e4c..6d93508891 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/colorPicker.js @@ -26,6 +26,19 @@ export const colorPickerConfig = { }, styles: { visibility: { type: 'toggle', displayName: 'Visibility' }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selectedColorHex: '#000000', @@ -45,6 +58,7 @@ export const colorPickerConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/icon.js b/frontend/src/AppBuilder/WidgetManager/widgets/icon.js index aea06c976c..8c0b0880e4 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/icon.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/icon.js @@ -78,6 +78,20 @@ export const iconConfig = { }, accordian: 'Icon', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'Icon', + }, }, exposedVariables: {}, actions: [ @@ -116,6 +130,7 @@ export const iconConfig = { styles: { iconColor: { value: '#000' }, iconAlign: { value: 'center' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js b/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js index 151dca3384..541ed95209 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/rangeslider.js @@ -84,6 +84,19 @@ export const rangeSliderConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: null, @@ -111,6 +124,7 @@ export const rangeSliderConfig = { handleColor: { value: '' }, trackColor: { value: '' }, visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js b/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js index 01240d0369..d6caf8013c 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/starrating.js @@ -89,6 +89,19 @@ export const starratingConfig = { defaultValue: false, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: 0, @@ -112,6 +125,7 @@ export const starratingConfig = { labelColor: { value: '' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/tags.js b/frontend/src/AppBuilder/WidgetManager/widgets/tags.js index 8af289b23a..6479eeaad0 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/tags.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/tags.js @@ -38,6 +38,19 @@ export const tagsConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: {}, definition: { @@ -54,6 +67,7 @@ export const tagsConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js b/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js index 6753fbb50d..6f61a7645d 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/toggleswitchv2.js @@ -126,6 +126,20 @@ export const toggleSwitchV2Config = { validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, accordian: 'switch', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -187,6 +201,7 @@ export const toggleSwitchV2Config = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/buttonGroup.js b/frontend/src/Editor/WidgetManager/configs/buttonGroup.js index 65b7e77807..4a1d5ff218 100644 --- a/frontend/src/Editor/WidgetManager/configs/buttonGroup.js +++ b/frontend/src/Editor/WidgetManager/configs/buttonGroup.js @@ -123,6 +123,19 @@ export const buttonGroupConfig = { defaultValue: '#007bff', }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selected: [1], @@ -148,6 +161,7 @@ export const buttonGroupConfig = { disabledState: { value: '{{false}}' }, selectedTextColor: { value: '' }, selectedBackgroundColor: { value: '' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/checkbox.js b/frontend/src/Editor/WidgetManager/configs/checkbox.js index c9b6424020..9f991be251 100644 --- a/frontend/src/Editor/WidgetManager/configs/checkbox.js +++ b/frontend/src/Editor/WidgetManager/configs/checkbox.js @@ -126,6 +126,20 @@ export const checkboxConfig = { ], accordian: 'label', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -189,6 +203,7 @@ export const checkboxConfig = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, validation: { mandatory: { value: '{{false}}' }, diff --git a/frontend/src/Editor/WidgetManager/configs/colorPicker.js b/frontend/src/Editor/WidgetManager/configs/colorPicker.js index b2fddd7e4c..6d93508891 100644 --- a/frontend/src/Editor/WidgetManager/configs/colorPicker.js +++ b/frontend/src/Editor/WidgetManager/configs/colorPicker.js @@ -26,6 +26,19 @@ export const colorPickerConfig = { }, styles: { visibility: { type: 'toggle', displayName: 'Visibility' }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selectedColorHex: '#000000', @@ -45,6 +58,7 @@ export const colorPickerConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/icon.js b/frontend/src/Editor/WidgetManager/configs/icon.js index aea06c976c..8c0b0880e4 100644 --- a/frontend/src/Editor/WidgetManager/configs/icon.js +++ b/frontend/src/Editor/WidgetManager/configs/icon.js @@ -78,6 +78,20 @@ export const iconConfig = { }, accordian: 'Icon', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'Icon', + }, }, exposedVariables: {}, actions: [ @@ -116,6 +130,7 @@ export const iconConfig = { styles: { iconColor: { value: '#000' }, iconAlign: { value: 'center' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/rangeslider.js b/frontend/src/Editor/WidgetManager/configs/rangeslider.js index 151dca3384..541ed95209 100644 --- a/frontend/src/Editor/WidgetManager/configs/rangeslider.js +++ b/frontend/src/Editor/WidgetManager/configs/rangeslider.js @@ -84,6 +84,19 @@ export const rangeSliderConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: null, @@ -111,6 +124,7 @@ export const rangeSliderConfig = { handleColor: { value: '' }, trackColor: { value: '' }, visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/starrating.js b/frontend/src/Editor/WidgetManager/configs/starrating.js index 01240d0369..d6caf8013c 100644 --- a/frontend/src/Editor/WidgetManager/configs/starrating.js +++ b/frontend/src/Editor/WidgetManager/configs/starrating.js @@ -89,6 +89,19 @@ export const starratingConfig = { defaultValue: false, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: 0, @@ -112,6 +125,7 @@ export const starratingConfig = { labelColor: { value: '' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/tags.js b/frontend/src/Editor/WidgetManager/configs/tags.js index 8af289b23a..6479eeaad0 100644 --- a/frontend/src/Editor/WidgetManager/configs/tags.js +++ b/frontend/src/Editor/WidgetManager/configs/tags.js @@ -38,6 +38,19 @@ export const tagsConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: {}, definition: { @@ -54,6 +67,7 @@ export const tagsConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js b/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js index 6753fbb50d..6f61a7645d 100644 --- a/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js +++ b/frontend/src/Editor/WidgetManager/configs/toggleswitchv2.js @@ -126,6 +126,20 @@ export const toggleSwitchV2Config = { validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, accordian: 'switch', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -187,6 +201,7 @@ export const toggleSwitchV2Config = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/buttonGroup.js b/server/src/modules/apps/services/widget-config/buttonGroup.js index c0fa889dd5..d7f479624b 100644 --- a/server/src/modules/apps/services/widget-config/buttonGroup.js +++ b/server/src/modules/apps/services/widget-config/buttonGroup.js @@ -123,6 +123,19 @@ export const buttonGroupConfig = { defaultValue: '#007bff', }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selected: [1], @@ -148,6 +161,7 @@ export const buttonGroupConfig = { disabledState: { value: '{{false}}' }, selectedTextColor: { value: '#FFFFFF' }, selectedBackgroundColor: { value: '#4368E3' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/checkbox.js b/server/src/modules/apps/services/widget-config/checkbox.js index c9b6424020..9f991be251 100644 --- a/server/src/modules/apps/services/widget-config/checkbox.js +++ b/server/src/modules/apps/services/widget-config/checkbox.js @@ -126,6 +126,20 @@ export const checkboxConfig = { ], accordian: 'label', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -189,6 +203,7 @@ export const checkboxConfig = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, validation: { mandatory: { value: '{{false}}' }, diff --git a/server/src/modules/apps/services/widget-config/colorPicker.js b/server/src/modules/apps/services/widget-config/colorPicker.js index b2fddd7e4c..6d93508891 100644 --- a/server/src/modules/apps/services/widget-config/colorPicker.js +++ b/server/src/modules/apps/services/widget-config/colorPicker.js @@ -26,6 +26,19 @@ export const colorPickerConfig = { }, styles: { visibility: { type: 'toggle', displayName: 'Visibility' }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { selectedColorHex: '#000000', @@ -45,6 +58,7 @@ export const colorPickerConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/icon.js b/server/src/modules/apps/services/widget-config/icon.js index aea06c976c..8c0b0880e4 100644 --- a/server/src/modules/apps/services/widget-config/icon.js +++ b/server/src/modules/apps/services/widget-config/icon.js @@ -78,6 +78,20 @@ export const iconConfig = { }, accordian: 'Icon', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'Icon', + }, }, exposedVariables: {}, actions: [ @@ -116,6 +130,7 @@ export const iconConfig = { styles: { iconColor: { value: '#000' }, iconAlign: { value: 'center' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/rangeslider.js b/server/src/modules/apps/services/widget-config/rangeslider.js index 151dca3384..541ed95209 100644 --- a/server/src/modules/apps/services/widget-config/rangeslider.js +++ b/server/src/modules/apps/services/widget-config/rangeslider.js @@ -84,6 +84,19 @@ export const rangeSliderConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: null, @@ -111,6 +124,7 @@ export const rangeSliderConfig = { handleColor: { value: '' }, trackColor: { value: '' }, visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/starrating.js b/server/src/modules/apps/services/widget-config/starrating.js index 01240d0369..d6caf8013c 100644 --- a/server/src/modules/apps/services/widget-config/starrating.js +++ b/server/src/modules/apps/services/widget-config/starrating.js @@ -89,6 +89,19 @@ export const starratingConfig = { defaultValue: false, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: { value: 0, @@ -112,6 +125,7 @@ export const starratingConfig = { labelColor: { value: '' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/tags.js b/server/src/modules/apps/services/widget-config/tags.js index 8af289b23a..6479eeaad0 100644 --- a/server/src/modules/apps/services/widget-config/tags.js +++ b/server/src/modules/apps/services/widget-config/tags.js @@ -38,6 +38,19 @@ export const tagsConfig = { defaultValue: true, }, }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + }, }, exposedVariables: {}, definition: { @@ -54,6 +67,7 @@ export const tagsConfig = { events: [], styles: { visibility: { value: '{{true}}' }, + padding: { value: 'default' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/toggleswitchv2.js b/server/src/modules/apps/services/widget-config/toggleswitchv2.js index 6753fbb50d..6f61a7645d 100644 --- a/server/src/modules/apps/services/widget-config/toggleswitchv2.js +++ b/server/src/modules/apps/services/widget-config/toggleswitchv2.js @@ -126,6 +126,20 @@ export const toggleSwitchV2Config = { validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] } }, accordian: 'switch', }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, + isFxNotRequired: true, + options: [ + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, + ], + accordian: 'switch', + }, }, exposedVariables: { value: false, @@ -187,6 +201,7 @@ export const toggleSwitchV2Config = { handleColor: { value: '#FFFFFF' }, alignment: { value: 'right' }, boxShadow: { value: '0px 0px 0px 0px #00000090' }, + padding: { value: 'default' }, }, }, }; From f2dd8343a056a6664938a7f6fd7bd55f43a2605d Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 26 Mar 2025 15:00:54 +0530 Subject: [PATCH 014/297] Adjust height for Icon and color picker widget --- frontend/src/Editor/Components/ColorPicker.jsx | 4 ++-- frontend/src/Editor/Components/Icon.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/Editor/Components/ColorPicker.jsx b/frontend/src/Editor/Components/ColorPicker.jsx index 58282490f1..fb5be51f5e 100644 --- a/frontend/src/Editor/Components/ColorPicker.jsx +++ b/frontend/src/Editor/Components/ColorPicker.jsx @@ -161,8 +161,8 @@ export const ColorPicker = function ({ : { display: 'none' }; return ( -
-
+
+
setShowColorPicker(true)} diff --git a/frontend/src/Editor/Components/Icon.jsx b/frontend/src/Editor/Components/Icon.jsx index 289e9d1194..8ebb400aed 100644 --- a/frontend/src/Editor/Components/Icon.jsx +++ b/frontend/src/Editor/Components/Icon.jsx @@ -84,7 +84,7 @@ export const Icon = ({
) : (
{ From d962755142266c317ad2230d470b7fdd10c9fbf3 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 27 Mar 2025 13:59:48 +0530 Subject: [PATCH 015/297] Fix dropdown and multiselect menuPlacement in modals when menus don't have bottom space --- frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx | 2 ++ frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx index a01ed895e0..a1d15ad44a 100644 --- a/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx +++ b/frontend/src/Editor/Components/DropdownV2/DropdownV2.jsx @@ -469,6 +469,8 @@ export const DropdownV2 = ({ menuPlacement="auto" onMenuOpen={() => fireEvent('onFocus')} onMenuClose={() => fireEvent('onBlur')} + // This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal) + minMenuHeight={300} />
diff --git a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx index 6ef45e84d9..855703dfe3 100644 --- a/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx +++ b/frontend/src/Editor/Components/MultiselectV2/MultiselectV2.jsx @@ -513,6 +513,8 @@ export const MultiselectV2 = ({ fireEvent={() => fireEvent('onSelect')} menuPlacement="auto" menuPortalTarget={document.body} + // This is not setting minheight, required to help calculate menuPlacement by providing fixed height upfront before rendering (required in the case of modal) + minMenuHeight={300} />
From 86355f6f9bb3a921f35b6470c8356fd309a41849 Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Fri, 28 Mar 2025 16:45:54 +0530 Subject: [PATCH 016/297] chore: Adds new design for form, container default children (#12239) Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> --- .../WidgetManager/widgets/container.js | 18 +- .../AppBuilder/WidgetManager/widgets/form.js | 266 +++++------------- .../Widgets/Container/Container.jsx | 3 +- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 21 +- .../src/AppBuilder/Widgets/Form/form.scss | 8 +- .../Editor/WidgetManager/configs/container.js | 18 +- .../src/Editor/WidgetManager/configs/form.js | 266 +++++------------- .../apps/services/widget-config/container.js | 20 +- .../apps/services/widget-config/form.js | 266 +++++------------- 9 files changed, 252 insertions(+), 634 deletions(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js index 37a895f553..d1670b8a93 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js @@ -3,7 +3,7 @@ export const containerConfig = { displayName: 'Container', description: 'Group components', defaultSize: { - width: 5, + width: 10, height: 200, }, component: 'Container', @@ -47,10 +47,16 @@ export const containerConfig = { defaultValue: true, }, }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, }, defaultChildren: [ { componentName: 'Text', + slotName: 'header', layout: { top: 20, left: 1, @@ -97,15 +103,6 @@ export const containerConfig = { }, accordian: 'container', }, - headerHeight: { - type: 'numberInput', - displayName: 'Height', - validation: { - schema: { type: 'number' }, - defaultValue: 80, - }, - accordian: 'header', - }, borderRadius: { type: 'numberInput', displayName: 'Border', @@ -157,6 +154,7 @@ export const containerConfig = { loadingState: { value: `{{false}}` }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: `{{80}}` }, }, events: [], styles: { diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js index c5194822b6..f28044d52c 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js @@ -4,7 +4,7 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 480, + height: 450, }, defaultChildren: [ { @@ -19,7 +19,7 @@ export const formConfig = { accessorKey: 'text', styles: ['fontWeight', 'textSize', 'textColor'], defaultValue: { - text: 'Form title', + text: 'Form', textSize: 20, textColor: '#000', }, @@ -34,203 +34,83 @@ export const formConfig = { }, properties: ['text'], defaultValue: { - text: 'Button2', + text: 'Submit', padding: 'none', }, }, - { - componentName: 'Text', - layout: { - top: 40, - left: 10, - height: 30, - width: 17, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'User Details', - fontWeight: 'bold', - textSize: 18, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 90, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Name', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 160, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Age', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, { componentName: 'TextInput', layout: { - top: 120, - left: 10, - height: 30, - width: 25, + top: 20, + left: 5, + height: 40, + width: 31, }, properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { placeholder: 'Enter your name', - label: '', + label: 'Name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { componentName: 'NumberInput', layout: { - top: 190, - left: 10, - height: 30, - width: 25, + top: 80, + left: 5, + height: 40, + width: 31, }, - properties: ['value', 'label'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - value: 24, - label: '', + placeholder: 'Age', + label: 'Age', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { - componentName: 'Button', + componentName: 'TextInput', layout: { - top: 240, - left: 10, - height: 30, - width: 10, + top: 140, + left: 5, + height: 40, + width: 31, }, - properties: ['text'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - text: 'Submit', + placeholder: 'Tomy', + label: 'Pet name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', + }, + }, + { + componentName: 'TextInput', + layout: { + top: 200, + left: 5, + height: 40, + width: 31, + }, + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto'], + defaultValue: { + label: 'Favorite color?', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, ], @@ -276,6 +156,16 @@ export const formConfig = { }, showHeader: { type: 'toggle', displayName: 'Header' }, showFooter: { type: 'toggle', displayName: 'Footer' }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, + footerHeight: { + type: 'numberInput', + displayName: 'Footer height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, visibility: { type: 'toggle', displayName: 'Visibility', @@ -323,22 +213,6 @@ export const formConfig = { defaultValue: '#ffffffff', }, }, - headerHeight: { - type: 'code', - displayName: 'Header height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, - footerHeight: { - type: 'code', - displayName: 'Footer height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -410,18 +284,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, - showHeader: { value: '{{false}}' }, - showFooter: { value: '{{false}}' }, + showHeader: { value: '{{true}}' }, + showFooter: { value: '{{true}}' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: 60 }, + footerHeight: { value: 60 }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - headerHeight: { value: '60px' }, - footerHeight: { value: '60px' }, }, }, }; diff --git a/frontend/src/AppBuilder/Widgets/Container/Container.jsx b/frontend/src/AppBuilder/Widgets/Container/Container.jsx index 4978427370..a706d29069 100644 --- a/frontend/src/AppBuilder/Widgets/Container/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container/Container.jsx @@ -33,7 +33,8 @@ export const Container = ({ shallow ); - const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles; + const { borderRadius, borderColor, boxShadow } = styles; + const { headerHeight = 80 } = properties; const contentBgColor = useMemo(() => { return { backgroundColor: diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index 1328fc195d..d918a1a2b5 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -41,16 +41,16 @@ export const Form = function Form(props) { onComponentClick, } = props; const childComponents = useStore((state) => state.getChildComponents(id), shallow); + const { borderRadius, borderColor, boxShadow, footerBackgroundColor, headerBackgroundColor } = styles; const { - borderRadius, - borderColor, - boxShadow, - headerHeight, - footerHeight, - footerBackgroundColor, - headerBackgroundColor, - } = styles; - const { buttonToSubmit, advanced, JSONSchema, showHeader = false, showFooter = false } = properties; + buttonToSubmit, + advanced, + JSONSchema, + showHeader = false, + showFooter = false, + headerHeight = 80, + footerHeight = 80, + } = properties; const { isDisabled, isVisible, isLoading } = useExposeState( properties.loadingState, properties.visibility, @@ -88,7 +88,8 @@ export const Form = function Form(props) { const [isValid, setValidation] = useState(true); const [uiComponents, setUIComponents] = useState([]); const mounted = useMounted(); - const canvasFooterHeight = getCanvasHeight(footerHeight) / 10; + const canvasHeaderHeight = headerHeight / 10; + const canvasFooterHeight = footerHeight / 10; useEffect(() => { const exposedVariables = { diff --git a/frontend/src/AppBuilder/Widgets/Form/form.scss b/frontend/src/AppBuilder/Widgets/Form/form.scss index e1e694d7c0..766b309a7f 100644 --- a/frontend/src/AppBuilder/Widgets/Form/form.scss +++ b/frontend/src/AppBuilder/Widgets/Form/form.scss @@ -10,8 +10,8 @@ content: ""; position: absolute; bottom: 0; - left: -7px; - right: -7px; + left: -2px; + right: -2px; height: 1px; background-color: var(--border-weak); } @@ -23,8 +23,8 @@ content: ""; position: absolute; top: 0; - left: -7px; - right: -7px; + left: -2px; + right: -2px; height: 1px; background-color: var(--border-weak); } diff --git a/frontend/src/Editor/WidgetManager/configs/container.js b/frontend/src/Editor/WidgetManager/configs/container.js index 37a895f553..d1670b8a93 100644 --- a/frontend/src/Editor/WidgetManager/configs/container.js +++ b/frontend/src/Editor/WidgetManager/configs/container.js @@ -3,7 +3,7 @@ export const containerConfig = { displayName: 'Container', description: 'Group components', defaultSize: { - width: 5, + width: 10, height: 200, }, component: 'Container', @@ -47,10 +47,16 @@ export const containerConfig = { defaultValue: true, }, }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, }, defaultChildren: [ { componentName: 'Text', + slotName: 'header', layout: { top: 20, left: 1, @@ -97,15 +103,6 @@ export const containerConfig = { }, accordian: 'container', }, - headerHeight: { - type: 'numberInput', - displayName: 'Height', - validation: { - schema: { type: 'number' }, - defaultValue: 80, - }, - accordian: 'header', - }, borderRadius: { type: 'numberInput', displayName: 'Border', @@ -157,6 +154,7 @@ export const containerConfig = { loadingState: { value: `{{false}}` }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: `{{80}}` }, }, events: [], styles: { diff --git a/frontend/src/Editor/WidgetManager/configs/form.js b/frontend/src/Editor/WidgetManager/configs/form.js index c5194822b6..f28044d52c 100644 --- a/frontend/src/Editor/WidgetManager/configs/form.js +++ b/frontend/src/Editor/WidgetManager/configs/form.js @@ -4,7 +4,7 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 480, + height: 450, }, defaultChildren: [ { @@ -19,7 +19,7 @@ export const formConfig = { accessorKey: 'text', styles: ['fontWeight', 'textSize', 'textColor'], defaultValue: { - text: 'Form title', + text: 'Form', textSize: 20, textColor: '#000', }, @@ -34,203 +34,83 @@ export const formConfig = { }, properties: ['text'], defaultValue: { - text: 'Button2', + text: 'Submit', padding: 'none', }, }, - { - componentName: 'Text', - layout: { - top: 40, - left: 10, - height: 30, - width: 17, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'User Details', - fontWeight: 'bold', - textSize: 18, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 90, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Name', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 160, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Age', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, { componentName: 'TextInput', layout: { - top: 120, - left: 10, - height: 30, - width: 25, + top: 20, + left: 5, + height: 40, + width: 31, }, properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { placeholder: 'Enter your name', - label: '', + label: 'Name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { componentName: 'NumberInput', layout: { - top: 190, - left: 10, - height: 30, - width: 25, + top: 80, + left: 5, + height: 40, + width: 31, }, - properties: ['value', 'label'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - value: 24, - label: '', + placeholder: 'Age', + label: 'Age', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { - componentName: 'Button', + componentName: 'TextInput', layout: { - top: 240, - left: 10, - height: 30, - width: 10, + top: 140, + left: 5, + height: 40, + width: 31, }, - properties: ['text'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - text: 'Submit', + placeholder: 'Tomy', + label: 'Pet name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', + }, + }, + { + componentName: 'TextInput', + layout: { + top: 200, + left: 5, + height: 40, + width: 31, + }, + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto'], + defaultValue: { + label: 'Favorite color?', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, ], @@ -276,6 +156,16 @@ export const formConfig = { }, showHeader: { type: 'toggle', displayName: 'Header' }, showFooter: { type: 'toggle', displayName: 'Footer' }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, + footerHeight: { + type: 'numberInput', + displayName: 'Footer height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, visibility: { type: 'toggle', displayName: 'Visibility', @@ -323,22 +213,6 @@ export const formConfig = { defaultValue: '#ffffffff', }, }, - headerHeight: { - type: 'code', - displayName: 'Header height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, - footerHeight: { - type: 'code', - displayName: 'Footer height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -410,18 +284,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, - showHeader: { value: '{{false}}' }, - showFooter: { value: '{{false}}' }, + showHeader: { value: '{{true}}' }, + showFooter: { value: '{{true}}' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: 60 }, + footerHeight: { value: 60 }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - headerHeight: { value: '60px' }, - footerHeight: { value: '60px' }, }, }, }; diff --git a/server/src/modules/apps/services/widget-config/container.js b/server/src/modules/apps/services/widget-config/container.js index ec1d5174b0..d1670b8a93 100644 --- a/server/src/modules/apps/services/widget-config/container.js +++ b/server/src/modules/apps/services/widget-config/container.js @@ -3,7 +3,7 @@ export const containerConfig = { displayName: 'Container', description: 'Group components', defaultSize: { - width: 5, + width: 10, height: 200, }, component: 'Container', @@ -47,10 +47,16 @@ export const containerConfig = { defaultValue: true, }, }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, }, defaultChildren: [ { componentName: 'Text', + slotName: 'header', layout: { top: 20, left: 1, @@ -97,15 +103,6 @@ export const containerConfig = { }, accordian: 'container', }, - headerHeight: { - type: 'numberInput', - displayName: 'Height', - validation: { - schema: { type: 'number' }, - defaultValue: 80, - }, - accordian: 'header', - }, borderRadius: { type: 'numberInput', displayName: 'Border', @@ -153,10 +150,11 @@ export const containerConfig = { showOnMobile: { value: '{{false}}' }, }, properties: { - showHeader: {value: `{{true}}`}, + showHeader: { value: `{{true}}` }, loadingState: { value: `{{false}}` }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: `{{80}}` }, }, events: [], styles: { diff --git a/server/src/modules/apps/services/widget-config/form.js b/server/src/modules/apps/services/widget-config/form.js index c5194822b6..f28044d52c 100644 --- a/server/src/modules/apps/services/widget-config/form.js +++ b/server/src/modules/apps/services/widget-config/form.js @@ -4,7 +4,7 @@ export const formConfig = { description: 'Wrapper for multiple components', defaultSize: { width: 13, - height: 480, + height: 450, }, defaultChildren: [ { @@ -19,7 +19,7 @@ export const formConfig = { accessorKey: 'text', styles: ['fontWeight', 'textSize', 'textColor'], defaultValue: { - text: 'Form title', + text: 'Form', textSize: 20, textColor: '#000', }, @@ -34,203 +34,83 @@ export const formConfig = { }, properties: ['text'], defaultValue: { - text: 'Button2', + text: 'Submit', padding: 'none', }, }, - { - componentName: 'Text', - layout: { - top: 40, - left: 10, - height: 30, - width: 17, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'User Details', - fontWeight: 'bold', - textSize: 18, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 90, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Name', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, - { - componentName: 'Text', - layout: { - top: 160, - left: 10, - height: 30, - }, - properties: ['text'], - styles: [ - 'textSize', - 'fontWeight', - 'fontStyle', - 'textColor', - 'isScrollRequired', - 'lineHeight', - 'textIndent', - 'textAlign', - 'verticalAlignment', - 'decoration', - 'transformation', - 'letterSpacing', - 'wordSpacing', - 'fontVariant', - 'backgroundColor', - 'borderColor', - 'borderRadius', - 'boxShadow', - 'padding', - ], - defaultValue: { - text: 'Age', - fontWeight: 'normal', - textSize: 14, - textColor: '#000', - backgroundColor: '#fff00000', - textAlign: 'left', - decoration: 'none', - transformation: 'none', - fontStyle: 'normal', - lineHeight: 1.5, - textIndent: '0', - letterSpacing: '0', - wordSpacing: '0', - fontVariant: 'normal', - verticalAlignment: 'top', - padding: 'default', - boxShadow: '0px 0px 0px 0px #00000090', - borderRadius: '0', - isScrollRequired: 'enabled', - }, - }, { componentName: 'TextInput', layout: { - top: 120, - left: 10, - height: 30, - width: 25, + top: 20, + left: 5, + height: 40, + width: 31, }, properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { placeholder: 'Enter your name', - label: '', + label: 'Name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { componentName: 'NumberInput', layout: { - top: 190, - left: 10, - height: 30, - width: 25, + top: 80, + left: 5, + height: 40, + width: 31, }, - properties: ['value', 'label'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - value: 24, - label: '', + placeholder: 'Age', + label: 'Age', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, { - componentName: 'Button', + componentName: 'TextInput', layout: { - top: 240, - left: 10, - height: 30, - width: 10, + top: 140, + left: 5, + height: 40, + width: 31, }, - properties: ['text'], + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto', 'padding'], defaultValue: { - text: 'Submit', + placeholder: 'Tomy', + label: 'Pet name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', + }, + }, + { + componentName: 'TextInput', + layout: { + top: 200, + left: 5, + height: 40, + width: 31, + }, + properties: ['placeholder', 'label'], + styles: ['alignment', 'width', 'auto'], + defaultValue: { + label: 'Favorite color?', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', }, }, ], @@ -276,6 +156,16 @@ export const formConfig = { }, showHeader: { type: 'toggle', displayName: 'Header' }, showFooter: { type: 'toggle', displayName: 'Footer' }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, + footerHeight: { + type: 'numberInput', + displayName: 'Footer height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + }, visibility: { type: 'toggle', displayName: 'Visibility', @@ -323,22 +213,6 @@ export const formConfig = { defaultValue: '#ffffffff', }, }, - headerHeight: { - type: 'code', - displayName: 'Header height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, - footerHeight: { - type: 'code', - displayName: 'Footer height', - validation: { - schema: { type: 'string' }, - defaultValue: '80px', - }, - }, backgroundColor: { type: 'color', displayName: 'Background color', @@ -410,18 +284,18 @@ export const formConfig = { value: "{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}", }, - showHeader: { value: '{{false}}' }, - showFooter: { value: '{{false}}' }, + showHeader: { value: '{{true}}' }, + showFooter: { value: '{{true}}' }, visibility: { value: '{{true}}' }, disabledState: { value: '{{false}}' }, + headerHeight: { value: 60 }, + footerHeight: { value: 60 }, }, events: [], styles: { backgroundColor: { value: '#fff' }, borderRadius: { value: '0' }, borderColor: { value: '#fff' }, - headerHeight: { value: '60px' }, - footerHeight: { value: '60px' }, }, }, }; From 7384a96c6e0155ddba47d957ff458de3fe07fee0 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:55:15 +0530 Subject: [PATCH 017/297] Adds default height for body --- .../AppBuilder/WidgetManager/widgets/form.js | 27 +++------- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 17 +++--- .../src/AppBuilder/Widgets/Form/FormUtils.js | 20 +++++++ .../src/Editor/WidgetManager/configs/form.js | 27 +++------- .../apps/services/widget-config/form.js | 52 +++++++++++++++++-- 5 files changed, 88 insertions(+), 55 deletions(-) diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js index f28044d52c..e5996062fb 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js @@ -47,11 +47,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Enter your name', label: 'Name', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -66,11 +67,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Age', label: 'Age', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -85,30 +87,13 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Tomy', label: 'Pet name', width: '{{60}}', alignment: 'side', - auto: '{{false}}', - padding: 'default', - }, - }, - { - componentName: 'TextInput', - layout: { - top: 200, - left: 5, - height: 40, - width: 31, - }, - properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto'], - defaultValue: { - label: 'Favorite color?', - width: '{{60}}', - alignment: 'side', + direction: 'left', auto: '{{false}}', padding: 'default', }, diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index d918a1a2b5..eb09a1ad4d 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react'; import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container'; // eslint-disable-next-line import/no-unresolved import _, { debounce, omit } from 'lodash'; -import { generateUIComponents } from './FormUtils'; +import { generateUIComponents, getBodyHeight } from './FormUtils'; import { useMounted } from '@/_hooks/use-mount'; import { onComponentClick, removeFunctionObjects } from '@/_helpers/appUtils'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; @@ -19,11 +19,6 @@ import { useActiveSlot } from '@/AppBuilder/_hooks/useActiveSlot'; import './form.scss'; -const getCanvasHeight = (height) => { - const parsedHeight = height.includes('px') ? parseInt(height, 10) : height; - return Math.ceil(parsedHeight); -}; - export const Form = function Form(props) { const { id, @@ -60,6 +55,9 @@ export const Form = function Form(props) { ); const backgroundColor = ['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor; + + const computedFormBodyHeight = getBodyHeight(height, showHeader, showFooter, headerHeight, footerHeight); + const computedStyles = { backgroundColor, borderRadius: borderRadius ? parseFloat(borderRadius) : 0, @@ -336,10 +334,13 @@ export const Form = function Form(props) { ) : (
{!advanced && ( -
+
{ if (/^(true|false)$/i.test(input) == true) return JSON.parse(input); return true; }; + +export const getBodyHeight = (height, showHeader, showFooter, headerHeight = 60, footerHeight = 60) => { + let modalHeight = height ? parseInt(height, 10) : 0; + let parsedHeaderHeight = showHeader ? parseInt(headerHeight, 10) : 0; + let parsedFooterHeight = showFooter ? parseInt(footerHeight, 10) : 0; + + if (showHeader) { + // 10 is header padding + modalHeight = modalHeight - parsedHeaderHeight - 10; + } + if (showFooter) { + // 14 is footer padding + modalHeight = modalHeight - parsedFooterHeight - 14; + } + + const rounded = Math.ceil(modalHeight / 10) * 10; + + console.log('rounded', rounded) + return `${Math.max(rounded - 20, 40)}px`; +}; diff --git a/frontend/src/Editor/WidgetManager/configs/form.js b/frontend/src/Editor/WidgetManager/configs/form.js index f28044d52c..e5996062fb 100644 --- a/frontend/src/Editor/WidgetManager/configs/form.js +++ b/frontend/src/Editor/WidgetManager/configs/form.js @@ -47,11 +47,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Enter your name', label: 'Name', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -66,11 +67,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Age', label: 'Age', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -85,30 +87,13 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Tomy', label: 'Pet name', width: '{{60}}', alignment: 'side', - auto: '{{false}}', - padding: 'default', - }, - }, - { - componentName: 'TextInput', - layout: { - top: 200, - left: 5, - height: 40, - width: 31, - }, - properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto'], - defaultValue: { - label: 'Favorite color?', - width: '{{60}}', - alignment: 'side', + direction: 'left', auto: '{{false}}', padding: 'default', }, diff --git a/server/src/modules/apps/services/widget-config/form.js b/server/src/modules/apps/services/widget-config/form.js index f28044d52c..d46c637849 100644 --- a/server/src/modules/apps/services/widget-config/form.js +++ b/server/src/modules/apps/services/widget-config/form.js @@ -47,11 +47,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Enter your name', label: 'Name', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -66,11 +67,12 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Age', label: 'Age', width: '{{60}}', + direction: 'left', alignment: 'side', auto: '{{false}}', padding: 'default', @@ -85,32 +87,72 @@ export const formConfig = { width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto', 'padding'], + styles: ['alignment', 'width', 'auto', 'padding', 'direction'], defaultValue: { placeholder: 'Tomy', label: 'Pet name', width: '{{60}}', alignment: 'side', + direction: 'left', auto: '{{false}}', padding: 'default', }, }, { - componentName: 'TextInput', + componentName: 'Text', layout: { top: 200, left: 5, + height: 30, + width: 10, + }, + properties: ['text'], + accessorKey: 'text', + styles: ['fontWeight', 'textSize', 'textColor', 'direction'], + defaultValue: { + text: 'Who are you', + textSize: 12, + direction: 'left', + textColor: '#000', + }, + }, + { + componentName: 'TextArea', + layout: { + top: 200, + left: 14, + height: 80, + width: 22, + }, + properties: ['placeholder', 'value'], + styles: ['alignment', 'width', 'auto', 'padding', 'visibility'], + defaultValue: { + placeholder: 'Tomy', + value: 'Pet name', + width: '{{60}}', + alignment: 'side', + auto: '{{false}}', + padding: 'default', + visibility: '{{true}}', + }, + }, + { + componentName: 'MultiselectV2', + layout: { + top: 400, + left: 5, height: 40, width: 31, }, properties: ['placeholder', 'label'], - styles: ['alignment', 'width', 'auto'], + styles: ['alignment', 'width', 'auto', 'direction'], defaultValue: { label: 'Favorite color?', width: '{{60}}', alignment: 'side', auto: '{{false}}', padding: 'default', + direction: 'left', }, }, ], From c9fb1f31188b9a7df749eb7b55053c2083c37b62 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Wed, 19 Mar 2025 13:45:02 +0530 Subject: [PATCH 018/297] steps v2 component --- .../Inspector/Components/Steps.jsx | 505 +++++++++++++++++ .../RightSideBar/Inspector/Inspector.jsx | 4 + .../AppBuilder/WidgetManager/widgets/steps.js | 191 ++++++- frontend/src/Editor/Components/Steps.jsx | 149 ++++- frontend/src/_styles/tabler.scss | 13 +- frontend/src/_styles/theme.scss | 514 ++++++++++-------- .../1742369436314-StepsV2Migration.ts | 81 +++ .../apps/services/component.service.ts | 4 +- .../apps/services/widget-config/steps.js | 191 ++++++- server/src/modules/apps/util.service.ts | 2 +- 10 files changed, 1339 insertions(+), 315 deletions(-) create mode 100644 frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx create mode 100644 server/data-migrations/1742369436314-StepsV2Migration.ts diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx new file mode 100644 index 0000000000..23636aec50 --- /dev/null +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx @@ -0,0 +1,505 @@ +import React, { useState, useEffect } from 'react'; +import Accordion from '@/_ui/Accordion'; +import { EventManager } from '../EventManager'; +import { renderElement } from '../Utils'; +import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; +import Popover from 'react-bootstrap/Popover'; +import List from '@/ToolJetUI/List/List'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import useStore from '@/AppBuilder/_stores/store'; +import CodeHinter from '@/AppBuilder/CodeEditor'; +import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton'; +import ListGroup from 'react-bootstrap/ListGroup'; +import { ButtonSolid } from '@/_ui/AppButton/AppButton'; +import SortableList from '@/_components/SortableList'; +import Trash from '@/_ui/Icon/solidIcons/Trash'; +import { shallow } from 'zustand/shallow'; +import Switch from '@/Editor/CodeBuilder/Elements/Switch'; + +export function Steps({ componentMeta, darkMode, ...restProps }) { + const { + layoutPropertyChanged, + component, + dataQueries, + paramUpdated, + currentState, + eventsChanged, + apps, + allComponents, + pages, + } = restProps; + const getResolvedValue = useStore((state) => state.getResolvedValue, shallow); + + const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value); + + const [options, setOptions] = useState([]); + const [hoveredOptionIndex, setHoveredOptionIndex] = useState(null); + let properties = []; + let additionalActions = []; + let optionsProperties = []; + + for (const [key] of Object.entries(componentMeta?.properties)) { + if (componentMeta?.properties[key]?.section === 'additionalActions') { + additionalActions.push(key); + } else if (componentMeta?.properties[key]?.accordian === 'Options') { + optionsProperties.push(key); + } else { + properties.push(key); + } + } + + const getItemStyle = (isDragging, draggableStyle) => ({ + userSelect: 'none', + ...draggableStyle, + }); + + const updateAllOptionsParams = (options, props) => { + paramUpdated({ name: 'steps' }, 'value', options, 'properties', false, props); + }; + + const generateNewOptions = () => { + let found = false; + let label = ''; + let currentNumber = options.length + 1; + while (!found) { + label = `step ${currentNumber}`; + if (options.find((option) => option.label === label) === undefined) { + found = true; + } + currentNumber += 1; + } + return { + name: label, + id: Number((Math.random() * 1000).toFixed(0)), + tooltip: label, + visible: { value: '{{true}}' }, + disabled: { value: '{{false}}' }, + }; + }; + + const handleAddOption = () => { + let _option = generateNewOptions(); + const _items = [...options, _option]; + setOptions(_items); + updateAllOptionsParams(_items); + }; + const handleDeleteOption = (index) => { + const _items = options.filter((option, i) => i !== index); + setOptions(_items); + updateAllOptionsParams(_items, { isParamFromDropdownOptions: true }); + }; + + const handleLabelChange = (propertyName, value, index) => { + const _options = options.map((option, i) => { + if (i === index) { + return { + ...option, + [propertyName]: value, + }; + } + return option; + }); + setOptions(_options); + updateAllOptionsParams(_options); + }; + + const reorderOptions = async (startIndex, endIndex) => { + const result = [...options]; + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + setOptions(result); + updateAllOptionsParams(result); + }; + + const onDragEnd = ({ source, destination }) => { + if (!destination || source?.index === destination?.index) { + return; + } + reorderOptions(source.index, destination.index); + }; + + const handleOnFxPress = (active, index, key) => { + const _options = options.map((option, i) => { + if (i === index) { + return { + ...option, + [key]: { + ...option[key], + fxActive: active, + }, + }; + } + return option; + }); + setOptions(_options); + updateAllOptionsParams(_options); + }; + + const _renderOverlay = (item, index) => { + return ( + + +
+ + handleLabelChange('name', value, index)} + /> +
+
+ + handleLabelChange('tooltip', value, index)} + /> +
+
+ + handleLabelChange( + 'visible', + { + value, + }, + index + ) + } + paramName={'visible'} + onFxPress={(active) => handleOnFxPress(active, index, 'visible')} + fxActive={item?.visible?.fxActive} + fieldMeta={{ + type: 'toggle', + displayName: 'Make editable', + }} + paramType={'toggle'} + /> +
+
+ handleLabelChange('disabled', { value }, index)} + onFxPress={(active) => handleOnFxPress(active, index, 'disabled')} + fxActive={item?.disabled?.fxActive} + fieldMeta={{ + type: 'toggle', + displayName: 'Make editable', + }} + paramType={'toggle'} + /> +
+
+
+ ); + }; + const _renderOptions = () => { + return ( + + { + onDragEnd(result); + }} + > + + {({ innerRef, droppableProps, placeholder }) => ( +
+ {options?.map((item, index) => { + return ( + + {(provided, snapshot) => ( +
+ +
+ setHoveredOptionIndex(index)} + onMouseLeave={() => setHoveredOptionIndex(null)} + {...restProps} + > +
+
+ +
+
+ {getResolvedValue(item.name)} +
+
+ {index === hoveredOptionIndex && ( + { + e.stopPropagation(); + handleDeleteOption(index); + }} + > + + + + + )} +
+
+
+
+
+
+ )} +
+ ); + })} + {placeholder} +
+ )} +
+
+ + Add new option + +
+ ); + }; + + const isDynamicStepsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value); + useEffect(() => { + setOptions(constructSteps()); + }, [component?.id, isDynamicStepsEnabled]); + + const constructSteps = () => { + try { + let optionsValue = isDynamicOptionsEnabled + ? component?.component?.definition?.properties?.schema?.value + : component?.component?.definition?.properties?.steps?.value; + let options = []; + + if (isDynamicOptionsEnabled || typeof optionsValue === 'string') { + options = getResolvedValue(optionsValue); + } else { + options = optionsValue?.map((option) => option); + } + return options.map((option) => { + const newOption = { ...option }; + + Object.keys(option).forEach((key) => { + if (typeof option[key]?.value === 'boolean') { + newOption[key]['value'] = `{{${option[key]?.value}}}`; + } + }); + + if (!('visible' in newOption)) { + newOption['visible'] = { value: '{{true}}' }; + } + return newOption; + }); + } catch (error) { + return []; + } + }; + + let items = []; + + items.push({ + title: 'Steps', + isOpen: true, + children: ( + <> + {properties + .filter((property) => !optionsProperties.includes(property)) + ?.map((property) => { + if (property === 'steps') { + return ( + <> + {renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + 'advanced', + 'properties', + currentState, + allComponents + )} + {isDynamicStepsEnabled + ? renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + 'schema', + 'properties', + currentState, + allComponents + ) + : _renderOptions()} + + ); + } + // else if (property === 'variant') { + // return renderTest( + // component, + // componentMeta, + // paramUpdated, + // dataQueries, + // 'variant', + // 'properties', + // currentState, + // allComponents, + // handleLabelChange + // ); + // } + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + 'properties', + currentState, + allComponents, + darkMode + ); + })} + + ), + }); + + items.push({ + title: 'Events', + isOpen: true, + children: ( + + ), + }); + items.push({ + title: `Additional Actions`, + isOpen: true, + children: additionalActions.map((property) => { + return renderElement( + component, + componentMeta, + paramUpdated, + dataQueries, + property, + 'properties', + currentState, + allComponents, + darkMode, + componentMeta.properties?.[property]?.placeholder + ); + }), + }); + + items.push({ + title: 'Devices', + isOpen: true, + children: ( + <> + {renderElement( + component, + componentMeta, + layoutPropertyChanged, + dataQueries, + 'showOnDesktop', + 'others', + currentState, + allComponents + )} + {renderElement( + component, + componentMeta, + layoutPropertyChanged, + dataQueries, + 'showOnMobile', + 'others', + currentState, + allComponents + )} + + ), + }); + + return ; +} + +function renderTest(...props) { + const [ + component, + componentMeta, + paramUpdated, + dataQueries, + param, + paramType, + currentState, + components = {}, + darkMode = false, + placeholder = '', + validationFn, + ] = props; + const value = componentMeta?.definition?.properties?.variant?.value; + return ( +
+ { + paramUpdated({ name: 'variant' }, 'value', e, 'properties', false, props); + }} + meta={{ + ...componentMeta.properties[param], + fullWidth: true, + }} + paramName={param} + isIcon={false} + component={component.component.definition.name} + /> +
+ ); +} diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx index ec3aac4309..785ee19f34 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx @@ -34,6 +34,7 @@ import Inspect from '@/_ui/Icon/solidIcons/Inspect'; import classNames from 'classnames'; import { EMPTY_ARRAY } from '@/_stores/editorStore'; import { Select } from './Components/Select'; +import { Steps } from './Components/Steps.jsx'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; import useStore from '@/AppBuilder/_stores/store'; import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes'; @@ -81,6 +82,7 @@ const NEW_REVAMPED_COMPONENTS = [ 'Container', 'ModalV2', 'Link', + 'Steps', ]; export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => { @@ -731,6 +733,8 @@ const GetAccordion = React.memo( case 'DatePickerV2': case 'TimePicker': return ; + case 'Steps': + return ; default: { return ; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/steps.js b/frontend/src/AppBuilder/WidgetManager/widgets/steps.js index a39c634919..bb75948fc9 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/steps.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/steps.js @@ -4,9 +4,30 @@ export const stepsConfig = { description: 'Step-by-step navigation aid', component: 'Steps', properties: { + variant: { + type: 'switch', + displayName: 'Variant', + validation: { schema: { type: 'string' }, defaultValue: 'titles' }, + options: [ + { displayName: 'Label', value: 'titles' }, + { displayName: 'Numbers', value: 'numbers' }, + { displayName: 'Plain', value: 'plain' }, + ], + accordian: 'label', + }, + schema: { + type: 'code', + displayName: 'Schema', + conditionallyRender: { + key: 'advanced', + value: true, + }, + accordian: 'Options', + }, steps: { type: 'code', - displayName: 'Steps', + displayName: '', + showLabel: false, validation: { schema: { type: 'array', @@ -15,6 +36,27 @@ export const stepsConfig = { defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`, }, }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + // disabledState: { + // type: 'toggle', + // displayName: 'Disable', + // validation: { schema: { type: 'boolean' }, defaultValue: true }, + // section: 'additionalActions', + // }, + advanced: { + type: 'toggle', + displayName: 'Dynamic options', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + accordian: 'Options', + }, currentStep: { type: 'code', displayName: 'Current step', @@ -30,6 +72,7 @@ export const stepsConfig = { schema: { type: 'boolean' }, defaultValue: false, }, + section: 'additionalActions', }, }, defaultSize: { @@ -40,46 +83,122 @@ export const stepsConfig = { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, + actions: [ + { + handle: 'setStep', + displayName: 'Set step', + params: [ + { + handle: 'option', + displayName: 'Option', + }, + ], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [ + { + handle: 'option', + displayName: 'Option', + }, + ], + }, + { + handle: 'resetSteps', + displayName: 'Reset steps', + params: [], + }, + { + handle: 'setStepVisible', + displayName: 'Set step visible', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'visibility', + displayName: 'visibility', + }, + ], + }, + { + handle: 'setStepDisable', + displayName: 'Set step disable', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'disabled', + displayName: 'disabled', + }, + ], + }, + ], events: { onSelect: { displayName: 'On select' }, }, styles: { - color: { + incompletedAccent: { type: 'color', - displayName: 'Color', + displayName: 'Incompleted accent', validation: { schema: { type: 'string' }, - defaultValue: '#000000', + defaultValue: '#E4E7EB', }, + accordian: 'steps', }, - textColor: { + incompletedLabel: { type: 'color', - displayName: 'Text color', + displayName: 'Incompleted label', validation: { schema: { type: 'string' }, - defaultValue: '#000000', + defaultValue: '#1B1F24', }, + accordian: 'steps', }, - theme: { - type: 'select', - displayName: 'Theme', + completedAccent: { + type: 'color', + displayName: 'Completed accent', + validation: { + schema: { type: 'string' }, + defaultValue: '#4368E3', + }, + accordian: 'steps', + }, + completedLabel: { + type: 'color', + displayName: 'Completed label', + validation: { + schema: { type: 'string' }, + defaultValue: '#FBFCFD', + }, + accordian: 'steps', + }, + currentStepLabel: { + type: 'color', + displayName: 'Current step label', + validation: { + schema: { type: 'string' }, + defaultValue: '#1B1F24', + }, + accordian: 'steps', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, options: [ - { name: 'titles', value: 'titles' }, - { name: 'numbers', value: 'numbers' }, - { name: 'plain', value: 'plain' }, + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, ], - validation: { - schema: { type: 'string' }, - defaultValue: 'titles', - }, - }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + accordian: 'container', }, }, exposedVariables: { @@ -92,17 +211,35 @@ export const stepsConfig = { }, properties: { steps: { - value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`, + value: [ + { name: 'step 1', tooltip: 'some tooltip', id: 1, visible: { value: true }, disabled: { value: false } }, + { name: 'step 2', tooltip: 'some tooltip', id: 2, visible: { value: true }, disabled: { value: false } }, + { name: 'step 3', tooltip: 'some tooltip', id: 3, visible: { value: true }, disabled: { value: false } }, + { name: 'step 4', tooltip: 'some tooltip', id: 4, visible: { value: true }, disabled: { value: false } }, + { name: 'step 5', tooltip: 'some tooltip', id: 5, visible: { value: true }, disabled: { value: false } }, + ], }, + schema: { + value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: 'some tooltip', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: 'some tooltip', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: 'some tooltip', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: 'some tooltip', id: 5,visible: true, disabled: false}]}}`, + }, + variant: { value: 'titles' }, currentStep: { value: '{{3}}' }, stepsSelectable: { value: true }, + advanced: { value: `{{false}}` }, + // disabledState: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, }, events: [], styles: { visibility: { value: '{{true}}' }, - theme: { value: 'titles' }, color: { value: '' }, textColor: { value: '' }, + padding: { value: 'default' }, + incompletedAccent: { value: '#E4E7EB' }, + incompletedLabel: { value: '#1B1F24' }, + completedAccent: { value: '#4368E3' }, + completedLabel: { value: '#1B1F24' }, + currentStepLabel: { value: '#1B1F24' }, }, }, }; diff --git a/frontend/src/Editor/Components/Steps.jsx b/frontend/src/Editor/Components/Steps.jsx index 4a42e9362e..e161e12440 100644 --- a/frontend/src/Editor/Components/Steps.jsx +++ b/frontend/src/Editor/Components/Steps.jsx @@ -1,52 +1,143 @@ import React, { useEffect, useState } from 'react'; import { isExpectedDataType } from '@/_helpers/utils'; +import { ToolTip } from '@/_components/ToolTip'; export const Steps = function Button({ properties, styles, fireEvent, setExposedVariable, height, darkMode, dataCy }) { - const { stepsSelectable } = properties; - const currentStep = isExpectedDataType(properties.currentStep, 'number'); - const steps = isExpectedDataType(properties.steps, 'array'); - const { color, theme, visibility, boxShadow } = styles; + const { stepsSelectable: disabledState } = properties; + const visibility = isExpectedDataType(properties.visibility, 'boolean'); + const currentStepId = isExpectedDataType(properties.currentStep, 'number'); + const isDynamicStepsEnabled = isExpectedDataType(properties.advanced, 'boolean'); + const steps = isDynamicStepsEnabled ? properties.schema : properties.steps; + const { color, boxShadow } = styles; const textColor = darkMode && styles.textColor === '#000' ? '#fff' : styles.textColor; - const [activeStep, setActiveStep] = useState(null); + const { completedAccent, incompletedAccent, incompletedLabel, completedLabel, currentStepLabel } = styles; + const [stepsArr, setStepsArr] = useState(steps); + const [isVisible, setIsVisible] = useState(visibility); + const [isDisabled, setIsDisabled] = useState(!disabledState); + const [activeStepId, setActiveStepId] = useState(currentStepId); + const filteredSteps = (stepsArr || []).filter((step) => step.visible); + const currentStepIndex = filteredSteps.findIndex((step) => step.id == activeStepId); + + useEffect(() => { + // this is required for legacy support where visible and disabled properties are not present + const sanitizedSteps = JSON.parse(JSON.stringify(steps || [])).map((step) => { + if (!('visible' in step)) step.visible = true; + if (!('disabled' in step)) step.disabled = false; + return step; + }); + setStepsArr(sanitizedSteps); + }, [JSON.stringify(steps)]); const dynamicStyle = { '--bgColor': styles.color, '--textColor': textColor, + '--completedAccent': completedAccent === '#4368E3' ? 'var(--primary-brand)' : completedAccent, + '--incompletedAccent': incompletedAccent === '#E4E7EB' ? 'var(--surfaces-surface-03)' : incompletedAccent, + '--incompletedLabel': incompletedLabel === '#1B1F24' ? 'var(--text-primary)' : incompletedLabel, + '--completedLabel': completedLabel === '#1B1F24' ? 'var(--text-primary)' : completedLabel, + '--currentStepLabel': currentStepLabel === '#1B1F24' ? 'var(--text-primary)' : currentStepLabel, }; + const theme = properties.variant; const activeStepHandler = (id) => { - const active = steps.filter((item) => item.id == id); - setExposedVariable('currentStepId', active[0].id); - fireEvent('onSelect'); - setActiveStep(active[0].id); + const step = filteredSteps.find((item) => item.id == id); + if (step) { + setActiveStepId(step.id); + fireEvent('onSelect'); + } }; useEffect(() => { - setActiveStep(currentStep); - setExposedVariable('currentStepId', currentStep); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentStep]); + setExposedVariable('setStep', (stepId) => setActiveStepId(stepId)); + setExposedVariable('setVisibility', (visibility) => setIsVisible(visibility)); + setExposedVariable('setDisabled', (disabled) => setIsDisabled(disabled)); + }, []); + + useEffect(() => { + setExposedVariable('currentStepId', activeStepId); + }, [activeStepId]); + + useEffect(() => { + setExposedVariable('steps', steps); + setExposedVariable('setStepVisible', (stepId, visibility) => { + setStepsArr((prev) => { + const updatedSteps = prev.map((item) => { + if (item.id == stepId) { + return { ...item, visible: visibility }; + } + return item; + }); + setExposedVariable('steps', updatedSteps); + return updatedSteps; + }); + }); + setExposedVariable('setStepDisable', (stepId, disabled) => { + setStepsArr((prev) => { + const updatedSteps = prev.map((item) => { + if (item.id == stepId) { + return { ...item, disabled: disabled }; + } + return item; + }); + setExposedVariable('steps', updatedSteps); + return updatedSteps; + }); + }); + setExposedVariable('resetSteps', () => { + setActiveStepId(stepsArr.filter((step) => step.visible)?.[0]?.id); + }); + }, [JSON.stringify(steps), JSON.stringify(stepsArr)]); + + useEffect(() => { + setExposedVariable('isVisible', isVisible); + }, [isVisible]); + + useEffect(() => { + setIsVisible(visibility); + }, [visibility]); + + useEffect(() => { + setExposedVariable('isDisabled', isDisabled); + }, [isDisabled]); + + useEffect(() => { + setIsDisabled(!disabledState); + }, [disabledState]); return ( - visibility && ( + isVisible && ( ) ); diff --git a/frontend/src/_styles/tabler.scss b/frontend/src/_styles/tabler.scss index 34afb6bd0e..fd12d00b55 100644 --- a/frontend/src/_styles/tabler.scss +++ b/frontend/src/_styles/tabler.scss @@ -17473,8 +17473,8 @@ a.step-item:hover { .step-item:not(:first-child):after { position: absolute; - left: -50%; - width: 100%; + left: calc(-50% + 8px); + width: calc(100% - 16px); content: ""; transform: translateY(-50%) } @@ -17487,11 +17487,18 @@ a.step-item:hover { box-sizing: content-box; display: block; content: ""; - border: 2px solid #fff; + border: 2px solid transparent; border-radius: 50%; transform: translateX(-50%) } +.steps.steps-counter { + .step-item:not(:first-child):after { + left: calc(-50% + 16px) !important; + width: calc(100% - 32px) !important; + } +} + .step-item.active { font-weight: 600 } diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 4e2d88ba13..f564828821 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -4749,15 +4749,18 @@ input[type="text"] { .folder-list { overflow-y: scroll; scrollbar-width: thin; - scrollbar-color: #888 transparent; + scrollbar-color: #888 transparent; + &:hover { &::-webkit-scrollbar { display: block; width: 5px; } + &::-webkit-scrollbar-thumb { background-color: #888; } + &::-webkit-scrollbar-track { background-color: transparent; } @@ -6507,6 +6510,7 @@ div#driver-page-overlay { // steps-widget a.step-item-disabled { text-decoration: none; + opacity: 0.5; } .steps { @@ -6516,7 +6520,7 @@ a.step-item-disabled { .step-item.active~.step-item:after, .step-item.active~.step-item:before { - background: #f3f5f5 !important; + background: var(--incompletedAccent) !important; } .step-item.active:before { @@ -6524,26 +6528,37 @@ a.step-item-disabled { } .steps .step-item.active:before { - border-color: #b4b2b2 !important; + border-color: var(--completedAccent) !important; } .steps-item { color: var(--textColor) !important; } + +.step-item { + &.completed-label { + color: var(--completedLabel) !important; + } + + &.incompleted-label { + color: var(--incompletedLabel) !important; + } + + &.active-label { + color: var(--currentStepLabel) !important; + } +} + .step-item:before { - background: var(--bgColor) !important; + background-color: var(--completedAccent) !important; // remaining code } .step-item:after { - background: var(--bgColor) !important; + background: var(--completedAccent) !important; } -.step-item.active~.step-item { - color: var(--textColor) !important; - ; -} .notification-center-badge { @@ -9857,25 +9872,30 @@ tbody { .workspace-settings-table-wrap { max-width: 880px; margin: 0 auto; - .tj-user-table-wrapper{ + + .tj-user-table-wrapper { padding-right: 4px; - } - &:hover{ - .tj-user-table-wrapper{ - padding-right: 0px; - } - ::-webkit-scrollbar{ - display: block; - width: 4px; - } - ::-webkit-scrollbar-track{ - background: var(--base); - } - ::-webkit-scrollbar-thumb{ - background: var(--slate7); - border-radius: 6px; - } - } + } + + &:hover { + .tj-user-table-wrapper { + padding-right: 0px; + } + + ::-webkit-scrollbar { + display: block; + width: 4px; + } + + ::-webkit-scrollbar-track { + background: var(--base); + } + + ::-webkit-scrollbar-thumb { + background: var(--slate7); + border-radius: 6px; + } + } } @@ -12131,8 +12151,10 @@ tbody { letter-spacing: -0.02em; } } + .sidebar-list-wrap.sidebar-list-wrap-with-banner.isAdmin { height: calc(100vh - 371px); + &.resource-limit-reached { height: calc(100vh - 371px); } @@ -15856,6 +15878,7 @@ tbody { .rest-api-options-codehinter { height: 100%; + .cm-content>.cm-line { // max-width: 357px !important; } @@ -16287,19 +16310,20 @@ fieldset:disabled { } .datepicker-validation-half { - flex:1 1 calc(50% - 8px); + flex: 1 1 calc(50% - 8px); } .date-validation-wrapper { .field { - height:24px; + height: 24px; } .code-flex-wrapper { - flex-wrap:wrap; + flex-wrap: wrap; } + margin-bottom: 3px; } @@ -16308,57 +16332,60 @@ fieldset:disabled { } - .react-datepicker__day--disabled { +.react-datepicker__day--disabled { + color: #ccc !important; +} + +.react-datepicker__time-list { + li.react-datepicker__time-list-item--disabled.react-datepicker__time-list-item { color: #ccc !important; } - - .react-datepicker__time-list{ - li.react-datepicker__time-list-item--disabled.react-datepicker__time-list-item { - color: #ccc !important; - } - } - - .inspector-validation-date-picker { - .react-datepicker-wrapper{ - input { - background-color: #fff; - } - input.dark-theme { - background-color: var(--slate3); - color: var(--slate12); - } +} +.inspector-validation-date-picker { + .react-datepicker-wrapper { + input { + background-color: #fff; } - + + input.dark-theme { + background-color: var(--slate3); + color: var(--slate12); + } + } +} -.datetimepicker-component, #component-portal, .custom-inspector-validation-time-picker { + +.datetimepicker-component, +#component-portal, +.custom-inspector-validation-time-picker { .datepicker-component { .react-datepicker { border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; - height:auto; + height: auto; } } - + .react-datepicker-time-component { border-radius: 10px; - width:auto; + width: auto; - .custom-time-input{ - border-left:none; - border-radius:10px; + .custom-time-input { + border-left: none; + border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; } .time-input-body { - padding-bottom:0px; + padding-bottom: 0px; } - + .time-col { height: 200px; } @@ -16367,28 +16394,32 @@ fieldset:disabled { border-radius: 10px; box-shadow: 8px 8px 16px 0px #3032331A; } - - .react-datepicker-time__input-container{ - border-radius:10px; + + .react-datepicker-time__input-container { + border-radius: 10px; } } - + .dark-theme { - .react-datepicker__year-text, .react-datepicker__month-text { + + .react-datepicker__year-text, + .react-datepicker__month-text { color: #fff; } - .react-datepicker__year-text:hover, .react-datepicker__month-text:hover { - background-color: #9ba1a6 ; + .react-datepicker__year-text:hover, + .react-datepicker__month-text:hover { + background-color: #9ba1a6; } } - .tj-datepicker-widget-year-selector:hover, .tj-datepicker-widget-month-selector:hover { - padding:1px 6px; + .tj-datepicker-widget-year-selector:hover, + .tj-datepicker-widget-month-selector:hover { + padding: 1px 6px; } - .react-datepicker{ + .react-datepicker { display: grid; grid-auto-flow: column; border-top-right-radius: 0rem; @@ -16401,48 +16432,49 @@ fieldset:disabled { justify-content: center; align-items: center; } + .react-datepicker__year-wrapper { - display:grid; - grid-template-columns:repeat(3, 1fr); + display: grid; + grid-template-columns: repeat(3, 1fr); max-width: unset; - gap:10px; + gap: 10px; } .react-datepicker { border-radius: 10px; } - .react-datepicker__header--custom{ + .react-datepicker__header--custom { height: 34px; margin-bottom: 14px; } - .react-datepicker__year--container{ - height:208px; + .react-datepicker__year--container { + height: 208px; width: 250px; box-shadow: 8px 8px 16px 0px #3032331A; border-radius: 10px; } .react-datepicker__year-text--selected { - background-color: #4368E3 !important; - height:24px; - width:61.33px; - border-radius: 8px; - color: #fff ; + background-color: #4368E3 !important; + height: 24px; + width: 61.33px; + border-radius: 8px; + color: #fff; } - .react-datepicker__year-text{ - font-family:'IBM Plex Sans' ; + .react-datepicker__year-text { + font-family: 'IBM Plex Sans'; font-size: 12px; line-height: 16px; text-align: center; font-weight: 400; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; justify-content: center; align-items: center; - display:flex; + display: flex; } } @@ -16457,42 +16489,42 @@ fieldset:disabled { } .react-datepicker__month-container { - height:208px; + height: 208px; width: 250px; box-shadow: 8px 8px 16px 0px #3032331A; border-radius: 10px; } .react-datepicker__monthPicker { - display:flex; + display: flex; flex-direction: column; - gap:10px; + gap: 10px; } .react-datepicker__month-text--selected { background-color: #4368E3 !important; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; border-radius: 8px; - color: #fff ; + color: #fff; } .react-datepicker__month-wrapper { - display:flex; - gap:24px; + display: flex; + gap: 24px; } .react-datepicker__month-text { - font-family:'IBM Plex Sans' ; + font-family: 'IBM Plex Sans'; font-size: 12px; line-height: 16px; text-align: center; font-weight: 400; - height:24px; - width:61.33px; + height: 24px; + width: 61.33px; justify-content: center; align-items: center; - display:flex; + display: flex; } } @@ -16502,7 +16534,7 @@ fieldset:disabled { .react-datepicker__month-container { width: 100%; - width:250px; + width: 250px; } .react-datepicker__input-time-container { @@ -16517,12 +16549,12 @@ fieldset:disabled { color: #ccc !important; pointer-events: none; } - + .react-datepicker-time__input { margin-left: 0px !important; .dark-time-input { - color:#f4f6fa !important; + color: #f4f6fa !important; background-color: var(--surfaces-surface-01) !important; } } @@ -16530,15 +16562,15 @@ fieldset:disabled { .react-datepicker-wrapper { width: 100%; } - + .react-datepicker-time__caption { - display:none; + display: none; } .custom-time-input { background-color: #fff; border-left: 1px solid #CCD1D5; - border-top-right-radius: 10px; + border-top-right-radius: 10px; border-bottom-right-radius: 10px; } @@ -16552,18 +16584,18 @@ fieldset:disabled { border-bottom: 1px solid #CCD1D5; font-weight: 500; font-family: 'IBM Plex Sans'; - color:#ACB2B9; + color: #ACB2B9; } - + .time-input-body { padding-bottom: 12px; } .time-col { margin-top: 5px; - overflow-y: auto; + overflow-y: auto; overflow-x: hidden; - scrollbar-width: none; + scrollbar-width: none; height: 265px; width: 62px; } @@ -16571,12 +16603,12 @@ fieldset:disabled { .selected-time { background-color: #4368E3 !important; border-radius: 6px; - color:#fff; + color: #fff; } .time-item { - width: 50px; - height:22px; + width: 50px; + height: 22px; display: flex; justify-content: center; align-items: center; @@ -16916,16 +16948,17 @@ section.ai-message-prompt-input-wrapper { .tj-inspector-timepicker.dark-theme { - .react-datepicker { - color:#f4f6fa !important; + .react-datepicker { + color: #f4f6fa !important; background-color: var(--surfaces-surface-01) !important; } - .react-datepicker, .react-datepicker__header { + .react-datepicker, + .react-datepicker__header { border: 1px solid var(--borders-default); background-color: #1f2936; - .react-datepicker-time__header{ + .react-datepicker-time__header { color: #fff !important; } @@ -16933,25 +16966,27 @@ section.ai-message-prompt-input-wrapper { } .tj-inspector-timepicker { - padding:0px !important; + padding: 0px !important; .react-datepicker__time-list { - scrollbar-width: none; + scrollbar-width: none; } .react-datepicker__triangle { - display:none; + display: none; } } -.custom-inspector-validation-date-picker, .custom-inspector-validation-time-picker { +.custom-inspector-validation-date-picker, +.custom-inspector-validation-time-picker { flex-basis: 100% !important; font-family: monospace; font-size: 12px; - height:32px; - + height: 32px; + .react-datepicker-wrapper { width: 100%; + input { width: 100%; border: 1px solid var(--slate7); @@ -16959,23 +16994,23 @@ section.ai-message-prompt-input-wrapper { background-color: var(--base); background-color: #fff; color: rgb(0, 92, 197); - height:32px; + height: 32px; } input.dark-theme { background-color: #272822; color: rgb(174, 129, 255); - + } } - + } .custom-inspector-validation-time-picker { .custom-time-input { - border-left:none; - border-radius:10px; + border-left: none; + border-radius: 10px; } .time-col { @@ -16983,19 +17018,21 @@ section.ai-message-prompt-input-wrapper { } .react-datepicker__input-time-container { - border-radius:10px; + border-radius: 10px; } - - + + } .custom-inspector-validation-time-picker-popper { - border-radius:10px; + border-radius: 10px; } -.input-date-display-format, .input-date-time-format { +.input-date-display-format, +.input-date-time-format { height: 60px; + .hide-fx { opacity: 0; transition: opacity 0.3s ease; @@ -17014,8 +17051,9 @@ section.ai-message-prompt-input-wrapper { color: white; } - .react-datepicker__day:hover, .react-datepicker__day--selecting-range-end { - background-color: var(--interactive-overlays-fill-hover) !important ; + .react-datepicker__day:hover, + .react-datepicker__day--selecting-range-end { + background-color: var(--interactive-overlays-fill-hover) !important; } .react-datepicker__day--keyboard-selected { @@ -17038,15 +17076,17 @@ section.ai-message-prompt-input-wrapper { .tj-daterange-widget { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; - .react-datepicker__day--in-selecting-range, .react-datepicker__day--in-range { - border-radius:0px; + .react-datepicker__day--in-selecting-range, + .react-datepicker__day--in-range { + border-radius: 0px; background-color: #4368E31A !important; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17057,44 +17097,48 @@ section.ai-message-prompt-input-wrapper { background-color: #ededee !important; } - .react-datepicker__day--selecting-range-start, .react-datepicker__day--selected, .react-datepicker__day--range-end { - border-radius:8px !important; + .react-datepicker__day--selecting-range-start, + .react-datepicker__day--selected, + .react-datepicker__day--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end), .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--selecting-range-end) { + .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end), + .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__day--in-range:has(+ .react-datepicker__day--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__day--range-start + .react-datepicker__day--in-range, .react-datepicker__day--selecting-range-start + .react-datepicker__day--in-selecting-range{ + .react-datepicker__day--range-start+.react-datepicker__day--in-range, + .react-datepicker__day--selecting-range-start+.react-datepicker__day--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__day--range-start + .react-datepicker__day--in-range { + .react-datepicker__day--range-start+.react-datepicker__day--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - + .react-datepicker__week { - .react-datepicker__day--in-range:first-of-type, - .react-datepicker__day--in-selecting-range:first-of-type, - .react-datepicker__day--outside-month + .react-datepicker__day--in-range, - .react-datepicker__day--outside-month + .react-datepicker__day--in-selecting-range{ + .react-datepicker__day--in-range:first-of-type, + .react-datepicker__day--in-selecting-range:first-of-type, + .react-datepicker__day--outside-month+.react-datepicker__day--in-range, + .react-datepicker__day--outside-month+.react-datepicker__day--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__day--in-range:last-of-type, - .react-datepicker__day--in-selecting-range:last-of-type, - .react-datepicker__day--in-range:has(+ .react-datepicker__day--outside-month), - .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--outside-month){ + .react-datepicker__day--in-range:last-of-type, + .react-datepicker__day--in-selecting-range:last-of-type, + .react-datepicker__day--in-range:has(+ .react-datepicker__day--outside-month), + .react-datepicker__day--in-selecting-range:has(+ .react-datepicker__day--outside-month) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17112,8 +17156,8 @@ section.ai-message-prompt-input-wrapper { } .tj-datepicker-widget-right { - position: absolute; - right: 10px; + position: absolute; + right: 10px; } .tj-datepicker-widget-left { @@ -17136,41 +17180,42 @@ section.ai-message-prompt-input-wrapper { } .react-datepicker { - border-radius:10px !important; - border:none; + border-radius: 10px !important; + border: none; } - + } -.tj-daterangepicker-widget-month-selector, .tj-daterangepicker-widget-year-selector { - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; - padding-right: 4px; - /* Add some padding on the right to create space for custom arrow */ - background-image: url('data:image/svg+xml;utf8,'); - /* Add a custom arrow (you can use your own SVG) */ - background-repeat: no-repeat; - background-position: right center; - border: none; - /* Remove the default border */ - padding: 8px; - /* Adjust padding as needed */ - cursor: pointer; - /* Add pointer cursor for better usability */ - background: none; - padding: 0px; - height: 24px; - text-align: center; - color: var(--text-primary); - font-weight: 500; - width:auto; +.tj-daterangepicker-widget-month-selector, +.tj-daterangepicker-widget-year-selector { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + padding-right: 4px; + /* Add some padding on the right to create space for custom arrow */ + background-image: url('data:image/svg+xml;utf8,'); + /* Add a custom arrow (you can use your own SVG) */ + background-repeat: no-repeat; + background-position: right center; + border: none; + /* Remove the default border */ + padding: 8px; + /* Adjust padding as needed */ + cursor: pointer; + /* Add pointer cursor for better usability */ + background: none; + padding: 0px; + height: 24px; + text-align: center; + color: var(--text-primary); + font-weight: 500; + width: auto; } .datepicker-widget { - .react-datepicker-wrapper{ - width:100% !important; + .react-datepicker-wrapper { + width: 100% !important; } } @@ -17184,26 +17229,29 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-month-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__month-container { box-shadow: none !important; } - + .react-datepicker__month-text { - height:26px !important; + height: 26px !important; margin: 0px; - width:100% !important; + width: 100% !important; } - .react-datepicker__month-text--in-selecting-range, .react-datepicker__month-text--in-range { - border-radius:0px; + .react-datepicker__month-text--in-selecting-range, + .react-datepicker__month-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17216,45 +17264,49 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__month-text--selecting-range-start, .react-datepicker__month-text--selected, .react-datepicker__month-text--range-end { - border-radius:8px !important; + .react-datepicker__month-text--selecting-range-start, + .react-datepicker__month-text--selected, + .react-datepicker__month-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range, .react-datepicker__month-text--selecting-range-start + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--selecting-range-start+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--range-start + .react-datepicker__month-text--in-range { + .react-datepicker__month-text--range-start+.react-datepicker__month-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - .react-datepicker__month-wrapper{ - gap:0px !important; - .react-datepicker__month-text--in-range:first-of-type, - .react-datepicker__month-text--in-selecting-range:first-of-type, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-range, - .react-datepicker__month-text--outside-month-text + .react-datepicker__month-text--in-selecting-range{ + .react-datepicker__month-wrapper { + gap: 0px !important; + + .react-datepicker__month-text--in-range:first-of-type, + .react-datepicker__month-text--in-selecting-range:first-of-type, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-range, + .react-datepicker__month-text--outside-month-text+.react-datepicker__month-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__month-text--in-range:last-of-type, - .react-datepicker__month-text--in-selecting-range:last-of-type, - .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), - .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text){ + .react-datepicker__month-text--in-range:last-of-type, + .react-datepicker__month-text--in-selecting-range:last-of-type, + .react-datepicker__month-text--in-range:has(+ .react-datepicker__month-text--outside-month-text), + .react-datepicker__month-text--in-selecting-range:has(+ .react-datepicker__month-text--outside-month-text) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } @@ -17274,44 +17326,47 @@ section.ai-message-prompt-input-wrapper { } .tj-daterange-widget.react-datepicker-year-component { - border-radius:10px; + border-radius: 10px; box-shadow: 0px 8px 16px 0px #3032331A !important; font-family: 'IBM Plex Sans'; + .react-datepicker__year-container { box-shadow: none !important; } - .react-datepicker__year-wrapper{ - gap:0px !important; + .react-datepicker__year-wrapper { + gap: 0px !important; - .react-datepicker__year-text--in-range:first-of-type, - .react-datepicker__year-text--in-selecting-range:first-of-type{ + .react-datepicker__year-text--in-range:first-of-type, + .react-datepicker__year-text--in-selecting-range:first-of-type { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--in-range:last-of-type, - .react-datepicker__year-text--in-selecting-range:last-of-type{ + .react-datepicker__year-text--in-range:last-of-type, + .react-datepicker__year-text--in-selecting-range:last-of-type { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } } - + .react-datepicker__year-text { - height:26px !important; + height: 26px !important; margin-top: 5px !important; - margin-bottom:5px !important; + margin-bottom: 5px !important; margin: 0px; - width:62px !important; + width: 62px !important; } - .react-datepicker__year-text--in-selecting-range, .react-datepicker__year-text--in-range { - border-radius:0px; + .react-datepicker__year-text--in-selecting-range, + .react-datepicker__year-text--in-range { + border-radius: 0px; background-color: #4368E31A !important; - color:#000; + color: #000; } - .react-datepicker__header{ + + .react-datepicker__header { background-color: var(--surfaces-surface-01); padding: 6px 0px; border: none; @@ -17324,31 +17379,35 @@ section.ai-message-prompt-input-wrapper { } - .react-datepicker__year-text--selecting-range-start, .react-datepicker__year-text--selected, .react-datepicker__year-text--range-end { - border-radius:8px !important; + .react-datepicker__year-text--selecting-range-start, + .react-datepicker__year-text--selected, + .react-datepicker__year-text--range-end { + border-radius: 8px !important; background-color: #4368E3 !important; color: #fff !important; } - .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end), + .react-datepicker__year-text--in-selecting-range:has(+ .react-datepicker__year-text--selecting-range-end) { border-top-right-radius: 8px; border-bottom-right-radius: 8px; } - + .react-datepicker__year-text--in-range:has(+ .react-datepicker__year-text--range-end) { box-shadow: 10px 0 0 0px #4368E31A; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range, .react-datepicker__year-text--selecting-range-start + .react-datepicker__year-text--in-selecting-range{ + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range, + .react-datepicker__year-text--selecting-range-start+.react-datepicker__year-text--in-selecting-range { border-top-left-radius: 8px; border-bottom-left-radius: 8px; } - .react-datepicker__year-text--range-start + .react-datepicker__year-text--in-range { + .react-datepicker__year-text--range-start+.react-datepicker__year-text--in-range { box-shadow: -10px 0 0 0px #4368E31A; } - - + + } .dark-theme { @@ -18658,6 +18717,7 @@ section.ai-message-prompt-input-wrapper { font-style: normal; font-weight: 400; line-height: 18px; + &.dark { background: #FFFAEB !important; } diff --git a/server/data-migrations/1742369436314-StepsV2Migration.ts b/server/data-migrations/1742369436314-StepsV2Migration.ts new file mode 100644 index 0000000000..dcb041db1f --- /dev/null +++ b/server/data-migrations/1742369436314-StepsV2Migration.ts @@ -0,0 +1,81 @@ +import { Component } from '@entities/component.entity'; +import { EntityManager, MigrationInterface, QueryRunner } from 'typeorm'; +import { processDataInBatches } from '@helpers/migration.helper'; + +export class StepsV2Migration1742369436314 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const componentTypes = ['Steps']; + const batchSize = 100; + const entityManager = queryRunner.manager; + + for (const componentType of componentTypes) { + await processDataInBatches( + entityManager, + async (entityManager: EntityManager) => { + return await entityManager.find(Component, { + where: { type: componentType }, + order: { createdAt: 'ASC' }, + }); + }, + async (entityManager: EntityManager, components: Component[]) => { + await this.processUpdates(entityManager, components); + }, + batchSize + ); + } + } + + public async down(queryRunner: QueryRunner): Promise {} + + private async processUpdates(entityManager, components) { + for (const component of components) { + const properties = component.properties; + const styles = component.styles; + const general = component.general; + const generalStyles = component.generalStyles; + const validation = component.validation; + + if (styles.visibility) { + properties.visibility = styles.visibility; + delete styles.visibility; + } + if (styles.theme) { + properties['variant'] = styles.theme; + delete styles.theme; + } + if (styles.color) { + styles['completedAccent'] = styles.color; + } + delete styles.color; + if (styles.textColor) { + styles['completedLabel'] = styles.textColor; + styles['incompletedLabel'] = styles.textColor; + styles['currentStepLabel'] = styles.textColor; + } + delete styles.textColor; + if (properties.steps) { + properties['schema'] = properties.steps; + delete properties.steps; + properties['advanced'] = { value: '{{true}}' }; + } + + // if (properties.stepsSelectable) { + // properties.disabledState = styles.disabledState; + // delete styles.disabledState; + // } + + // if (generalStyles?.boxShadow) { + // styles.boxShadow = generalStyles?.boxShadow; + // delete generalStyles?.boxShadow; + // } + + await entityManager.update(Component, component.id, { + properties, + styles, + general, + generalStyles, + validation, + }); + } + } +} diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts index a7538f5f40..fcc01e52f0 100644 --- a/server/src/modules/apps/services/component.service.ts +++ b/server/src/modules/apps/services/component.service.ts @@ -95,7 +95,9 @@ export class ComponentsService implements IComponentsService { if (componentData.type === 'Table' && _.isArray(objValue)) { return srcValue; } else if ( - (componentData.type === 'DropdownV2' || componentData.type === 'MultiselectV2') && + (componentData.type === 'DropdownV2' || + componentData.type === 'MultiselectV2' || + componentData.type === 'Steps') && _.isArray(objValue) ) { return _.isArray(srcValue) ? srcValue : Object.values(srcValue); diff --git a/server/src/modules/apps/services/widget-config/steps.js b/server/src/modules/apps/services/widget-config/steps.js index a39c634919..bb75948fc9 100644 --- a/server/src/modules/apps/services/widget-config/steps.js +++ b/server/src/modules/apps/services/widget-config/steps.js @@ -4,9 +4,30 @@ export const stepsConfig = { description: 'Step-by-step navigation aid', component: 'Steps', properties: { + variant: { + type: 'switch', + displayName: 'Variant', + validation: { schema: { type: 'string' }, defaultValue: 'titles' }, + options: [ + { displayName: 'Label', value: 'titles' }, + { displayName: 'Numbers', value: 'numbers' }, + { displayName: 'Plain', value: 'plain' }, + ], + accordian: 'label', + }, + schema: { + type: 'code', + displayName: 'Schema', + conditionallyRender: { + key: 'advanced', + value: true, + }, + accordian: 'Options', + }, steps: { type: 'code', - displayName: 'Steps', + displayName: '', + showLabel: false, validation: { schema: { type: 'array', @@ -15,6 +36,27 @@ export const stepsConfig = { defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`, }, }, + visibility: { + type: 'toggle', + displayName: 'Visibility', + validation: { schema: { type: 'boolean' }, defaultValue: true }, + section: 'additionalActions', + }, + // disabledState: { + // type: 'toggle', + // displayName: 'Disable', + // validation: { schema: { type: 'boolean' }, defaultValue: true }, + // section: 'additionalActions', + // }, + advanced: { + type: 'toggle', + displayName: 'Dynamic options', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + accordian: 'Options', + }, currentStep: { type: 'code', displayName: 'Current step', @@ -30,6 +72,7 @@ export const stepsConfig = { schema: { type: 'boolean' }, defaultValue: false, }, + section: 'additionalActions', }, }, defaultSize: { @@ -40,46 +83,122 @@ export const stepsConfig = { showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, }, + actions: [ + { + handle: 'setStep', + displayName: 'Set step', + params: [ + { + handle: 'option', + displayName: 'Option', + }, + ], + }, + { + handle: 'setVisibility', + displayName: 'Set visibility', + params: [ + { + handle: 'option', + displayName: 'Option', + }, + ], + }, + { + handle: 'resetSteps', + displayName: 'Reset steps', + params: [], + }, + { + handle: 'setStepVisible', + displayName: 'Set step visible', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'visibility', + displayName: 'visibility', + }, + ], + }, + { + handle: 'setStepDisable', + displayName: 'Set step disable', + params: [ + { + handle: 'id', + displayName: 'Step id', + }, + { + handle: 'disabled', + displayName: 'disabled', + }, + ], + }, + ], events: { onSelect: { displayName: 'On select' }, }, styles: { - color: { + incompletedAccent: { type: 'color', - displayName: 'Color', + displayName: 'Incompleted accent', validation: { schema: { type: 'string' }, - defaultValue: '#000000', + defaultValue: '#E4E7EB', }, + accordian: 'steps', }, - textColor: { + incompletedLabel: { type: 'color', - displayName: 'Text color', + displayName: 'Incompleted label', validation: { schema: { type: 'string' }, - defaultValue: '#000000', + defaultValue: '#1B1F24', }, + accordian: 'steps', }, - theme: { - type: 'select', - displayName: 'Theme', + completedAccent: { + type: 'color', + displayName: 'Completed accent', + validation: { + schema: { type: 'string' }, + defaultValue: '#4368E3', + }, + accordian: 'steps', + }, + completedLabel: { + type: 'color', + displayName: 'Completed label', + validation: { + schema: { type: 'string' }, + defaultValue: '#FBFCFD', + }, + accordian: 'steps', + }, + currentStepLabel: { + type: 'color', + displayName: 'Current step label', + validation: { + schema: { type: 'string' }, + defaultValue: '#1B1F24', + }, + accordian: 'steps', + }, + padding: { + type: 'switch', + displayName: 'Padding', + validation: { + schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, + defaultValue: 'default', + }, options: [ - { name: 'titles', value: 'titles' }, - { name: 'numbers', value: 'numbers' }, - { name: 'plain', value: 'plain' }, + { displayName: 'Default', value: 'default' }, + { displayName: 'None', value: 'none' }, ], - validation: { - schema: { type: 'string' }, - defaultValue: 'titles', - }, - }, - visibility: { - type: 'toggle', - displayName: 'Visibility', - validation: { - schema: { type: 'boolean' }, - defaultValue: true, - }, + accordian: 'container', }, }, exposedVariables: { @@ -92,17 +211,35 @@ export const stepsConfig = { }, properties: { steps: { - value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`, + value: [ + { name: 'step 1', tooltip: 'some tooltip', id: 1, visible: { value: true }, disabled: { value: false } }, + { name: 'step 2', tooltip: 'some tooltip', id: 2, visible: { value: true }, disabled: { value: false } }, + { name: 'step 3', tooltip: 'some tooltip', id: 3, visible: { value: true }, disabled: { value: false } }, + { name: 'step 4', tooltip: 'some tooltip', id: 4, visible: { value: true }, disabled: { value: false } }, + { name: 'step 5', tooltip: 'some tooltip', id: 5, visible: { value: true }, disabled: { value: false } }, + ], }, + schema: { + value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: 'some tooltip', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: 'some tooltip', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: 'some tooltip', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: 'some tooltip', id: 5,visible: true, disabled: false}]}}`, + }, + variant: { value: 'titles' }, currentStep: { value: '{{3}}' }, stepsSelectable: { value: true }, + advanced: { value: `{{false}}` }, + // disabledState: { value: '{{false}}' }, + visibility: { value: '{{true}}' }, }, events: [], styles: { visibility: { value: '{{true}}' }, - theme: { value: 'titles' }, color: { value: '' }, textColor: { value: '' }, + padding: { value: 'default' }, + incompletedAccent: { value: '#E4E7EB' }, + incompletedLabel: { value: '#1B1F24' }, + completedAccent: { value: '#4368E3' }, + completedLabel: { value: '#1B1F24' }, + currentStepLabel: { value: '#1B1F24' }, }, }, }; diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 36def10913..7cf95ea067 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -487,7 +487,7 @@ export class AppsUtilService implements IAppsUtilService { if (['Table'].includes(currentComponentData?.component?.component) && isArray(objValue)) { return srcValue; } else if ( - ['DropdownV2', 'MultiselectV2'].includes(currentComponentData?.component?.component) && + ['DropdownV2', 'MultiselectV2', 'Steps'].includes(currentComponentData?.component?.component) && isArray(objValue) ) { return isArray(srcValue) ? srcValue : Object.values(srcValue); From 8c2c42701ee39f155f180de12a4a8727c3ed3dce Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Fri, 21 Mar 2025 20:32:48 +0530 Subject: [PATCH 019/297] wa feedbacks --- .../Inspector/Components/Steps.jsx | 14 ++ .../AppBuilder/WidgetManager/widgets/steps.js | 14 +- frontend/src/Editor/Components/Steps.jsx | 46 ++-- .../src/Editor/WidgetManager/configs/steps.js | 201 +++++++++++++++--- server/ee | 2 +- .../apps/services/widget-config/steps.js | 14 +- 6 files changed, 232 insertions(+), 59 deletions(-) diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx index 23636aec50..6956236919 100644 --- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx +++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Steps.jsx @@ -139,6 +139,20 @@ export function Steps({ componentMeta, darkMode, ...restProps }) { return ( +
+ + handleLabelChange('id', value, index)} + /> +