Merge pull request #8711 from ToolJet/appbuilder-1.5

Release v2.29.0 - App builder 1.5
This commit is contained in:
Kavin Venkatachalam 2024-02-15 11:23:25 +05:30 committed by GitHub
commit f6ececf1ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
157 changed files with 24665 additions and 8590 deletions

View file

@ -1 +1 @@
2.28.4
2.29.0

View file

@ -82,6 +82,8 @@ module.exports = defineConfig({
"cypress/e2e/editor/inspectorHappypath.cy.js",
"cypress/e2e/editor/queries/runpyHappyPath.cy.js",
"cypress/e2e/editor/queries/runjsHappyPath.cy.js",
"cypress/e2e/editor/multipage/multipageHappypath.cy.js",
"cypress/e2e/editor/queries/chainingOfQueries.cy.js",
],
numTestsKeptInMemory: 1,
redirectionLimit: 7,

View file

@ -133,23 +133,6 @@ Cypress.Commands.add(
}
);
// cy.apiLogin();
// cy.apiCreateApp();
// cy.apiCreateGDS(
// "http://localhost:3000/api/v2/data_sources",
// "aaaaaadish",
// "postgresql",
// [
// { key: "host", value: "localhost" },
// { key: "port", value: 5432 },
// { key: "database", value: "" },
// { key: "username", value: "dev@tooljet.io" },
// { key: "password", value: "password", encrypted: true },
// { key: "ssl_enabled", value: true, encrypted: false },
// { key: "ssl_certificate", value: "none", encrypted: false },
// ]
// );
Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => {
cy.getCookie("tj_auth_token").then((cookie) => {
cy.request(
@ -212,6 +195,7 @@ Cypress.Commands.add("userInviteApi", (userName, userEmail) => {
});
});
});
Cypress.Commands.add("addQueryApi", (queryName, query, dataQueryId) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
@ -236,3 +220,53 @@ Cypress.Commands.add("addQueryApi", (queryName, query, dataQueryId) => {
});
});
});
Cypress.Commands.add(
"apiAddQueryToApp",
(queryName, options, dsName, dsKind) => {
cy.getCookie("tj_auth_token", { log: false }).then((cookie) => {
const authToken = `tj_auth_token=${cookie.value}`;
const workspaceId = Cypress.env("workspaceId");
const appId = Cypress.env("appId");
cy.request({
method: "GET",
url: `http://localhost:3000/api/apps/${appId}`,
headers: {
"Tj-Workspace-Id": workspaceId,
Cookie: `${authToken}; app_id=${appId}`,
},
body: {},
}).then((appResponse) => {
const editingVersionId = appResponse.body.editing_version.id;
Cypress.env("version-id", editingVersionId);
cy.request({
method: "POST",
url: "http://localhost:3000/api/data_queries",
headers: {
"Content-Type": "application/json",
Cookie: authToken,
"tj-workspace-id": workspaceId,
},
body: {
app_id: appId,
app_version_id: editingVersionId,
name: queryName,
kind: dsKind,
options: options,
data_source_id: dsName != null ? Cypress.env(`${dsName}-id`) : null,
plugin_id: null,
},
}).then((queryResponse) => {
expect(queryResponse.status).to.equal(201);
Cypress.log({
name: "Created queery",
displayName: "QUERY CREATED",
message: `: ${queryName}: ${dsKind}`,
});
});
});
});
}
);

View file

@ -115,9 +115,9 @@ Cypress.Commands.add(
.last()
.click()
.type(createBackspaceText(text), { delay: 0 }),
{
delay: 0,
};
{
delay: 0,
};
});
if (!Array.isArray(value)) {
cy.wrap(subject).last().type(value, {
@ -193,9 +193,9 @@ Cypress.Commands.add(
.invoke("text")
.then((text) => {
cy.wrap(subject).realType(createBackspaceText(text)),
{
delay: 0,
};
{
delay: 0,
};
});
}
);
@ -387,6 +387,23 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add("releaseApp", () => {
if (Cypress.env("environment") !== "Community") {
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
cy.get(commonEeSelectors.promoteButton).click();
cy.get(commonEeSelectors.promoteButton).eq(1).click();
cy.waitForAppLoad();
cy.wait(3000);
}
cy.get(commonSelectors.releaseButton).click();
cy.get(commonSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");
cy.wait(1000);
});
Cypress.Commands.add("backToApps", () => {
cy.get(commonSelectors.editorPageLogo).click();
cy.get(commonSelectors.backToAppOption).click();
@ -399,12 +416,16 @@ Cypress.Commands.add("removeAssignedApps", () => {
});
});
Cypress.Commands.add('saveFromIntercept', (interceptAlias, property, envVariable) => {
cy.get(interceptAlias).its('response.body').then((responseBody) => {
Cypress.env(envVariable, responseBody[property]);
});
});
Cypress.Commands.add(
"saveFromIntercept",
(interceptAlias, property, envVariable) => {
cy.get(interceptAlias)
.its("response.body")
.then((responseBody) => {
Cypress.env(envVariable, responseBody[property]);
});
}
);
Cypress.Commands.add("verifyLabel", (labelName) => {
cy.get(commonSelectors.label(`${labelName}`)).verifyVisibleElement(
@ -413,8 +434,29 @@ Cypress.Commands.add("verifyLabel", (labelName) => {
);
});
Cypress.Commands.add(
"visitSlug",
({ actualUrl, currentUrl = "http://localhost:8082/error/unknown" }) => {
cy.visit(actualUrl);
cy.wait(3000);
cy.url().then((url) => {
if (url === currentUrl) {
cy.visit(actualUrl);
}
});
}
);
Cypress.Commands.add(
"verifyCssProperty",
(selector, property, expectedValue) => {
cy.get(selector).should("have.css", property).and("eq", expectedValue);
}
);
Cypress.Commands.add("skipWalkthrough", () => {
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
});
});

View file

@ -188,7 +188,7 @@ export const commonWidgetText = {
accordionEvents: "Events",
accordionGenaral: "General",
accordionValidation: "Validation",
accordionLayout: "Layout",
accordionLayout: "Devices",
accordionDevices: "Devices",
parameterCustomValidation: "Custom validation",
@ -204,6 +204,7 @@ export const commonWidgetText = {
parameterOptionvalues: "Option values",
boxShadowColor: "Box shadow Color",
boxShadowFxValue: "-5px 6px 5px 8px #ee121240",
loadingState: "Loading state",
codeMirrorLabelTrue: "{{true}}",
codeMirrorLabelFalse: "{{false}}",

View file

@ -24,7 +24,7 @@ import {
} from "Support/utils/database";
import { fake } from "Fixtures/fake";
import { randomNumber } from "Support/utils/commonWidget";
import { randomString } from "Support/utils/textInput";
import { randomString } from "Support/utils/editor/textInput";
describe("Database Functionality", () => {
const data = {};

View file

@ -3,7 +3,7 @@ import {
verifyMultipleComponentValuesFromInspector,
verifyComponentValueFromInspector,
} from "Support/utils/commonWidget";
import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector";
import { verifyNodeData, openNode, verifyValue, deleteComponentFromInspector } from "Support/utils/inspector";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { addNewPage } from "Support/utils/multipage";
import {
@ -12,8 +12,16 @@ import {
addSupportCSAData,
} from "Support/utils/events";
import { multipageSelector } from "Selectors/multipage";
import {
navigateToCreateNewVersionModal
} from "Support/utils/version";
import { createNewVersion } from "Support/utils/exportImport";
describe("Editor- Inspector", () => {
let currentVersion = "";
let newVersion = [];
let versionFrom = "";
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
@ -170,4 +178,17 @@ describe("Editor- Inspector", () => {
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
cy.apiDeleteApp();
});
it("should verify deletion of component from inspector", () => {
cy.dragAndDropWidget("button", 500, 500);
cy.get(commonWidgetSelector.sidebarinspector).click();
deleteComponentFromInspector("button1");
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! (⌘ + Z to undo)"
);
navigateToCreateNewVersionModal((currentVersion = "v1"));
createNewVersion((newVersion = ["v2"]), (versionFrom = "v1"));
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
});
});

View file

@ -36,6 +36,7 @@ import {
modifyPageHandle,
clearSearch,
searchPage,
disableOrEnablePage,
} from "Support/utils/multipage";
describe("Multipage", () => {
@ -285,4 +286,27 @@ describe("Multipage", () => {
cy.get(multipageSelector.homePageLabel).click();
});
it("should verify the disable/delete functions of multipage", () => {
cy.get(multipageSelector.sidebarPageButton).click();
cy.get(multipageSelector.pagesPinIcon).click();
addNewPage("pageOne");
addNewPage("pageTwo");
addNewPage("pageThree");
detetePage("pageOne");
disableOrEnablePage("pageTwo");
cy.get(
'[data-cy="pages-name-pagetwo"] > .color-slate09'
).verifyVisibleElement("have.text", "Disabled");
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.notVisible(`[data-cy="pages-name-pageone}"]`);
cy.notVisible(`[data-cy="pages-name-pagetwo}"]`);
cy.wait(1000);
cy.url().should("contain", "/home?");
cy.url().then((url) => {
cy.visit(url.replace("home", "pagetwo"));
});
cy.wait(1000);
cy.url().should("contain", "/home?");
});
});

View file

@ -0,0 +1,176 @@
import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { openEditorSidebar } from "Support/utils/commonWidget";
import { selectEvent } from "Support/utils/events";
import { randomString } from "Support/utils/textInput";
import { buttonText } from "Texts/button";
import { addSuccessNotification, chainQuery } from "Support/utils/queries";
import { resizeQueryPanel } from "Support/utils/dataSource";
describe("Chaining of queries", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
cy.viewport(1800, 1800);
cy.dragAndDropWidget("Button");
resizeQueryPanel("80");
});
it("should verify the chainig of runjs, restapi, runpy, tooljetdb and postgres", () => {
const data = {};
let dsName = fake.companyName;
data.customText = randomString(12);
cy.apiAddQueryToApp(
"runjs",
{ code: "return true", hasParamSupport: true, parameters: [] },
null,
"runjs"
);
cy.apiAddQueryToApp(
"runpy",
{ code: "True", hasParamSupport: true, parameters: [] },
null,
"runpy"
);
cy.apiAddQueryToApp(
"restapi",
{
method: "get",
url: "https://gorest.co.in/public/v2/users",
url_params: [["", ""]],
},
null,
"restapi"
);
cy.apiAddQueryToApp(
"tjdb",
{
operation: "",
transformationLanguage: "javascript",
enableTransformation: false,
},
null,
"tooljetdb"
);
cy.apiCreateGDS(
"http://localhost:3000/api/v2/data_sources",
`cypress-${dsName}-postgresql`,
"postgresql",
[
{ key: "host", value: Cypress.env("pg_host") },
{ key: "port", value: 5432 },
{ key: "database", value: Cypress.env("pg_user") },
{ key: "username", value: Cypress.env("pg_user") },
{ key: "password", value: Cypress.env("pg_password"), encrypted: true },
{ key: "ssl_enabled", value: false, encrypted: false },
{ key: "ssl_certificate", value: "none", encrypted: false },
]
);
cy.apiAddQueryToApp(
"psql",
{
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: `SELECT * FROM server_side_pagination`,
},
`cypress-${dsName}-postgresql`,
"postgresql"
);
cy.reload();
resizeQueryPanel("80");
chainQuery("psql", "runjs");
addSuccessNotification("psql");
chainQuery("runjs", "runpy");
addSuccessNotification("runjs");
chainQuery("runpy", "restapi");
addSuccessNotification("runpy");
chainQuery("restapi", "tjdb");
addSuccessNotification("restapi");
openEditorSidebar(buttonText.defaultWidgetName);
selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1);
cy.wait(500);
cy.get('[data-cy="query-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}psql{enter}`);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "psql");
cy.verifyToastMessage(commonSelectors.toastMessage, "runjs");
cy.verifyToastMessage(commonSelectors.toastMessage, "runpy");
cy.verifyToastMessage(commonSelectors.toastMessage, "restapi");
cy.verifyToastMessage(commonSelectors.toastMessage, "Invalid operation");
});
it("should verify query duplication", () => {
const data = {};
let dsName = fake.companyName;
data.customText = randomString(12);
cy.apiAddQueryToApp(
"runjs",
{ code: "return true", hasParamSupport: true, parameters: [] },
null,
"runjs"
);
cy.apiAddQueryToApp(
"runpy",
{ code: "True", hasParamSupport: true, parameters: [] },
null,
"runpy"
);
cy.reload();
resizeQueryPanel("80");
addSuccessNotification("runpy");
chainQuery("runjs", "runpy");
addSuccessNotification("runjs");
openEditorSidebar(buttonText.defaultWidgetName);
selectEvent("On Click", "Run Query", 1, `[data-cy="add-event-handler"]`, 1);
cy.wait(500);
cy.get('[data-cy="query-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}runjs{enter}`);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.verifyToastMessage(commonSelectors.toastMessage, "runjs");
cy.verifyToastMessage(commonSelectors.toastMessage, "runpy");
cy.get('[data-cy="list-query-runjs"]')
.trigger("mouseover")
.parent()
.parent()
.find('[data-cy="copy-icon"]')
.eq(0)
.invoke("show")
.click({ force: true });
cy.get('[data-cy="list-query-runjs_copy"]').verifyVisibleElement(
"have.text",
"runjs_copy "
);
cy.get('[data-cy="notification-on-success-toggle-switch"]').should(
"have.value",
"on"
);
cy.get('[data-cy="success-message-input-field"]').should(
"contain.text",
"runjs"
);
cy.get(".query-definition-pane-wrapper").within(() => {
cy.get('[data-cy="event-handler-card"]').eq(0).click();
cy.wait(500);
});
cy.get(
`[data-cy="action-selection"] > .select-search > .react-select__control > .react-select__value-container > `
).should("have.text", "Run Query");
cy.get('[data-cy="query-selection-field"]').should("have.text", "runpy");
});
});

View file

@ -72,7 +72,7 @@ describe("RunJS", () => {
deleteDownloadsFolder();
});
it.only("should verify basic runjs", () => {
it("should verify basic runjs", () => {
const data = {};
data.customText = randomString(12);

View file

@ -0,0 +1,65 @@
import { fake } from "Fixtures/fake";
import { textInputText } from "Texts/textInput";
import { commonWidgetText, widgetValue, customValidation } from "Texts/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
addDefaultEventHandler,
verifyComponentValueFromInspector,
selectColourFromColourPicker,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
randomNumber,
closeAccordions,
} from "Support/utils/commonWidget";
import {
selectCSA,
selectEvent,
addSupportCSAData,
} from "Support/utils/events";
describe("Editor title", () => {
const data = {};
beforeEach(() => {
data.appName = fake.companyName;
cy.apiLogin();
cy.apiCreateApp(data.appName);
cy.visit("/");
});
afterEach(() => {
cy.apiDeleteApp();
});
it("should verify titles", () => {
cy.url().should("include", "/my-workspace");
cy.title().should("eq", "ToolJet - Dashboard");
cy.log(data.appName);
cy.openApp();
cy.url().should("include", Cypress.env("appId"));
cy.title().should("eq", `${data.appName} - Tooljet`);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
cy.title().should("eq", `${data.appName}`);
cy.go("back");
cy.releaseApp();
cy.url().then((url) => cy.visit(`/applications/${url.split("/").pop()}`));
cy.url().should("include", `/applications/${Cypress.env("appId")}`);
cy.title().should("eq", `${data.appName}`);
});
});

View file

@ -234,7 +234,7 @@ describe("Editor- Test Button widget", () => {
).should("have.attr", "disabled");
cy.get(commonWidgetSelector.parameterTogglebutton("Disable")).click();
cy.get('[data-cy="border-radius-input-field"]').realHover();
cy.get(
commonWidgetSelector.parameterFxButton(
commonWidgetText.parameterBorderRadius
@ -443,4 +443,27 @@ describe("Editor- Test Button widget", () => {
).should("not.be.visible");
cy.apiDeleteApp();
});
it("Should verify deletion of button component from right side panel", () => {
cy.get(".col-2").click();
cy.get(".list-item-popover-body > :nth-child(3)").click();
cy.get('[data-cy="yes-button"]').click();
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! (⌘ + Z to undo)"
);
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
cy.reload();
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
});
it("Should delete button via keyboard action", () => {
cy.realPress("Backspace");
cy.get('[data-cy="yes-button"]').click();
cy.verifyToastMessage(
`[class=go3958317564]`,
"Component deleted! (⌘ + Z to undo)"
);
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
cy.reload();
cy.notVisible(commonWidgetSelector.draggableWidget("button1"));
});
});

View file

@ -0,0 +1,110 @@
import { fake } from "Fixtures/fake";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import { addBasicData, verifyBasicData } from "Support/utils/button";
import { openEditorSidebar } from "Support/utils/commonWidget";
describe("Editor- component duplication", () => {
const data = {};
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-App`);
cy.openApp();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 500);
data.appName = `${fake.companyName}-App`;
data.alertMessage = fake.randomSentence;
data.widgetName = fake.widgetName;
data.customMessage = fake.randomSentence;
data.backgroundColor = fake.randomRgba;
data.textColor = fake.randomRgba;
data.loaderColor = fake.randomRgba;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.tooltipText = fake.randomSentence;
});
it("should verify duplication using copy and paste", () => {
addBasicData(data);
cy.forceClickOnCanvas();
openEditorSidebar("button1");
cy.realPress(["Control", "c"]);
cy.moveComponent("button1", 200, 200);
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Component copied successfully"
);
cy.forceClickOnCanvas();
cy.get('[data-cy="real-canvas"]').realPress(["Control", "v"]);
verifyBasicData("button2", data);
cy.reload();
cy.wait(2500);
verifyBasicData("button2", data);
});
it("should verify componen paste to container", () => {
addBasicData(data);
cy.forceClickOnCanvas();
openEditorSidebar("button1");
cy.realPress(["Control", "c"]);
cy.moveComponent("button1", 200, 90);
cy.dragAndDropWidget("Container", 300, 200);
cy.resizeWidget("container1", 800, 500);
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Component copied successfully",
false
);
cy.forceClickOnCanvas();
openEditorSidebar("container1");
cy.get(`${commonWidgetSelector.draggableWidget("container1")}>`)
.click({ force: true })
.within(() => {
cy.realPress(["Control", "v"]);
cy.wait(1000);
cy.get(commonWidgetSelector.draggableWidget("button2")).should(
"be.visible"
);
});
verifyBasicData("button2", data);
});
it("should verify duplication using right side panel", () => {
addBasicData(data);
cy.forceClickOnCanvas();
openEditorSidebar("button1");
cy.get('[data-cy="component-inspector-options"]>').click();
cy.get('[data-cy="component-inspector-duplicate-button"]').click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Component cloned succesfully"
);
cy.moveComponent("button1", 200, 200);
cy.forceClickOnCanvas();
cy.wait(1000);
verifyBasicData("button2", data);
});
it("should verify duplication using keyboard", () => {
addBasicData(data);
cy.forceClickOnCanvas();
openEditorSidebar("button1");
cy.realPress(["Control", "d"]);
cy.verifyToastMessage(
commonSelectors.toastMessage,
"Component cloned succesfully"
);
cy.moveComponent("button1", 200, 200);
cy.forceClickOnCanvas();
cy.wait(1000);
verifyBasicData("button2", data);
cy.reload();
cy.wait(2500);
verifyBasicData("button2", data);
});
});

View file

@ -356,14 +356,14 @@ describe("Basic components", () => {
verifyComponent("container1");
openEditorSidebar("container1");
editAndVerifyWidgetName("container2", ["Layout"]);
editAndVerifyWidgetName("container2", ["Devices"]);
cy.forceClickOnCanvas();
cy.resizeWidget("container2", 650, 400, false);
cy.waitForAutoSave();
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyComponent("container2", ["Layout"]);
verifyComponent("container2", ["Devices"]);
cy.go("back");
resizeQueryPanel(0);

View file

@ -1,300 +1,369 @@
import { fake } from "Fixtures/fake";
import { commonWidgetText } from "Texts/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import {
addDefaultEventHandler,
checkPaddingOfContainer,
closeAccordions,
editAndVerifyWidgetName,
openAccordion,
openEditorSidebar,
randomNumber,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyBoxShadowCss,
verifyComponentValueFromInspector,
verifyContainerElements,
verifyLayout,
verifyStylesGeneralAccordion,
verifyTooltip,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
import { numberInputText } from "Texts/numberInput";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
verifyComponentValueFromInspector,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
addTextWidgetToVerifyValue,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
randomNumber,
fillBoxShadowParams,
selectColourFromColourPicker,
} from "Support/utils/commonWidget";
addAllInputFieldColors,
addAndVerifyAdditionalActions,
addCustomWidthOfLabel,
addValidations,
verifyAlignment,
verifyCustomWidthOfLabel,
verifyInputFieldColors,
verifyLabelStyleElements,
} from "Support/utils/editor/inputFieldUtils";
import { addCSA, verifyCSA } from "Support/utils/editor/passwordNumberInput.js";
import { commonWidgetText } from "Texts/common";
describe("Number Input", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-numberinput-App`);
cy.openApp();
cy.dragAndDropWidget("Number Input");
cy.dragAndDropWidget("Number Input", 500, 500);
});
afterEach(() => {
cy.apiDeleteApp();
});
it("should verify the properties of the number input widget", () => {
itß("should verify the properties of the number input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
data.randomNumber = `${randomNumber(10, 99)}`;
data.minimumvalue = `${randomNumber(5, 10)}`;
data.maximumValue = `${randomNumber(90, 99)}`;
data.minimumLength = randomNumber(2, 4);
data.maximumLength = randomNumber(8, 10);
data.customText = randomNumber(12);
data.customNumber = randomNumber(12);
openEditorSidebar(numberInputText.defaultWidgetName);
editAndVerifyWidgetName(data.widgetName);
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", "99");
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
closeAccordions([
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
editAndVerifyWidgetName(data.widgetName, [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
openAccordion("Data", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
"Properties",
"Layout",
]);
verifyAndModifyParameter(
commonWidgetText.labelDefaultValue,
data.randomNumber
data.customNumber
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.randomNumber);
).verifyVisibleElement("have.value", data.customNumber);
verifyComponentValueFromInspector(data.widgetName, data.randomNumber);
verifyComponentValueFromInspector(data.widgetName, data.customNumber);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
data.customText = fake.randomSentence;
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
openAccordion("Data", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
"Properties",
"Layout",
]);
verifyAndModifyParameter(
commonWidgetText.labelMinimumValue,
data.minimumvalue
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
randomNumber(1, 4)
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", `${data.minimumvalue}`);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
"Events",
"Properties",
"Layout",
]);
verifyAndModifyParameter(
commonWidgetText.labelMaximumValue,
`${data.maximumValue}`
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
randomNumber(100, 110)
);
cy.forceClickOnCanvas;
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", `${data.maximumValue}`);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
"Events",
"Properties",
"Layout",
]);
verifyAndModifyParameter(
commonWidgetText.labelPlaceHolder,
data.randomNumber
data.customText
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", data.randomNumber);
.should("contain", data.customText);
verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
addDefaultEventHandler(data.customText);
cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}");
// verifyLayout(data.widgetName);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
`${data.customNumber}{Enter}`
);
cy.verifyToastMessage(commonSelectors.toastMessage, data.customText);
cy.forceClickOnCanvas();
// cy.get(commonWidgetSelector.changeLayoutButton).click();
// cy.get(
// commonWidgetSelector.parameterTogglebutton(
// commonWidgetText.parameterShowOnDesktop
// )
// ).click();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
addValidations(data.widgetName, data, "Min value", "Max value");
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customNumber
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelRegex)
)
.click()
.clearCodeMirror();
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.clear()
.type("1");
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
`Minimum value is ${data.minimumLength}`
);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.parameterInputField("Min value")
).clearAndTypeOnCodeMirror("0");
cy.forceClickOnCanvas();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
"99"
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
`Maximum value is ${data.maximumLength}`
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement("have.text", data.customText);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
addAndVerifyAdditionalActions(data.widgetName, data.tooltipText);
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
verifyLayout(data.widgetName, "Devices");
cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterShowOnDesktop
)
).click();
openEditorSidebar(data.widgetName);
openAccordion("Validation", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
cy.get(
commonWidgetSelector.parameterInputField("Min value")
).clearAndTypeOnCodeMirror("2");
cy.forceClickOnCanvas();
cy.waitForAutoSave();
openEditorSidebar(data.widgetName);
cy.get(commonWidgetSelector.widgetDocumentationLink).should(
"have.text",
numberInputText.numberInputDocumentationLink
"Read documentation for NumberInput"
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", data.customText);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
`${data.customText}{Enter}`
);
cy.verifyToastMessage(commonSelectors.toastMessage, data.customText);
cy.forceClickOnCanvas();
// cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
// cy.clearAndType(
// commonWidgetSelector.draggableWidget(data.widgetName),
// data.customText
// );
// cy.forceClickOnCanvas();
// cy.get(
// commonWidgetSelector.validationFeedbackMessage(data.widgetName)
// ).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
// cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.forceClickOnCanvas();
cy.clearAndType(commonWidgetSelector.draggableWidget(data.widgetName), "1");
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
`Minimum value is ${data.minimumLength}`
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
"13"
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
`Maximum value is ${data.maximumLength}`
);
cy.forceClickOnCanvas();
verifyTooltip(
commonWidgetSelector.draggableWidget(data.widgetName),
data.tooltipText
);
});
it("should verify the styles of the number input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.bgColor = fake.randomRgba;
data.borderColor = fake.randomRgba;
data.textColor = fake.randomRgba;
data.errorTextColor = fake.randomRgba;
data.iconColor = fake.randomRgba;
data.labelColor = fake.randomRgba;
data.widgetName = numberInputText.defaultWidgetName;
openEditorSidebar(numberInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
addAllInputFieldColors(data);
verifyAndModifyToggleFx(
commonWidgetText.parameterVisibility,
commonWidgetText.codeMirrorLabelTrue
);
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).should("not.be.visible");
cy.clearAndType('[data-cy="border-radius-input"]', "20");
cy.get('[data-cy="icon-visibility-button"]').click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterVisibility
)
).click();
verifyAndModifyToggleFx(
commonWidgetText.parameterDisable,
commonWidgetText.codeMirrorLabelFalse
);
cy.waitForAutoSave();
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).should("have.attr", "disabled");
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterDisable
)
).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyInputFieldColors("numberinput1", data);
verifyStylesGeneralAccordion(
numberInputText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
3
);
});
it("should verify the app preview", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.tooltipText = fake.randomSentence;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.randomNumber = randomNumber(10, 99);
data.minimumvalue = randomNumber(5, 10);
data.maximumValue = randomNumber(90, 99);
openEditorSidebar(numberInputText.defaultWidgetName);
verifyAndModifyParameter(
commonWidgetText.labelDefaultValue,
data.randomNumber
);
verifyAndModifyParameter(
commonWidgetText.labelMinimumValue,
`${data.minimumvalue}`
);
verifyAndModifyParameter(
commonWidgetText.labelMaximumValue,
`${data.maximumValue}`
);
verifyAndModifyParameter(
commonWidgetText.labelPlaceHolder,
data.randomNumber
);
verifyPropertiesGeneralAccordion(
numberInputText.defaultWidgetName,
data.tooltipText
4
);
openEditorSidebar(numberInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.forceClickOnCanvas();
openEditorSidebar(numberInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
openAccordion(commonWidgetText.accordionGenaral, [], 1);
cy.get(commonWidgetSelector.boxShadowColorPicker).click();
verifyContainerElements();
checkPaddingOfContainer(numberInputText.defaultWidgetName, 1);
cy.get('[data-cy="togglr-button-none"]').click();
checkPaddingOfContainer(numberInputText.defaultWidgetName, 0);
fillBoxShadowParams(
commonWidgetSelector.boxShadowDefaultParam,
data.boxShadowParam
);
verifyLabelStyleElements();
verifyAlignment(numberInputText.defaultWidgetName, "sideLeft");
cy.get('[data-cy="togglr-button-top"]').click();
verifyAlignment(numberInputText.defaultWidgetName, "topLeft");
cy.get('[data-cy="togglr-button-right"]').click();
verifyAlignment(numberInputText.defaultWidgetName, "topRight");
cy.get('[data-cy="togglr-button-side"]').click();
verifyAlignment(numberInputText.defaultWidgetName, "sideRight");
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(numberInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "50");
selectColourFromColourPicker(
commonWidgetText.boxShadowColor,
data.boxShadowColor,
3
"Text",
data.labelColor,
0,
commonWidgetSelector.colourPickerParent,
"0"
);
verifyWidgetColorCss(
`[data-cy="label-${numberInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
addTextWidgetToVerifyValue("components.numberinput1.value");
cy.waitForAutoSave();
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).verifyVisibleElement("have.value", data.randomNumber);
cy.clearAndType(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName),
randomNumber(1, 4)
verifyWidgetColorCss(
`[data-cy="label-${numberInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).verifyVisibleElement("have.value", `${data.minimumvalue}`);
cy.clearAndType(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName),
randomNumber(100, 110)
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).verifyVisibleElement("have.value", `${data.maximumValue}`);
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
)
.invoke("attr", "placeholder")
.should("contain", data.randomNumber);
verifyTooltip(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName),
data.tooltipText
);
cy.get(
commonWidgetSelector.draggableWidget(commonWidgetText.text1)
).verifyVisibleElement("have.text", data.maximumValue);
verifyAlignment(numberInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(numberInputText.defaultWidgetName, "50");
verifyInputFieldColors("numberinput1", data);
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyBoxShadowCss(
numberInputText.defaultWidgetName,
data.boxShadowColor,
data.boxShadowParam
);
cy.get(
commonWidgetSelector.draggableWidget(numberInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
});
it.skip("should verify the app preview", () => {});
it("should verify CSA", () => {
const data = {};
data.widgetName = numberInputText.defaultWidgetName;
data.customText = randomNumber(12);
addCSA(data);
verifyCSA(data);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyCSA(data);
});
});

View file

@ -1,35 +1,47 @@
import { fake } from "Fixtures/fake";
import { commonWidgetText, customValidation } from "Texts/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
verifyComponentValueFromInspector,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
addTextWidgetToVerifyValue,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
randomNumber,
fillBoxShadowParams,
selectColourFromColourPicker,
addDefaultEventHandler,
checkPaddingOfContainer,
closeAccordions,
editAndVerifyWidgetName,
openAccordion,
openEditorSidebar,
randomNumber,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyBoxShadowCss,
verifyComponentValueFromInspector,
verifyContainerElements,
verifyLayout,
verifyStylesGeneralAccordion,
verifyTooltip,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
import {
addAllInputFieldColors,
addAndVerifyAdditionalActions,
addCustomWidthOfLabel,
addValidations,
verifyAlignment,
verifyCustomWidthOfLabel,
verifyInputFieldColors,
verifyLabelStyleElements,
} from "Support/utils/editor/inputFieldUtils";
import {
addCSA,
randomString,
verifyCSA,
} from "Support/utils/editor/passwordNumberInput.js";
import { commonWidgetText } from "Texts/common";
import { passwordInputText } from "Texts/passwordInput";
import { randomString } from "Support/utils/textInput";
describe("Password Input", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(`${fake.companyName}-Passwordinput-App`);
cy.openApp();
cy.modifyCanvasSize(1200, 780);
cy.dragAndDropWidget("Password Input", 350, 300);
cy.dragAndDropWidget("Password Input", 500, 500);
});
afterEach(() => {
cy.apiDeleteApp();
@ -37,52 +49,81 @@ describe("Password Input", () => {
it("should verify the properties of the password input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.customText = fake.firstName;
data.tooltipText = fake.randomSentence;
data.minimumLength = randomNumber(1, 4);
data.maximumLength = randomNumber(8, 10);
data.customText = randomString(12);
openEditorSidebar(passwordInputText.defaultWidgetName);
closeAccordions(["Events", "Validation", "Properties", "Layout"]);
editAndVerifyWidgetName(data.widgetName);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", "password");
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
"Events",
closeAccordions([
"Data",
"Validation",
"Properties",
"Layout",
"Additional Actions",
"Devices",
"Events",
]);
editAndVerifyWidgetName(data.widgetName, [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
openAccordion("Data", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
verifyAndModifyParameter(
commonWidgetText.labelDefaultValue,
data.customText
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.customText);
verifyAndModifyParameter("Placeholder", data.customText);
verifyComponentValueFromInspector(data.widgetName, data.customText);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
data.customText = fake.randomSentence;
openEditorSidebar(data.widgetName);
openAccordion("Data", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
verifyAndModifyParameter(
commonWidgetText.labelPlaceHolder,
data.customText
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", data.customText);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
addDefaultEventHandler(data.customText);
cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}");
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
`${data.customText}{Enter}`
);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should(
"have.value",
data.customText
);
verifyComponentValueFromInspector(data.widgetName, data.customText);
cy.verifyToastMessage(commonSelectors.toastMessage, data.customText);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionValidation);
verifyAndModifyParameter(
commonWidgetText.labelRegex,
commonWidgetText.regularExpression
);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
addValidations(data.widgetName, data);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
@ -93,15 +134,10 @@ describe("Password Input", () => {
).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionValidation);
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelRegex)
).clearCodeMirror();
verifyAndModifyParameter(
commonWidgetText.labelMinLength,
data.minimumLength
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
@ -113,269 +149,194 @@ describe("Password Input", () => {
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength)
).clearCodeMirror();
verifyAndModifyParameter(
commonWidgetText.labelMaxLength,
data.maximumLength
);
).clearAndTypeOnCodeMirror("0");
cy.forceClickOnCanvas();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
data.customText.toUpperCase().replaceAll(" ", "").replaceAll(".", "")
);
cy.get('[data-cy="real-canvas"]').click("topLeft", { force: true });
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
);
openEditorSidebar(data.widgetName);
verifyAndModifyParameter(
commonWidgetText.labelcustomValidadtion,
customValidation(data.widgetName, data.customText)
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get('[data-cy="real-canvas"]').click("topLeft", { force: true });
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement("have.text", data.customText);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionProperties)
).click();
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText);
addAndVerifyAdditionalActions(data.widgetName, data.tooltipText);
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionProperties)
).click();
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
// verifyLayout(data.widgetName);
verifyLayout(data.widgetName, "Devices");
// cy.get(commonWidgetSelector.changeLayoutButton).click();
// cy.get(
// commonWidgetSelector.parameterTogglebutton(
// commonWidgetText.parameterShowOnDesktop
// )
// ).click();
cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterShowOnDesktop
)
).click();
openEditorSidebar(data.widgetName);
openAccordion("Validation", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength)
).clearAndTypeOnCodeMirror("5");
cy.forceClickOnCanvas();
cy.waitForAutoSave();
openEditorSidebar(data.widgetName);
cy.get(commonWidgetSelector.widgetDocumentationLink).should(
"have.text",
"Read documentation for PasswordInput"
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", data.customText);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
`${data.customText}{Enter}`
);
cy.verifyToastMessage(commonSelectors.toastMessage, data.customText);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
commonWidgetText.minLengthValidationError("5")
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText.toUpperCase().replaceAll(" ", "").replaceAll(".", "")
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
);
cy.forceClickOnCanvas();
verifyTooltip(
commonWidgetSelector.draggableWidget(data.widgetName),
data.tooltipText
);
});
it("should verify the styles of the password input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.bgColor = fake.randomRgba;
data.borderColor = fake.randomRgba;
data.textColor = fake.randomRgba;
data.errorTextColor = fake.randomRgba;
data.iconColor = fake.randomRgba;
data.labelColor = fake.randomRgba;
data.widgetName = passwordInputText.defaultWidgetName;
openEditorSidebar(passwordInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
addAllInputFieldColors(data);
verifyAndModifyToggleFx(
commonWidgetText.parameterVisibility,
commonWidgetText.codeMirrorLabelTrue
);
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("not.be.visible");
cy.clearAndType('[data-cy="border-radius-input"]', "20");
// cy.get('[data-cy="icon-visibility-button"]').click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterVisibility
)
).click();
verifyAndModifyToggleFx(
commonWidgetText.parameterDisable,
commonWidgetText.codeMirrorLabelFalse
);
cy.waitForAutoSave();
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("have.attr", "disabled");
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterDisable
)
).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyInputFieldColors("passwordinput1", data);
verifyStylesGeneralAccordion(
passwordInputText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
1
);
});
it("should verify the app preview", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.minimumLength = randomNumber(1, 4);
data.maximumLength = randomNumber(8, 10);
data.customText = randomString(12);
data.maxLengthText = randomString(data.maximumLength);
openEditorSidebar(passwordInputText.defaultWidgetName);
verifyAndModifyParameter("Placeholder", data.customText);
openAccordion(commonWidgetText.accordionValidation);
verifyAndModifyParameter(
commonWidgetText.labelRegex,
commonWidgetText.regularExpression
);
verifyAndModifyParameter(
commonWidgetText.labelMinLength,
data.minimumLength
);
verifyAndModifyParameter(
commonWidgetText.labelMaxLength,
data.maximumLength
);
verifyAndModifyParameter(
commonWidgetText.labelcustomValidadtion,
customValidation(passwordInputText.defaultWidgetName, data.customText)
);
verifyPropertiesGeneralAccordion(
passwordInputText.defaultWidgetName,
data.tooltipText
4
);
openEditorSidebar(passwordInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
verifyContainerElements();
checkPaddingOfContainer(passwordInputText.defaultWidgetName, 1);
cy.get('[data-cy="togglr-button-none"]').click();
checkPaddingOfContainer(passwordInputText.defaultWidgetName, 0);
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterVisibility
)
)
.click()
.click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
cy.waitForAutoSave();
cy.reload();
openEditorSidebar(passwordInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
openAccordion(commonWidgetText.accordionGenaral, [], 1);
cy.get(commonWidgetSelector.boxShadowColorPicker).click();
fillBoxShadowParams(
commonWidgetSelector.boxShadowDefaultParam,
data.boxShadowParam
);
verifyLabelStyleElements();
verifyAlignment(passwordInputText.defaultWidgetName, "sideLeft");
cy.get('[data-cy="togglr-button-top"]').click();
verifyAlignment(passwordInputText.defaultWidgetName, "topLeft");
cy.get('[data-cy="togglr-button-right"]').click();
verifyAlignment(passwordInputText.defaultWidgetName, "topRight");
cy.get('[data-cy="togglr-button-side"]').click();
verifyAlignment(passwordInputText.defaultWidgetName, "sideRight");
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(passwordInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "50");
selectColourFromColourPicker(
commonWidgetText.boxShadowColor,
data.boxShadowColor,
1
"Text",
data.labelColor,
0,
commonWidgetSelector.colourPickerParent,
"0"
);
addTextWidgetToVerifyValue("components.passwordinput1.value");
cy.waitForAutoSave();
verifyWidgetColorCss(
`[data-cy="label-${passwordInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
)
.invoke("attr", "placeholder")
.should("contain", data.customText);
cy.get('[data-cy="real-canvas"]').click("topLeft", { force: true });
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
)
.type(" ")
.type("{selectAll}{backspace}");
cy.get(
commonWidgetSelector.validationFeedbackMessage(
passwordInputText.defaultWidgetName
)
).verifyVisibleElement(
"have.text",
commonWidgetText.minLengthValidationError(data.minimumLength)
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName),
"t"
);
cy.get(
commonWidgetSelector.draggableWidget(commonWidgetText.text1)
).verifyVisibleElement("have.text", "t");
cy.forceClickOnCanvas();
cy.get('[data-cy="real-canvas"]').click("topLeft", { force: true });
cy.get(
commonWidgetSelector.validationFeedbackMessage(
passwordInputText.defaultWidgetName
)
).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.clearAndType(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName),
data.customText.toUpperCase()
);
cy.get('[data-cy="real-canvas"]').click("topLeft", { force: true });
// cy.get(
// commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
// )
// .type("1")
// .type("{selectAll}{backspace}");
cy.get(
commonWidgetSelector.validationFeedbackMessage(
passwordInputText.defaultWidgetName
)
).verifyVisibleElement(
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
verifyWidgetColorCss(
`[data-cy="label-${passwordInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName),
data.maxLengthText.toUpperCase()
);
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyAlignment(passwordInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(passwordInputText.defaultWidgetName, "50");
verifyInputFieldColors("passwordinput1", data);
verifyBoxShadowCss(
passwordInputText.defaultWidgetName,
@ -383,9 +344,22 @@ describe("Password Input", () => {
data.boxShadowParam
);
verifyTooltip(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName),
data.tooltipText
);
cy.get(
commonWidgetSelector.draggableWidget(passwordInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
});
it.skip("should verify the app preview", () => {});
it("should verify CSA", () => {
const data = {};
data.widgetName = passwordInputText.defaultWidgetName;
data.customText = randomString(12);
addCSA(data);
verifyCSA(data);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyCSA(data);
});
});

View file

@ -52,6 +52,7 @@ import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector";
import { textInputText } from "Texts/textInput";
import { deleteDownloadsFolder } from "Support/utils/common";
import { resizeQueryPanel } from "Support/utils/dataSource";
import { waitForQueryAction } from "Support/utils/queries";
describe("Table", () => {
beforeEach(() => {
@ -339,6 +340,32 @@ describe("Table", () => {
openEditorSidebar(tableText.defaultWidgetName);
editAndVerifyWidgetName(data.widgetName, []);
openEditorSidebar(data.widgetName);
cy.wait(500);
addAndOpenColumnOption("Fake-Link", `link`);
verifyAndEnterColumnOptionInput("key", "name");
cy.get('[data-cy="dropdown-link-target"] >>:eq(0)')
.click()
.find("input")
.type(`{selectAll}{backspace}new window{enter}`);
cy.forceClickOnCanvas();
cy.get('[data-cy="linksarah-cell-3"]')
.contains("a", "Sarah")
.should("have.attr", "target", "_blank");
openEditorSidebar(data.widgetName);
cy.get('[data-cy*="column-Fake-Link"]').click();
cy.get('[data-cy="dropdown-link-target"] >>:eq(0)')
.click()
.find("input")
.type(`{selectAll}{backspace}same window{enter}`);
cy.forceClickOnCanvas();
cy.get('[data-cy="linksarah-cell-3"]')
.contains("a", "Sarah")
.should("have.attr", "target", "_self");
//String/default
openEditorSidebar(data.widgetName);
cy.wait(500);
@ -1259,4 +1286,70 @@ describe("Table", () => {
.eq(0)
.verifyVisibleElement("have.value", "Jack");
});
it("should verify server-side paginaion", () => {
let dsName = fake.companyName;
cy.apiCreateGDS(
"http://localhost:3000/api/v2/data_sources",
`cypress-${dsName}-postgresql`,
"postgresql",
[
{ key: "host", value: Cypress.env("pg_host") },
{ key: "port", value: 5432 },
{ key: "database", value: Cypress.env("pg_user") },
{ key: "username", value: Cypress.env("pg_user") },
{ key: "password", value: Cypress.env("pg_password"), encrypted: true },
{ key: "ssl_enabled", value: false, encrypted: false },
{ key: "ssl_certificate", value: "none", encrypted: false },
]
);
cy.apiAddQueryToApp(
"q112",
{
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: `SELECT *
FROM server_side_pagination
ORDER BY id
LIMIT 10 OFFSET {{(components.table1.pageIndex-1)*10}};`,
},
`cypress-${dsName}-postgresql`,
"postgresql"
);
cy.reload();
openEditorSidebar(tableText.defaultWidgetName);
cy.get("[data-state=off]:eq(3)").click();
cy.get(
'[data-cy="total-records-server-side-input-field"]'
).clearAndTypeOnCodeMirror("50");
selectEvent("Page changed", "Run Query", 1);
cy.get('[data-cy="query-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}q112{enter}`);
cy.get('[data-cy="table-data-input-field"]').clearAndTypeOnCodeMirror(
`{{queries.q112.data`
);
cy.get('[data-cy="pagination-button-to-next"]').click();
waitForQueryAction("run");
cy.get('[data-cy="11-cell-0"]').verifyVisibleElement("have.text", "11");
cy.get('[data-cy="pagination-button-to-previous"]').click();
waitForQueryAction("run");
cy.get('[data-cy="1-cell-0"]').verifyVisibleElement("have.text", "1");
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.wait(4000);
cy.get('[data-cy="pagination-button-to-next"]').click();
cy.get('[data-cy="11-cell-0"]', { timeout: 10000 }).verifyVisibleElement(
"have.text",
"11"
);
cy.get('[data-cy="pagination-button-to-previous"]').click();
cy.get('[data-cy="1-cell-0"]', { timeout: 10000 }).verifyVisibleElement(
"have.text",
"1"
);
});
});

View file

@ -1,39 +1,34 @@
import { fake } from "Fixtures/fake";
import { textInputText } from "Texts/textInput";
import { commonWidgetText, widgetValue, customValidation } from "Texts/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
verifyNodeData,
openNode,
verifyValue,
deleteComponentFromInspector,
verifyfunctions,
} from "Support/utils/inspector";
import { commonWidgetSelector } from "Selectors/common";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
addDefaultEventHandler,
verifyComponentValueFromInspector,
selectColourFromColourPicker,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
verifyPropertiesGeneralAccordion,
selectFromSidebarDropdown,
addValueOnInput,
selectColourFromColourPicker,
verifyStylesGeneralAccordion,
randomNumber,
closeAccordions,
} from "Support/utils/commonWidget";
import {
addSupportCSAData,
selectCSA,
selectEvent,
addSupportCSAData,
} from "Support/utils/events";
import { randomString } from "Support/utils/textInput";
import { buttonText } from "Texts/button";
describe("Text Input", () => {
const data = {};
beforeEach(() => {
cy.viewport(1200, 1200);
data.appName = `${fake.companyName}-text-App1`;
cy.apiLogin();
cy.apiCreateApp();
cy.apiCreateApp(data.appName);
cy.openApp();
cy.dragAndDropWidget("Text");
});
@ -41,6 +36,60 @@ describe("Text Input", () => {
afterEach(() => {
cy.apiDeleteApp();
});
it("should verify properties of text component", () => {
data.componentName = fake.widgetName;
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
editAndVerifyWidgetName(data.componentName, [
"Data",
"Events",
"Additional Actions",
"Devices",
]);
cy.get(
'[data-cy="textcomponenttextinput-input-field"]'
).clearAndTypeOnCodeMirror("Cypress testing text component");
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.draggableWidget(data.componentName)
).verifyVisibleElement("have.text", "Cypress testing text component");
});
it.only("should verify styles of text component", () => {
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
selectFromSidebarDropdown("Weight", "bolder");
selectFromSidebarDropdown("Font variant", "initial");
addValueOnInput("Size", 25);
addValueOnInput("Line Height", 3);
addValueOnInput("Text Indent", 2);
addValueOnInput("Letter Spacing", 2);
addValueOnInput("Word Spacing", 2);
addValueOnInput("Border radius", 2);
data.textColor = fake.randomRgba;
data.backgroundColor = fake.randomRgba;
data.borderColor = fake.randomRgba;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
selectColourFromColourPicker("color", data.textColor);
selectColourFromColourPicker("background", data.backgroundColor);
selectColourFromColourPicker("border", data.borderColor);
data.colourHex = fake.randomRgbaHex;
verifyStylesGeneralAccordion(
"text1",
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
4,
"#00000090"
);
});
it("should verify preview of text component", () => {});
it("should verify CSA", () => {
const data = {};
data.customText = randomString(12);
@ -71,4 +120,22 @@ describe("Text Input", () => {
"not.be.visible"
);
});
it("should verify expossed values", () => {
cy.get(commonWidgetSelector.sidebarinspector).click();
verifyNodeData("components", "Object", "1 entry ");
openNode("components");
openNode("text1");
verifyValue("text", "String", `"Hello World👋"`);
verifyValue("isVisible", "Boolean", "true");
verifyValue("isLoading", "Boolean", "false");
verifyValue("isDisabled", "Boolean", "false");
verifyfunctions("clear", "Function");
verifyfunctions("setText", "Function");
verifyfunctions("visibility", "Function");
verifyfunctions("setDisabled", "Function");
verifyfunctions("setVisibility", "Function");
verifyfunctions("setLoadingState", "Function");
});
});

View file

@ -1,39 +1,45 @@
import { fake } from "Fixtures/fake";
import { textInputText } from "Texts/textInput";
import { commonWidgetText, widgetValue, customValidation } from "Texts/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { buttonText } from "Texts/button";
import {
verifyControlComponentAction,
randomString,
} from "Support/utils/textInput";
import {
openAccordion,
verifyAndModifyParameter,
openEditorSidebar,
verifyAndModifyToggleFx,
addDefaultEventHandler,
verifyComponentValueFromInspector,
selectColourFromColourPicker,
verifyBoxShadowCss,
verifyLayout,
verifyTooltip,
editAndVerifyWidgetName,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
randomNumber,
checkPaddingOfContainer,
closeAccordions,
editAndVerifyWidgetName,
openAccordion,
openEditorSidebar,
randomNumber,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyBoxShadowCss,
verifyComponentValueFromInspector,
verifyContainerElements,
verifyLayout,
verifyStylesGeneralAccordion,
verifyTooltip,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
import {
selectCSA,
selectEvent,
addSupportCSAData,
} from "Support/utils/events";
addAllInputFieldColors,
addAndVerifyAdditionalActions,
addCustomWidthOfLabel,
addValidations,
verifyAlignment,
verifyCustomWidthOfLabel,
verifyInputFieldColors,
verifyLabelStyleElements,
} from "Support/utils/editor/inputFieldUtils";
import {
addCSA,
randomString,
verifyCSA,
} from "Support/utils/editor/textInput";
import { commonWidgetText } from "Texts/common";
import { textInputText } from "Texts/textInput";
describe("Text Input", () => {
beforeEach(() => {
cy.apiLogin();
cy.apiCreateApp("11111");
cy.apiCreateApp(`${fake.companyName}-Textinput-App`);
cy.openApp();
cy.dragAndDropWidget("Text Input", 500, 500);
});
@ -43,7 +49,6 @@ describe("Text Input", () => {
it("should verify the properties of the text input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
data.minimumLength = randomNumber(1, 4);
@ -51,12 +56,25 @@ describe("Text Input", () => {
data.customText = randomString(12);
openEditorSidebar(textInputText.defaultWidgetName);
closeAccordions(["Validation", "General", "Properties", "Layout"]);
editAndVerifyWidgetName(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
closeAccordions([
"Data",
"Validation",
"General",
"Properties",
"Additional Actions",
"Devices",
"Events",
]);
editAndVerifyWidgetName(data.widgetName, [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
openAccordion("Data", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
verifyAndModifyParameter(
@ -74,11 +92,12 @@ describe("Text Input", () => {
data.customText = fake.randomSentence;
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionProperties, [
openAccordion("Data", [
"Data",
"Validation",
"General",
"Additional Actions",
"Devices",
"Events",
"Properties",
]);
verifyAndModifyParameter(
commonWidgetText.labelPlaceHolder,
@ -90,8 +109,8 @@ describe("Text Input", () => {
.should("contain", data.customText);
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]);
addDefaultEventHandler(widgetValue(data.widgetName));
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
addDefaultEventHandler(data.customText);
cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}");
cy.clearAndType(
@ -102,12 +121,9 @@ describe("Text Input", () => {
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
openEditorSidebar(data.widgetName);
openAccordion(commonWidgetText.accordionValidation);
verifyAndModifyParameter(
commonWidgetText.labelRegex,
commonWidgetText.regularExpression
);
addValidations(data.widgetName, data);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
@ -121,10 +137,7 @@ describe("Text Input", () => {
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelRegex)
).clearCodeMirror();
verifyAndModifyParameter(
commonWidgetText.labelMinLength,
data.minimumLength
);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
@ -136,15 +149,11 @@ describe("Text Input", () => {
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength)
).clearCodeMirror();
verifyAndModifyParameter(
commonWidgetText.labelMaxLength,
data.maximumLength
);
).clearAndTypeOnCodeMirror("0");
cy.forceClickOnCanvas();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
data.customText.toUpperCase().replaceAll(" ", "").replaceAll(".", "")
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
@ -152,33 +161,23 @@ describe("Text Input", () => {
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
);
verifyAndModifyParameter(
commonWidgetText.labelcustomValidadtion,
customValidation(data.widgetName, data.customText)
);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement("have.text", data.customText);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionProperties)
).click();
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
verifyPropertiesGeneralAccordion(data.widgetName, data.tooltipText);
addAndVerifyAdditionalActions(data.widgetName, data.tooltipText);
openEditorSidebar(data.widgetName);
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionProperties)
).click();
cy.get(
commonWidgetSelector.accordion(commonWidgetText.accordionValidation)
).click();
verifyLayout(data.widgetName);
verifyLayout(data.widgetName, "Devices");
cy.get(commonWidgetSelector.changeLayoutToDesktopButton).click();
cy.get(
@ -187,290 +186,180 @@ describe("Text Input", () => {
)
).click();
openEditorSidebar(data.widgetName);
openAccordion("Validation", [
"Data",
"Validation",
"Additional Actions",
"Devices",
"Events",
]);
cy.get(
commonWidgetSelector.parameterInputField(commonWidgetText.labelMinLength)
).clearAndTypeOnCodeMirror("5");
cy.forceClickOnCanvas();
cy.waitForAutoSave();
openEditorSidebar(data.widgetName);
cy.get(commonWidgetSelector.widgetDocumentationLink).should(
"have.text",
textInputText.textInputDocumentationLink
);
data.customText = fake.firstName;
verifyControlComponentAction(data.widgetName, data.customText);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.invoke("attr", "placeholder")
.should("contain", data.customText);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
`${data.customText}{Enter}`
);
cy.verifyToastMessage(commonSelectors.toastMessage, data.customText);
cy.forceClickOnCanvas();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText
);
cy.forceClickOnCanvas();
// cy.get(
// commonWidgetSelector.validationFeedbackMessage(data.widgetName)
// ).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
commonWidgetText.minLengthValidationError("5")
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(data.widgetName),
data.customText.toUpperCase().replaceAll(" ", "").replaceAll(".", "")
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(data.widgetName)
).verifyVisibleElement(
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
);
cy.forceClickOnCanvas();
verifyTooltip(
commonWidgetSelector.draggableWidget(data.widgetName),
data.tooltipText
);
});
it("should verify the styles of the text input widget", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.bgColor = fake.randomRgba;
data.borderColor = fake.randomRgba;
data.textColor = fake.randomRgba;
data.errorTextColor = fake.randomRgba;
data.iconColor = fake.randomRgba;
data.labelColor = fake.randomRgba;
ata.widgetName = textInputText.defaultWidgetName;
openEditorSidebar(textInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
addAllInputFieldColors(data);
verifyAndModifyToggleFx(
commonWidgetText.parameterVisibility,
commonWidgetText.codeMirrorLabelTrue
);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).should("not.be.visible");
cy.clearAndType('[data-cy="border-radius-input"]', "20");
cy.get('[data-cy="icon-visibility-button"]').click();
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterVisibility
)
).click();
verifyAndModifyToggleFx(
commonWidgetText.parameterDisable,
commonWidgetText.codeMirrorLabelFalse
);
cy.waitForAutoSave();
cy.get("[data-cy='draggable-widget-textinput1']")
.parent('[class="text-input true"]')
.invoke("attr", "data-disabled")
.and("contain", "true");
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterDisable
)
).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyStylesGeneralAccordion(
textInputText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
4
);
});
it("should verify the app preview", () => {
const data = {};
data.appName = `${fake.companyName}-App`;
data.widgetName = fake.widgetName;
data.tooltipText = fake.randomSentence;
data.maxLengthErrText = fake.randomSentence;
data.colourHex = fake.randomRgbaHex;
data.boxShadowColor = fake.randomRgba;
data.boxShadowParam = fake.boxShadowParam;
data.minimumLength = randomNumber(1, 4);
data.maximumLength = randomNumber(8, 10);
data.customText = randomString(12);
data.maxLengthText = randomString(data.maximumLength);
openEditorSidebar(textInputText.defaultWidgetName);
verifyAndModifyParameter(
commonWidgetText.labelDefaultValue,
data.customText
);
verifyAndModifyParameter(
commonWidgetText.labelPlaceHolder,
data.customText
);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]);
addDefaultEventHandler(widgetValue(textInputText.defaultWidgetName));
cy.get(commonWidgetSelector.eventSelection).type("On Enter Pressed{Enter}");
openAccordion(commonWidgetText.accordionValidation);
verifyAndModifyParameter(
commonWidgetText.labelRegex,
commonWidgetText.regularExpression
);
verifyAndModifyParameter(
commonWidgetText.labelMinLength,
data.minimumLength
);
verifyAndModifyParameter(
commonWidgetText.labelMaxLength,
data.maximumLength
);
verifyAndModifyParameter(
commonWidgetText.labelcustomValidadtion,
customValidation(textInputText.defaultWidgetName, data.customText)
);
verifyPropertiesGeneralAccordion(
textInputText.defaultWidgetName,
data.tooltipText
);
verifyControlComponentAction(
textInputText.defaultWidgetName,
data.customText
);
openEditorSidebar(textInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
verifyAndModifyParameter(
commonWidgetText.parameterBorderRadius,
commonWidgetText.borderRadiusInput
);
verifyStylesGeneralAccordion(
textInputText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
4
);
cy.waitForAutoSave();
cy.openInCurrentTab(commonWidgetSelector.previewButton);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).verifyVisibleElement("have.value", data.customText);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
)
.invoke("attr", "placeholder")
.should("contain", data.customText);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
)
.type(`{selectAll}{backspace}{enter}`)
.type(data.customText);
cy.forceClickOnCanvas();
cy.get(
commonWidgetSelector.validationFeedbackMessage(
textInputText.defaultWidgetName
)
).verifyVisibleElement("have.text", commonWidgetText.regexValidationError);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).clear();
cy.get(
commonWidgetSelector.validationFeedbackMessage(
textInputText.defaultWidgetName
)
).verifyVisibleElement(
"have.text",
commonWidgetText.minLengthValidationError(data.minimumLength)
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName),
data.customText.toUpperCase()
);
cy.get(
commonWidgetSelector.validationFeedbackMessage(
textInputText.defaultWidgetName
)
).verifyVisibleElement(
"have.text",
commonWidgetText.maxLengthValidationError(data.maximumLength)
);
cy.clearAndType(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName),
`${data.maxLengthText.toUpperCase()}{Enter}`
);
cy.verifyToastMessage(
commonSelectors.toastMessage,
data.maxLengthText.toUpperCase()
);
cy.get(
commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)
).should("have.text", data.maxLengthText.toUpperCase());
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
verifyInputFieldColors("textinput1", data);
verifyStylesGeneralAccordion(
textInputText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
4
);
openEditorSidebar(textInputText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
verifyContainerElements();
checkPaddingOfContainer(textInputText.defaultWidgetName, 1);
cy.get('[data-cy="togglr-button-none"]').click();
checkPaddingOfContainer(textInputText.defaultWidgetName, 0);
verifyLabelStyleElements();
verifyAlignment(textInputText.defaultWidgetName, "sideLeft");
cy.get('[data-cy="togglr-button-top"]').click();
verifyAlignment(textInputText.defaultWidgetName, "topLeft");
cy.get('[data-cy="togglr-button-right"]').click();
verifyAlignment(textInputText.defaultWidgetName, "topRight");
cy.get('[data-cy="togglr-button-side"]').click();
verifyAlignment(textInputText.defaultWidgetName, "sideRight");
cy.get('[data-cy="togglr-button-left"]').click();
verifyAlignment(textInputText.defaultWidgetName, "sideLeft");
addCustomWidthOfLabel("50");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "50");
selectColourFromColourPicker(
"Text",
data.labelColor,
0,
commonWidgetSelector.colourPickerParent,
"0"
);
verifyWidgetColorCss(
`[data-cy="label-${textInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyWidgetColorCss(
`[data-cy="label-${textInputText.defaultWidgetName}"]>label`,
"color",
data.labelColor,
true
);
verifyAlignment(textInputText.defaultWidgetName, "sideLeft");
verifyCustomWidthOfLabel(textInputText.defaultWidgetName, "50");
verifyInputFieldColors("textinput1", data);
verifyBoxShadowCss(
textInputText.defaultWidgetName,
data.boxShadowColor,
data.boxShadowParam
);
verifyTooltip(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName),
data.tooltipText
);
cy.get(
commonWidgetSelector.draggableWidget(textInputText.defaultWidgetName)
).should("have.css", "border-radius", "20px");
});
it.skip("should verify the app preview", () => {});
it("should verify CSA", () => {
const data = {};
data.customText = randomString(12);
data.widgetName = textInputText.defaultWidgetName;
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 200);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Visibility");
addCSA(data);
verifyCSA(data);
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget("Text input", 50, 50);
selectEvent("On change", "Control Component");
selectCSA("textinput1", "Set text", "500");
addSupportCSAData("text", "{{components.textinput2.value");
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 50, 200);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Clear", "500");
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 50, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Disable", "500");
cy.wait(500);
cy.get('[data-cy="Value-fx-button"]').click();
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror("{{true");
// cy.wait(1000);
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 300, 50);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set blur", "500");
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
cy.dragAndDropWidget(buttonText.defaultWidgetText, 300, 200);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set focus");
cy.clearAndType(
commonWidgetSelector.draggableWidget("textinput2"),
data.customText
);
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", "");
cy.get(commonWidgetSelector.draggableWidget("button5")).click();
cy.realType(data.customText);
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button4")).click();
cy.realType("not working");
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.parent()
.should("have.attr", "data-disabled", "true");
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
"not.be.visible"
);
cy.openInCurrentTab(commonWidgetSelector.previewButton);
verifyCSA(data);
});
});

View file

@ -29,7 +29,10 @@ function randomSentence() {
function randomRgba() {
let rgba = faker.color.rgb({ format: "decimal", includeAlpha: true });
rgba[rgba.length - 1] = rgba[rgba.length - 1].toPrecision(2) * 100;
let alpha = rgba[rgba.length - 1].toPrecision(2) * 100;
alpha = Math.min(Math.max(alpha, 20), 80);
rgba[rgba.length - 1] = alpha;
return rgba;
}

View file

@ -1,6 +1,18 @@
import { commonWidgetSelector } from "Selectors/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget";
import { buttonText } from "Texts/button";
import { commonWidgetText } from "Texts/common";
import {
addDefaultEventHandler,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyBoxShadowCss,
verifyLoaderColor,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
verifyTooltip,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
export const verifyControlComponentAction = (widgetName, value) => {
cy.forceClickOnCanvas();
@ -24,10 +36,97 @@ export const verifyControlComponentAction = (widgetName, value) => {
.clearAndTypeOnCodeMirror(value);
cy.forceClickOnCanvas();
cy.waitForAutoSave();
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click();
};
export const addBasicData = (data) => {
openEditorSidebar(buttonText.defaultWidgetName);
verifyAndModifyParameter(buttonText.buttonTextLabel, data.widgetName);
openAccordion(commonWidgetText.accordionEvents);
addDefaultEventHandler(data.alertMessage);
verifyPropertiesGeneralAccordion(
buttonText.defaultWidgetName,
data.tooltipText
);
openEditorSidebar(buttonText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
selectColourFromColourPicker(
buttonText.backgroundColor,
data.backgroundColor
);
cy.forceClickOnCanvas();
openEditorSidebar(buttonText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
selectColourFromColourPicker(buttonText.textColor, data.textColor, 1);
cy.forceClickOnCanvas();
openEditorSidebar(buttonText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
selectColourFromColourPicker(buttonText.loaderColor, data.loaderColor, 2);
cy.forceClickOnCanvas();
openEditorSidebar(buttonText.defaultWidgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
cy.get(
commonWidgetSelector.parameterInputField(
commonWidgetText.parameterBorderRadius
)
)
.first()
.clear()
.type(buttonText.borderRadiusInput);
verifyStylesGeneralAccordion(
buttonText.defaultWidgetName,
data.boxShadowParam,
data.colourHex,
data.boxShadowColor,
4
);
verifyControlComponentAction(
buttonText.defaultWidgetName,
data.customMessage
);
cy.waitForAutoSave();
};
export const verifyBasicData = (widgetName, data) => {
cy.get(commonWidgetSelector.draggableWidget(widgetName)).verifyVisibleElement(
"have.text",
data.widgetName
);
cy.wait(1500);
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click({
force: true,
});
cy.wait(500);
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
"have.value",
value
data.customMessage
);
verifyTooltip(
commonWidgetSelector.draggableWidget(widgetName),
data.tooltipText
);
verifyWidgetColorCss(widgetName, "background-color", data.backgroundColor);
verifyWidgetColorCss(widgetName, "color", data.textColor);
verifyLoaderColor(widgetName, data.loaderColor);
cy.get(commonWidgetSelector.draggableWidget(widgetName)).should(
"have.css",
"border-radius",
"20px"
);
verifyBoxShadowCss(widgetName, data.boxShadowColor, data.boxShadowParam);
};

View file

@ -1,10 +1,6 @@
import { faker } from "@faker-js/faker";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import {
commonWidgetText,
commonText,
codeMirrorInputLabel,
} from "Texts/common";
import { commonWidgetSelector } from "Selectors/common";
import { codeMirrorInputLabel, commonWidgetText } from "Texts/common";
export const openAccordion = (
accordionName,
@ -50,6 +46,7 @@ export const verifyAndModifyToggleFx = (
"have.text",
paramName
);
cy.get(commonWidgetSelector.parameterTogglebutton(paramName)).realHover();
cy.get(commonWidgetSelector.parameterFxButton(paramName, " > svg")).click();
if (defaultValue)
cy.get(commonWidgetSelector.parameterInputField(paramName))
@ -80,11 +77,11 @@ export const addAndVerifyTooltip = (widgetSelector, message) => {
export const editAndVerifyWidgetName = (
name,
accordion = ["General", "Properties", "Layout"]
accordion = ["General", "Properties", "Devices"]
) => {
closeAccordions(accordion);
cy.clearAndType(commonWidgetSelector.WidgetNameInputField, name);
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click({ force: true });
cy.get(commonWidgetSelector.draggableWidget(name)).trigger("mouseover");
cy.get(commonWidgetSelector.widgetConfigHandle(name))
@ -129,9 +126,14 @@ export const selectColourFromColourPicker = (
paramName,
colour,
index = 0,
parent = commonWidgetSelector.colourPickerParent
parent = commonWidgetSelector.colourPickerParent,
hasIndex = false
) => {
cy.get(commonWidgetSelector.stylePicker(paramName)).click();
if (hasIndex === false) {
cy.get(commonWidgetSelector.stylePicker(paramName)).last().click();
} else {
cy.get(commonWidgetSelector.stylePicker(paramName)).eq(hasIndex).click();
}
cy.get(parent)
.eq(index)
.then(() => {
@ -210,7 +212,8 @@ export const verifyAndModifyStylePickerFx = (
defaultValue,
value,
index = 0,
boxShadow = ""
boxShadow = "",
hasIndex = false
) => {
cy.get(commonWidgetSelector.parameterLabel(paramName)).should(
"have.text",
@ -224,8 +227,16 @@ export const verifyAndModifyStylePickerFx = (
cy.get(commonWidgetSelector.stylePickerValue(paramName))
.should("be.visible")
.verifyVisibleElement("have.text", defaultValue);
cy.get(commonWidgetSelector.parameterFxButton(paramName, " > svg")).click();
if (hasIndex === false) {
cy.get(commonWidgetSelector.stylePicker(paramName)).last().realHover();
} else {
cy.get(commonWidgetSelector.stylePicker(paramName))
.eq(hasIndex)
.realHover();
}
cy.get(commonWidgetSelector.parameterFxButton(paramName)).click();
cy.get(commonWidgetSelector.stylePickerFxInput(paramName)).within(() => {
cy.get(".CodeMirror-line")
.should("be.visible")
@ -274,9 +285,12 @@ export const verifyLoaderColor = (widgetName, color) => {
});
};
export const verifyLayout = (widgetName) => {
export const verifyLayout = (
widgetName,
layout = commonWidgetText.accordionLayout
) => {
openEditorSidebar(widgetName);
openAccordion(commonWidgetText.accordionLayout);
openAccordion(layout);
verifyAndModifyToggleFx(
commonWidgetText.parameterShowOnDesktop,
commonWidgetText.codeMirrorLabelTrue
@ -306,21 +320,24 @@ export const verifyStylesGeneralAccordion = (
boxShadowParameter,
hexColor,
boxShadowColor,
index = 0
index = 0,
boxShadowDefaultValue = commonWidgetText.boxShadowDefaultValue
) => {
openEditorSidebar(widgetName);
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
// openAccordion(commonWidgetText.accordionGenaral, []);
verifyAndModifyStylePickerFx(
commonWidgetText.parameterBoxShadow,
commonWidgetText.boxShadowDefaultValue,
boxShadowDefaultValue,
`${boxShadowParameter[0]}px ${boxShadowParameter[1]}px ${boxShadowParameter[2]}px ${boxShadowParameter[3]}px ${hexColor}`,
0,
"0px 0px 0px 0px "
);
cy.get(
commonWidgetSelector.parameterFxButton(commonWidgetText.parameterBoxShadow)
).click();
)
.realHover()
.click();
cy.get(
commonWidgetSelector.stylePicker(commonWidgetText.parameterBoxShadow)
@ -354,9 +371,7 @@ export const verifyTooltip = (widgetSelector, message) => {
.trigger("mouseover", { timeout: 2000 })
.trigger("mouseover")
.then(() => {
cy.get(commonWidgetSelector.tooltipLabel)
.last()
.should("have.text", message);
cy.get(".tooltip-inner").last().should("have.text", message);
});
};
@ -397,3 +412,41 @@ export const closeAccordions = (accordionNames = [], index = "0") => {
});
}
};
export const selectFromSidebarDropdown = (property, option) => {
cy.get(`[data-cy="dropdown-${property.toLowerCase().replace(/\s+/g, "-")}"]`)
.click()
.type(`${option}{enter}`);
};
export const addValueOnInput = (property, value) => {
cy.get(`[data-cy="${property.toLowerCase().replace(/\s+/g, "-")}-input"]`)
.clear()
.click()
.type(`${value}`);
};
export const verifyContainerElements = () => {
cy.get('[data-cy="widget-accordion-container"]').verifyVisibleElement(
"have.text",
"container"
);
cy.get('[data-cy="label-padding"]').verifyVisibleElement(
"have.text",
"Padding"
);
cy.get('[data-cy="togglr-button-default"]').verifyVisibleElement(
"have.text",
"Default"
);
cy.get('[data-cy="togglr-button-none"]').verifyVisibleElement(
"have.text",
"None"
);
};
export const checkPaddingOfContainer = (widgetName, value, mode = "Box") => {
cy.get(commonWidgetSelector.draggableWidget(widgetName))
.parents(`[role=${mode}]`)
.should("have.css", "padding", `${value}px`);
};

View file

@ -0,0 +1,98 @@
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget";
import { buttonText } from "Texts/button";
import { commonWidgetText } from "Texts/common";
import {
addDefaultEventHandler,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyBoxShadowCss,
verifyLoaderColor,
verifyPropertiesGeneralAccordion,
verifyStylesGeneralAccordion,
verifyTooltip,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
export const selectFromDropDown = (dropdownName, option, index = 3) => {
cy.get(`[data-cy="dropdown-input-${dropdownName.toLowerCase()}"]`).click(
"center"
);
cy.wait(100);
cy.contains(`[id*='react-select-${index}-option-']`, option).click();
};
export const clearSelection = (dropdownName) => {
cy.get(`[data-cy=dropdown-input-${dropdownName.toLowerCase()}]>>>>`)
.eq(1)
.click();
};
export const verifySelectedOptionOnDropdown = (dropdownName, option) => {
cy.get(`[data-cy=dropdown-input-${dropdownName.toLowerCase()}]>>>>`)
.eq(0)
.verifyVisibleElement("have.text", option);
};
export const verifyOptionOnSidePanel = (option) => {
cy.get(
`[data-cy="options-label-${option.toLowerCase()}"]`
).verifyVisibleElement("have.text", option);
};
export const deleteOption = (option) => {
cy.get(`[data-cy="options-label-${option.toLowerCase()}"]`).realHover();
cy.get(
`[data-cy="options-${option.toLowerCase()}-delete-icon"]>span`
).click();
cy.notVisible(`[data-cy="options-label-${option.toLowerCase()}"]`);
};
export const addNewOption = () => {
cy.get('[data-cy="add-new-dropdown-option"]').click();
};
export const updateOptionLabelAndValue = (option, label, value) => {
cy.get(`[data-cy="options-label-${option.toLowerCase()}"]`).click();
cy.get(`[data-cy="option-label-input-field"]`).clearAndTypeOnCodeMirror(
label
);
cy.get(`[data-cy="option-value-input-field"]`).clearAndTypeOnCodeMirror(
value
);
};
export const verifyOptionOnDropdown = (dropdownName, options) => {
cy.get(`[data-cy="dropdown-input-${dropdownName.toLowerCase()}"]`).click(
"center"
);
options.forEach((option, i) => {
cy.get(`#react-select-3-option-${i} > .d-flex`).verifyVisibleElement(
"have.text",
option
);
});
};
export const verifyOptionMenuElements = (option, options) => {
cy.get(`[data-cy="options-label-${option.toLowerCase()}"]`).click();
cy.get(`[data-cy="label-option-label"]`).verifyVisibleElement(
"have.text",
"Option label"
);
cy.get(`[data-cy="label-option-value"]`).verifyVisibleElement(
"have.text",
"Option value"
);
cy.get('[data-cy="label-mark-this-as-default-option"]').verifyVisibleElement(
"have.text",
"Mark this as default option"
);
cy.get('[data-cy="label-visibility"]')
.eq(1)
.verifyVisibleElement("have.text", "Visibility");
cy.get('[data-cy="label-disable"]')
.eq(1)
.verifyVisibleElement("have.text", "Disable");
};

View file

@ -0,0 +1,176 @@
import { commonWidgetSelector } from "Selectors/common";
import {
addAndVerifyTooltip,
openAccordion,
openEditorSidebar,
selectColourFromColourPicker,
verifyAndModifyParameter,
verifyAndModifyToggleFx,
verifyWidgetColorCss,
} from "Support/utils/commonWidget";
import { commonWidgetText, customValidation } from "Texts/common";
import { textInputText } from "Texts/textInput";
export const addValidations = (
widgetName,
data,
min = commonWidgetText.labelMinLength,
max = commonWidgetText.labelMaxLength
) => {
openEditorSidebar(widgetName);
openAccordion(commonWidgetText.accordionValidation);
verifyAndModifyParameter(
commonWidgetText.labelRegex,
commonWidgetText.regularExpression
);
verifyAndModifyParameter(min, data.minimumLength);
verifyAndModifyParameter(max, data.maximumLength);
verifyAndModifyParameter(
commonWidgetText.labelcustomValidadtion,
customValidation(data.widgetName, data.customText)
);
verifyAndModifyToggleFx("Make this field mandatory", "");
};
export const addAndVerifyAdditionalActions = (widgetName, tooltipText) => {
openEditorSidebar(widgetName);
openAccordion("Additional Actions");
verifyAndModifyToggleFx(
commonWidgetText.parameterVisibility,
commonWidgetText.codeMirrorLabelTrue
);
cy.get(commonWidgetSelector.draggableWidget(widgetName)).should(
"not.be.visible"
);
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterVisibility
)
).click();
verifyAndModifyToggleFx(
commonWidgetText.parameterDisable,
commonWidgetText.codeMirrorLabelFalse
);
cy.waitForAutoSave();
cy.get(commonWidgetSelector.draggableWidget(widgetName)).should(
"have.attr",
"disabled"
);
cy.get(
commonWidgetSelector.parameterTogglebutton(
commonWidgetText.parameterDisable
)
).click();
verifyAndModifyToggleFx(
commonWidgetText.loadingState,
commonWidgetText.codeMirrorLabelFalse
);
cy.get(commonWidgetSelector.draggableWidget(widgetName))
.parent()
.within(() => {
cy.get(".tj-widget-loader").should("be.visible");
});
cy.get(
commonWidgetSelector.parameterTogglebutton(commonWidgetText.loadingState)
).click();
addAndVerifyTooltip(
commonWidgetSelector.draggableWidget(widgetName),
tooltipText
);
};
export const addAllInputFieldColors = (data) => {
selectColourFromColourPicker("Background", data.bgColor);
selectColourFromColourPicker("Border", data.borderColor);
selectColourFromColourPicker("Text", data.textColor);
selectColourFromColourPicker("Error text", data.errorTextColor);
selectColourFromColourPicker("", data.iconColor);
cy.forceClickOnCanvas();
openEditorSidebar(data.widgetName);
cy.get('[data-cy="make-this-field-mandatory-toggle-button"]').click();
cy.get(commonWidgetSelector.buttonStylesEditorSideBar).click();
};
export const verifyInputFieldColors = (selectorInput, data) => {
verifyWidgetColorCss(selectorInput, "color", data.textColor);
verifyWidgetColorCss(selectorInput, "border-color", data.borderColor);
verifyWidgetColorCss(selectorInput, "background-color", data.bgColor);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).clear();
cy.forceClickOnCanvas();
cy.verifyCssProperty(
`[data-cy="${data.widgetName}-invalid-feedback"]`,
"color",
`rgba(${data.errorTextColor[0]}, ${data.errorTextColor[1]}, ${
data.errorTextColor[2]
}, ${data.errorTextColor[3] / 100})`
);
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.siblings("svg")
.should(
"have.css",
"stroke",
`rgba(${data.iconColor[0]}, ${data.iconColor[1]}, ${data.iconColor[2]}, ${
data.iconColor[3] / 100
})`
);
};
export const verifyLabelStyleElements = () => {
cy.get('[data-cy="widget-accordion-label"]').verifyVisibleElement(
"have.text",
"label"
);
cy.get('[data-cy="label-alignment"]').verifyVisibleElement(
"have.text",
"Alignment"
);
cy.get('[data-cy="label-width"]').verifyVisibleElement("have.text", "Width");
cy.get('[data-cy="width-input-field"]')
.eq(0)
.should("have.value", "33")
.siblings("label")
.should("have.text", "% of the field");
cy.get('[data-cy="auto-width-label"]').verifyVisibleElement(
"have.text",
"Auto width"
);
};
export const verifyAlignment = (componentName, position, side) => {
const alignments = {
topLeft: { y: "flex-column", x: "flex-start" },
topRight: { y: "flex-column", x: "flex-end" },
sideLeft: { y: "align-items-center", x: "flex-start" },
sideRight: { y: "align-items-center", x: "flex-end" },
};
const { y, x } = alignments[position];
cy.get(`[data-cy="label-${componentName.toLowerCase()}"]`)
.should("have.class", y)
.children("label")
.should("have.css", "justify-content", x);
};
export const verifyCustomWidthOfLabel = (componentName, width) => {
cy.get(`[data-cy="label-${componentName.toLowerCase()}"]`)
.children("label")
.should("have.attr", "style")
.and("include", `width: ${width}%`);
//
// .should("have.css", "width", `${width}%`);
};
export const addCustomWidthOfLabel = (width) => {
cy.get('[data-cy="auto-width-checkbox"]').click();
cy.get('[data-cy="width-input-field"]')
.eq(0)
.type(`{selectAll}{backspace}${width}`, { force: true });
};

View file

@ -0,0 +1,136 @@
import { faker } from "@faker-js/faker";
import { commonWidgetSelector } from "Selectors/common";
import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget";
import { buttonText } from "Texts/button";
import { commonWidgetText } from "Texts/common";
import {
addSupportCSAData,
selectCSA,
selectEvent,
} from "Support/utils/events";
export const verifyControlComponentAction = (widgetName, value) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget("button", 340, 90);
openEditorSidebar(widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
cy.get(commonWidgetSelector.addMoreEventHandlerLink).click();
cy.get(commonWidgetSelector.eventHandlerCard).eq(1).click();
cy.get(commonWidgetSelector.actionSelection).type("Control component{Enter}");
cy.get(commonWidgetSelector.eventComponentSelection).type("button1{Enter}");
cy.get(commonWidgetSelector.eventComponentActionSelection).type(
"Set text{Enter}"
);
cy.get(commonWidgetSelector.componentTextInput)
.find('[data-cy*="-input-field"]')
.clearAndTypeOnCodeMirror(["{{", `components.${widgetName}.value}}`]);
cy.clearAndType(commonWidgetSelector.draggableWidget(widgetName), value);
cy.get(
commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)
).should("have.text", value);
};
export const randomString = (length) => {
let str = faker.lorem.words();
return str.replace(/\s/g, "").substr(0, length);
};
export const verifyCSA = (data) => {
cy.clearAndType(
commonWidgetSelector.draggableWidget("textinput1"),
data.customText
);
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", "");
cy.get(commonWidgetSelector.draggableWidget("button5")).click();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.should("have.focus")
.realType(String(data.customText));
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button4")).click();
cy.realType("not working123");
cy.get(
commonWidgetSelector.draggableWidget(data.widgetName)
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button6")).click();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.parent()
.within(() => {
cy.get(".tj-widget-loader").should("be.visible");
});
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName))
.parent()
.should("have.attr", "data-disabled", "true");
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).should(
"not.be.visible"
);
};
export const addCSA = (data) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 50, 500);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Set visibility");
cy.forceClickOnCanvas();
cy.dragAndDropWidget("Text input", 50, 50);
selectEvent("On change", "Control Component");
cy.wait(500);
selectCSA(data.widgetName, "Set text", "500");
cy.wait(500);
addSupportCSAData("text", `{{components.textinput1.value`);
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 150, 400);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Clear", "500");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 250, 400);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Set disable", "500");
cy.wait(500);
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{true"
);
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 350, 400);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Set blur", "500");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 450, 400);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Set focus");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 300, 300);
selectEvent("On click", "Control Component");
selectCSA(data.widgetName, "Set loading", "500");
cy.wait(500);
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{true"
);
};

View file

@ -0,0 +1,5 @@
export const verifyBasicStyles = (data) => {
verifyWidgetColorCss(selectorInput, "border-color", data.borderColor);
verifyWidgetColorCss(selectorInput, "background-color", data.bgColor);
verifyWidgetColorCss(selectorInput, "color", data.textColor);
};

View file

@ -0,0 +1,159 @@
import { faker } from "@faker-js/faker";
import { commonWidgetSelector } from "Selectors/common";
import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget";
import { buttonText } from "Texts/button";
import { commonWidgetText } from "Texts/common";
import {
addSupportCSAData,
selectCSA,
selectEvent,
} from "Support/utils/events";
export const verifyControlComponentAction = (widgetName, value) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget("button", 340, 90);
openEditorSidebar(widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Devices"]);
cy.get(commonWidgetSelector.addMoreEventHandlerLink).click();
cy.get(commonWidgetSelector.eventHandlerCard).eq(1).click();
cy.get(commonWidgetSelector.actionSelection).type("Control component{Enter}");
cy.get(commonWidgetSelector.eventComponentSelection).type("button1{Enter}");
cy.get(commonWidgetSelector.eventComponentActionSelection).type(
"Set text{Enter}"
);
cy.get(commonWidgetSelector.componentTextInput)
.find('[data-cy*="-input-field"]')
.clearAndTypeOnCodeMirror(["{{", `components.${widgetName}.value}}`]);
cy.clearAndType(commonWidgetSelector.draggableWidget(widgetName), value);
cy.get(
commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)
).should("have.text", value);
};
export const randomString = (length) => {
let str = faker.lorem.words();
return str.replace(/\s/g, "").substr(0, length);
};
export const verifyCSA = (data) => {
cy.clearAndType(
commonWidgetSelector.draggableWidget("textinput2"),
data.customText
);
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", "");
cy.get(commonWidgetSelector.draggableWidget("button5")).click();
cy.realType(data.customText);
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button4")).click();
cy.realType("not working");
cy.get(
commonWidgetSelector.draggableWidget("textinput1")
).verifyVisibleElement("have.value", data.customText);
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.parent()
.should("have.attr", "data-disabled", "true");
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
"not.be.visible"
);
cy.get(commonWidgetSelector.draggableWidget("button7")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
"be.visible"
);
cy.get(commonWidgetSelector.draggableWidget("button6")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.parent()
.should("not.have.attr", "data-disabled", "true");
cy.get(commonWidgetSelector.draggableWidget("button8")).click();
cy.get(commonWidgetSelector.draggableWidget("textinput1"))
.parent()
.within(() => {
cy.get(".tj-widget-loader").should("be.visible");
});
};
export const addCSA = (data) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 50, 500);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Visibility");
cy.forceClickOnCanvas();
cy.dragAndDropWidget("Text input", 50, 50);
selectEvent("On change", "Control Component");
cy.wait(500);
selectCSA("textinput1", "Set text", "500");
cy.wait(500);
addSupportCSAData("text", "{{components.textinput2.value");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 150, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Clear", "500");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 250, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Disable", "500");
cy.wait(500);
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{true"
);
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 350, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set blur", "500");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 450, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set focus");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 550, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set disable", "500");
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 650, 400);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set visibility", "500");
cy.wait(500);
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{true"
);
cy.forceClickOnCanvas();
cy.dragAndDropWidget(buttonText.defaultWidgetText, 300, 300);
selectEvent("On click", "Control Component");
selectCSA("textinput1", "Set loading", "500");
cy.wait(500);
cy.get('[data-cy="event-Value-fx-button"]').click();
cy.get('[data-cy="event-Value-input-field"]').clearAndTypeOnCodeMirror(
"{{true"
);
};

View file

@ -5,6 +5,7 @@ export const selectEvent = (
addEventhandlerSelector = '[data-cy="add-event-handler"]',
eventIndex = 0
) => {
cy.intercept("PUT", "events").as("events");
cy.get(addEventhandlerSelector).eq(index).click();
cy.get('[data-cy="event-handler"]').eq(eventIndex).click();
cy.get('[data-cy="event-selection"]')
@ -15,6 +16,7 @@ export const selectEvent = (
.click()
.find("input")
.type(`{selectAll}{backspace}${action}{enter}`);
cy.wait("@events");
};
export const selectCSA = (
@ -22,6 +24,7 @@ export const selectCSA = (
componentAction,
debounce = `{selectAll}{backspace}`
) => {
cy.intercept("PUT", "events").as("events");
cy.get('[data-cy="action-options-component-selection-field"]')
.click()
.find("input")
@ -30,19 +33,37 @@ export const selectCSA = (
.click()
.find("input")
.type(`{selectAll}{backspace}${componentAction}{enter}`);
cy.get('[data-cy="-input-field"]')
cy.wait("@events");
cy.get('[data-cy="debounce-input-field"]')
.eq(1)
.click()
.type(`{selectAll}{backspace}${debounce}{enter}`);
cy.wait("@events");
};
export const addSupportCSAData = (field, data) => {
cy.get(`[data-cy="${field}-input-field"]`).clearAndTypeOnCodeMirror(data);
cy.intercept("PUT", "events").as("events");
cy.get(`[data-cy="event-${field}-input-field"]`)
.click({ force: true })
.clearAndTypeOnCodeMirror(data);
};
export const selectSupportCSAData = (option) => {
cy.intercept("PUT", "events").as("events");
cy.get('[data-cy="action-options-action-selection-field"]')
.eq(1)
.click()
.find("input")
.type(`{selectAll}{backspace}${option}{enter}`);
cy.wait("@events");
};
export const changeEventType = (event, eventIndex = 0) => {
cy.intercept("PUT", "events").as("events");
cy.get('[data-cy="event-handler"]').eq(eventIndex).click();
cy.get('[data-cy="event-selection"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${event}{enter}`);
cy.wait("@events");
};

View file

@ -24,11 +24,29 @@ export const verifyValue = (node, type, children, index = 0) => {
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-2`)
.eq(index)
.realHover()
.verifyVisibleElement("have.text", `${children}`);
.verifyVisibleElement("contain.text", `${children}`);
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
.eq(index)
.verifyVisibleElement("have.text", node);
.verifyVisibleElement("contain.text", node);
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`)
.eq(index)
.verifyVisibleElement("have.text", type);
.verifyVisibleElement("contain.text", type);
};
export const deleteComponentFromInspector = (node) => {
cy.get('[data-cy="inspector-node-components"] > .node-key').click();
cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).click();
cy.get('[style="height: 13px; width: 13px;"] > img').click();
};
export const verifyfunctions = (node, type, index = 0) => {
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`)
.eq(index)
.realHover()
.verifyVisibleElement("contain.text", `${type}`);
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
.eq(index)
.verifyVisibleElement("contain.text", node);
// cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`)
// .eq(index)
// .verifyVisibleElement("contain.text", type);
};

View file

@ -1,4 +1,5 @@
import { multipageSelector } from "Selectors/multipage";
import { commonSelectors } from "../../constants/selectors/common";
export const searchPage = (pageName) => {
cy.get('[title="Search"]').click();
@ -21,10 +22,17 @@ export const modifyPageHandle = (pageName, handle) => {
};
export const detetePage = (pageName) => {
cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`).click();
cy.get(multipageSelector.pageMenuIcon).click();
cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`)
.click()
.parent()
.find(multipageSelector.pageMenuIcon)
.click();
cy.get(multipageSelector.deletePageOptionButton).click();
cy.get(multipageSelector.modalConfirmButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
`${pageName} page deleted.`
);
cy.notVisible(`[data-cy="pages-name-${pageName.toLowerCase()}"]`);
};
@ -73,3 +81,12 @@ export const hideOrUnhidePageMenu = () => {
cy.get(multipageSelector.pagesMenuIcon).click();
cy.get(multipageSelector.disableMenuToggle).click();
};
export const disableOrEnablePage = (pageName, option = "disable") => {
cy.get(`[data-cy="pages-name-${pageName.toLowerCase()}"]`)
.click()
.parent()
.find(multipageSelector.pageMenuIcon)
.click();
cy.get(`[data-cy="${option}-option-button"]`).click();
};

View file

@ -1,4 +1,5 @@
import { postgreSqlSelector } from "Selectors/postgreSql";
import { selectEvent } from "Support/utils/events";
export const selectQueryFromLandingPage = (dbName, label) => {
cy.get(
@ -11,7 +12,7 @@ export const selectQueryFromLandingPage = (dbName, label) => {
export const deleteQuery = (queryName) => {
cy.get(`[data-cy="list-query-${queryName}"]`).realHover();
cy.get(`[data-cy="elete-query-${queryName}"]`).click();
cy.get(`[data-cy="delete-query-${queryName}"]`).click();
};
export const query = (action) => {
@ -43,3 +44,19 @@ export const waitForQueryAction = (action) => {
"button-loading"
);
};
export const chainQuery = (currentQuery, trigger) => {
cy.get(`[data-cy="list-query-${currentQuery}"]`).click();
selectEvent("Query Success", "Run Query");
cy.get('[data-cy="query-selection-field"]')
.click()
.find("input")
.type(`{selectAll}{backspace}${trigger}{enter}`);
};
export const addSuccessNotification = (notification) => {
changeQueryToggles("notification-on-success");
cy.get('[data-cy="success-message-input-field"]').clearAndTypeOnCodeMirror(
notification
);
};

View file

@ -1,35 +0,0 @@
import { commonWidgetSelector } from "Selectors/common";
import { openAccordion, openEditorSidebar } from "Support/utils/commonWidget";
import { buttonText } from "Texts/button";
import { commonWidgetText } from "Texts/common";
import { faker } from "@faker-js/faker";
export const verifyControlComponentAction = (widgetName, value) => {
cy.forceClickOnCanvas();
cy.dragAndDropWidget("button", 340, 90);
openEditorSidebar(widgetName);
openAccordion(commonWidgetText.accordionEvents, ["Validation", "Layout"]);
cy.get(commonWidgetSelector.addMoreEventHandlerLink).click();
cy.get(commonWidgetSelector.eventHandlerCard).eq(1).click();
cy.get(commonWidgetSelector.actionSelection).type("Control component{Enter}");
cy.get(commonWidgetSelector.eventComponentSelection).type("button1{Enter}");
cy.get(commonWidgetSelector.eventComponentActionSelection).type(
"Set text{Enter}"
);
cy.get(commonWidgetSelector.componentTextInput)
.find('[data-cy*="-input-field"]')
.clearAndTypeOnCodeMirror(["{{", `components.${widgetName}.value}}`]);
cy.clearAndType(commonWidgetSelector.draggableWidget(widgetName), value);
cy.get(
commonWidgetSelector.draggableWidget(buttonText.defaultWidgetName)
).should("have.text", value);
};
export const randomString = (length) => {
let str = faker.lorem.words();
return str.replace(/\s/g, "").substr(0, length);
};

View file

@ -3,7 +3,6 @@ import { DocsCard } from './';
import styles from './DocsCard.css'
export const DocsCardList = ({ list }) => {
console.log('list', list);
return (
<div className='card-container-setup'>
{list.map(item => <DocsCard key={item.docId} label={item.label} imgSrc={item.docId.split('/')[1]} link={item.href} />)}

View file

@ -1 +1 @@
2.28.4
2.29.0

View file

@ -495,6 +495,7 @@
"properties": "Properties",
"events": "Events",
"layout": "Layout",
"devices":"Devices",
"styles": "Styles",
"general": "General",
"validation": "Validation",
@ -507,6 +508,7 @@
"visibility": "Visibility",
"disable": "Disable",
"borderRadius": "Border radius",
"transformation": "Transformation",
"boxShadow": "Box shadow",
"tooltip": "Tooltip",
"showOnDesktop": "Show on desktop",

22360
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,11 +10,12 @@
"@emoji-mart/react": "^1.1.1",
"@radix-ui/colors": "^0.1.8",
"@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.0.4",
"@react-google-maps/api": "^2.18.1",
"@sentry/react": "^7.60.0",
"@sentry/tracing": "^7.60.0",
"@sentry/webpack-plugin": "^2.5.0",
"@sentry/react": "^7.100.1",
"@sentry/tracing": "^7.100.1",
"@sentry/webpack-plugin": "^2.14.0",
"@tabler/icons-react": "^2.4.0",
"@tooljet/plugins": "../plugins",
"@uiw/react-codemirror": "^3.0.6",
@ -79,6 +80,7 @@
"react-lazy-load-image-component": "^1.5.6",
"react-lazyload": "^3.2.0",
"react-loading-skeleton": "^3.1.1",
"react-markdown": "^9.0.0",
"react-mentions": "^4.4.7",
"react-multi-select-component": "^4.3.4",
"react-pdf": "^6.2.2",

View file

@ -31,6 +31,7 @@ import 'react-tooltip/dist/react-tooltip.css';
import { getWorkspaceIdOrSlugFromURL } from '@/_helpers/routes';
import ErrorPage from '@/_components/ErrorComponents/ErrorPage';
import WorkspaceConstants from '@/WorkspaceConstants';
import { useAppDataStore } from '@/_stores/appDataStore';
const AppWrapper = (props) => {
return (
@ -57,6 +58,7 @@ class AppComponent extends React.Component {
};
fetchMetadata = () => {
tooljetService.fetchMetaData().then((data) => {
useAppDataStore.getState().actions.setMetadata(data);
localStorage.setItem('currentVersion', data.installed_version);
if (data.latest_version && lt(data.installed_version, data.latest_version) && data.version_ignored === false) {
this.setState({ updateAvailable: true });

View file

@ -5,6 +5,7 @@ import { CustomSelect } from './CustomSelect';
import { toast } from 'react-hot-toast';
import { shallow } from 'zustand/shallow';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { useEditorStore } from '@/_stores/editorStore';
const appVersionLoadingStatus = Object.freeze({
loading: 'loading',
@ -12,7 +13,13 @@ const appVersionLoadingStatus = Object.freeze({
error: 'error',
});
export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion, onVersionDelete }) {
export const AppVersionsManager = function ({
appId,
setAppDefinitionFromVersion,
onVersionDelete,
isEditable = true,
isViewer,
}) {
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
const [deleteVersion, setDeleteVersion] = useState({
versionId: '',
@ -29,6 +36,12 @@ export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion
}),
shallow
);
const { currentLayout } = useEditorStore(
(state) => ({
currentLayout: state?.currentLayout,
}),
shallow
);
useEffect(() => {
if (appVersions && appVersions.length > 0) {
@ -102,7 +115,7 @@ export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion
{appVersion.name}
</div>
</div>
{appVersion.id !== releasedVersionId && (
{isEditable && appVersion.id !== releasedVersionId && (
<div
className="col cursor-pointer m-auto app-version-delete"
onClick={(e) => {
@ -139,15 +152,32 @@ export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion
};
return (
<div className="app-versions-selector" data-cy="app-version-selector">
<CustomSelect
isLoading={appVersionStatus === 'loading'}
options={options}
value={editingVersion.id}
onChange={(id) => selectVersion(id)}
{...customSelectProps}
className={` ${darkMode && 'dark-theme'}`}
/>
<div
className="d-flex align-items-center p-0"
style={{ margin: isViewer && currentLayout === 'mobile' ? '0px' : '0 24px' }}
>
<div
className={cx('d-flex version-manager-container p-0', {
'w-100': isViewer && currentLayout === 'mobile',
})}
>
<div
className={cx('app-versions-selector', {
'w-100': isViewer && currentLayout === 'mobile',
})}
data-cy="app-version-selector"
>
<CustomSelect
isLoading={appVersionStatus === 'loading'}
options={options}
value={editingVersion?.id}
onChange={(id) => selectVersion(id)}
{...customSelectProps}
className={` ${darkMode && 'dark-theme'}`}
isEditable={isEditable}
/>
</div>
</div>
</div>
);
};

View file

@ -7,7 +7,8 @@ import { CreateVersion } from './CreateVersionModal';
import { ConfirmDialog } from '@/_components';
import { defaultAppEnvironments } from '@/_helpers/utils';
const Menu = (props) => {
export const Menu = (props) => {
const isEditable = props.selectProps.isEditable;
return (
<components.Menu {...props}>
<div>
@ -20,7 +21,7 @@ const Menu = (props) => {
<div className="col-10 text-truncate tj-text-xsm color-slate12">
{props?.selectProps?.value?.appVersionName}
</div>
{!props?.selectProps?.value?.isReleasedVersion && (
{isEditable && !props?.selectProps?.value?.isReleasedVersion && (
<div className="col-1">
<svg
className="icon"
@ -44,35 +45,37 @@ const Menu = (props) => {
</div>
<hr className="m-0" />
<div>{props.children}</div>
<div
className="cursor-pointer tj-text-xsm"
style={{ padding: '8px 12px', color: '#3E63DD' }}
onClick={() => props.selectProps.setShowCreateAppVersion(true)}
>
<svg
className="icon me-1"
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{isEditable && (
<div
className="cursor-pointer tj-text-xsm"
style={{ padding: '8px 12px', color: '#3E63DD' }}
onClick={() => props.selectProps.setShowCreateAppVersion(true)}
>
<rect width="34" height="34" rx="6" fill="#F0F4FF" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 11C17.4142 11 17.75 11.3358 17.75 11.75V16.25H22.25C22.6642 16.25 23 16.5858 23 17C23 17.4142 22.6642 17.75 22.25 17.75H17.75V22.25C17.75 22.6642 17.4142 23 17 23C16.5858 23 16.25 22.6642 16.25 22.25V17.75H11.75C11.3358 17.75 11 17.4142 11 17C11 16.5858 11.3358 16.25 11.75 16.25H16.25V11.75C16.25 11.3358 16.5858 11 17 11Z"
fill="#3E63DD"
/>
</svg>
Create new version
</div>
<svg
className="icon me-1"
width="34"
height="34"
viewBox="0 0 34 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="34" height="34" rx="6" fill="#F0F4FF" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17 11C17.4142 11 17.75 11.3358 17.75 11.75V16.25H22.25C22.6642 16.25 23 16.5858 23 17C23 17.4142 22.6642 17.75 22.25 17.75H17.75V22.25C17.75 22.6642 17.4142 23 17 23C16.5858 23 16.25 22.6642 16.25 22.25V17.75H11.75C11.3358 17.75 11 17.4142 11 17C11 16.5858 11.3358 16.25 11.75 16.25H16.25V11.75C16.25 11.3358 16.5858 11 17 11Z"
fill="#3E63DD"
/>
</svg>
Create new version
</div>
)}
</div>
</components.Menu>
);
};
const SingleValue = ({ selectProps }) => {
export const SingleValue = ({ selectProps }) => {
return (
<div className="d-inline-flex align-items-center" data-cy="app-version-label" style={{ gap: '8px' }}>
<div className="d-inline-flex align-items-center" style={{ gap: '2px' }}>
@ -105,16 +108,15 @@ const SingleValue = ({ selectProps }) => {
</div>
);
};
export const CustomSelect = ({ ...props }) => {
const [showEditAppVersion, setShowEditAppVersion] = useState(false);
const [showCreateAppVersion, setShowCreateAppVersion] = useState(false);
const { deleteVersion, deleteAppVersion, resetDeleteModal } = props;
const { deleteVersion, deleteAppVersion, resetDeleteModal, isEditable } = props;
return (
<>
{showCreateAppVersion && (
{isEditable && showCreateAppVersion && (
<CreateVersion
{...props}
showCreateAppVersion={showCreateAppVersion}
@ -122,7 +124,9 @@ export const CustomSelect = ({ ...props }) => {
/>
)}
<EditVersion {...props} showEditAppVersion={showEditAppVersion} setShowEditAppVersion={setShowEditAppVersion} />
{isEditable && (
<EditVersion {...props} showEditAppVersion={showEditAppVersion} setShowEditAppVersion={setShowEditAppVersion} />
)}
{/* When we merge this code to EE update the defaultAppEnvironments object with rest of default environments (then delete this comment)*/}
<ConfirmDialog
show={deleteVersion.showModal}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState, useMemo, useContext, useRef, memo } from 'react';
import React, { useEffect, useState, useMemo, useContext, useRef, memo, useCallback } from 'react';
import { Button } from './Components/Button';
import { Image } from './Components/Image';
import { Text } from './Components/Text';
@ -68,9 +68,8 @@ import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
import { useTranslation } from 'react-i18next';
import { useCurrentState } from '@/_stores/currentStateStore';
import { useAppInfo } from '@/_stores/appDataStore';
import WidgetIcon from '@/../assets/images/icons/widgets';
const AllComponents = {
export const AllComponents = {
Button,
Image,
Text,
@ -149,22 +148,13 @@ export const Box = memo(
sideBarDebugger,
readOnly,
childComponents,
isResizing,
adjustHeightBasedOnAlignment,
currentLayout,
}) => {
const { t } = useTranslation();
const backgroundColor = yellow ? 'yellow' : '';
const currentState = useCurrentState();
let styles = {
height: '100%',
padding: '1px',
};
if (inCanvas) {
styles = {
...styles,
};
}
const { events } = useAppInfo();
const componentMeta = useMemo(() => {
@ -183,11 +173,11 @@ export const Box = memo(
: [resolvedProperties, []];
const resolvedStyles = resolveStyles(component, currentState, null, customResolvables);
const [validatedStyles, styleErrors] =
mode === 'edit' && component.validate
? validateProperties(resolvedStyles, componentMeta.styles)
: [resolvedStyles, []];
validatedStyles.visibility = validatedStyles.visibility !== false ? true : false;
const resolvedGeneralProperties = resolveGeneralProperties(component, currentState, null, customResolvables);
const [validatedGeneralProperties, generalPropertiesErrors] =
@ -205,6 +195,15 @@ export const Box = memo(
const darkMode = localStorage.getItem('darkMode') === 'true';
const { variablesExposedForPreview, exposeToCodeHinter } = useContext(EditorContext) || {};
let styles = {
height: '100%',
};
if (inCanvas) {
styles = {
...styles,
};
}
useEffect(() => {
if (!component?.parent) {
onComponentOptionChanged && onComponentOptionChanged(component, 'id', id);
@ -279,17 +278,29 @@ export const Box = memo(
...{ validationObject: component.definition.validation, currentState },
customResolveObjects: customResolvables,
});
const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput', 'Text'];
return (
<OverlayTrigger
placement={inCanvas ? 'auto' : 'top'}
delay={{ show: 500, hide: 0 }}
trigger={inCanvas && !validatedGeneralProperties.tooltip?.toString().trim() ? null : ['hover', 'focus']}
trigger={
inCanvas && shouldAddBoxShadow.includes(component.component)
? !validatedProperties.tooltip?.toString().trim()
? null
: ['hover', 'focus']
: !validatedGeneralProperties.tooltip?.toString().trim()
? null
: ['hover', 'focus']
}
overlay={(props) =>
renderTooltip({
props,
text: inCanvas
? `${validatedGeneralProperties.tooltip}`
? `${
shouldAddBoxShadow.includes(component.component)
? validatedProperties.tooltip
: validatedGeneralProperties.tooltip
}`
: `${t(`widget.${component.name}.description`, component.description)}`,
})
}
@ -298,6 +309,7 @@ export const Box = memo(
style={{
...styles,
backgroundColor,
padding: validatedStyles?.padding ? (validatedStyles?.padding == 'default' ? '1px' : '0px') : '1px',
}}
role={preview ? 'BoxPreview' : 'Box'}
>
@ -320,7 +332,12 @@ export const Box = memo(
canvasWidth={canvasWidth}
properties={validatedProperties}
exposedVariables={exposedVariables}
styles={{ ...validatedStyles, boxShadow: validatedGeneralStyles?.boxShadow }}
styles={{
...validatedStyles,
...(!shouldAddBoxShadow.includes(component.component)
? { boxShadow: validatedGeneralStyles?.boxShadow }
: {}),
}}
setExposedVariable={(variable, value) => onComponentOptionChanged(component, variable, value, id)}
setExposedVariables={(variableSet) =>
onComponentOptionsChanged(component, Object.entries(variableSet), id)
@ -338,6 +355,9 @@ export const Box = memo(
resetComponent={() => setResetStatus(true)}
childComponents={childComponents}
dataCy={`draggable-widget-${String(component.name).toLowerCase()}`}
isResizing={isResizing}
adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment}
currentLayout={currentLayout}
></ComponentToRender>
) : (
<></>

View file

@ -35,8 +35,17 @@ import cx from 'classnames';
import { Alert } from '@/_ui/Alert/Alert';
import { useCurrentState } from '@/_stores/currentStateStore';
import ClientServerSwitch from './Elements/ClientServerSwitch';
import { CodeHinterContext } from './CodeHinterContext';
import Switch from './Elements/Switch';
import Checkbox from './Elements/Checkbox';
import Slider from './Elements/Slider';
import { Input } from './Elements/Input';
import { Icon } from './Elements/Icon';
import { Visibility } from './Elements/Visibility';
import { NumberInput } from './Elements/NumberInput';
import { validateProperty } from '../component-properties-validation';
const HIDDEN_CODE_HINTER_LABELS = ['Table data', 'Column data'];
const HIDDEN_CODE_HINTER_LABELS = ['Table data', 'Column data', 'Text Format', 'TextComponentTextInput'];
const AllElements = {
Color,
@ -47,11 +56,19 @@ const AllElements = {
Number,
BoxShadow,
ClientServerSwitch,
Slider,
Switch,
Input,
Checkbox,
Icon,
Visibility,
NumberInput,
};
export function CodeHinter({
initialValue,
onChange,
onVisibilityChange,
mode,
theme,
lineNumbers,
@ -77,8 +94,12 @@ export function CodeHinter({
callgpt = () => null,
isCopilotEnabled = false,
currentState: _currentState,
verticalLine = true,
isIcon = false,
inspectorTab,
staticText,
}) {
const context = useContext(CodeHinterContext);
const darkMode = localStorage.getItem('darkMode') === 'true';
const options = {
lineNumbers: lineNumbers ?? false,
@ -92,7 +113,8 @@ export function CodeHinter({
placeholder,
};
const currentState = useCurrentState();
const [realState, setRealState] = useState(currentState);
const [realState, setRealState] = useState({ ...currentState, ..._currentState, ...context });
const [currentValue, setCurrentValue] = useState('');
const [prevCurrentValue, setPrevCurrentValue] = useState(null);
@ -102,6 +124,7 @@ export function CodeHinter({
const [isFocused, setFocused] = useState(false);
const [heightRef, currentHeight] = useHeight();
const isPreviewFocused = useRef(false);
const [isPropertyHovered, setPropertyHovered] = useState(false);
const wrapperRef = useRef(null);
// Todo: Remove this when workspace variables are deprecated
@ -172,13 +195,10 @@ export function CodeHinter({
}, []);
useEffect(() => {
if (_currentState) {
setRealState(_currentState);
} else {
setRealState(currentState);
}
const newState = { ...currentState, ..._currentState, ...context };
setRealState(newState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify({ currentState, _currentState })]);
}, [JSON.stringify([currentState.components, _currentState, context])]);
useEffect(() => {
const handleClickOutside = (event) => {
@ -199,38 +219,43 @@ export function CodeHinter({
};
}, [wrapperRef, isFocused, isPreviewFocused, currentValue, prevCountRef, isOpen]);
useEffect(() => {
const updatePreview = () => {
let globalPreviewCopy = null;
let globalErrorCopy = null;
if (enablePreview && isFocused && JSON.stringify(currentValue) !== JSON.stringify(prevCurrentValue)) {
const [preview, error] = getPreviewAndErrorFromValue(currentValue);
// checking type error if any in run time
const [_valid, errorMessages] = checkTypeErrorInRunTime(preview);
const [preview, error] = getPreviewAndErrorFromValue(currentValue);
setPrevCurrentValue(currentValue);
setPrevCurrentValue(currentValue);
if (error || !_valid || typeof preview === 'function') {
globalPreviewCopy = null;
globalErrorCopy = error || errorMessages?.[errorMessages?.length - 1];
setResolvingError(error || errorMessages?.[errorMessages?.length - 1]);
setResolvedValue(null);
} else {
globalPreviewCopy = preview;
globalErrorCopy = null;
setResolvingError(null);
setResolvedValue(preview);
}
const [_valid, errorMessages] = checkTypeErrorInRunTime(preview);
setPrevCurrentValue(currentValue);
if (error || !_valid || typeof preview === 'function') {
globalPreviewCopy = null;
globalErrorCopy = error || errorMessages?.[errorMessages?.length - 1];
setResolvingError(error || errorMessages?.[errorMessages?.length - 1]);
setResolvedValue(null);
} else {
globalPreviewCopy = preview;
globalErrorCopy = null;
setResolvingError(null);
setResolvedValue(preview);
}
return [globalPreviewCopy, globalErrorCopy];
};
useEffect(() => {
let [globalPreviewCopy, globalErrorCopy] = enablePreview ? updatePreview() : [null, null];
return () => {
if (enablePreview && isFocused && JSON.stringify(currentValue) !== JSON.stringify(prevCurrentValue)) {
if (enablePreview) {
setPrevCurrentValue(null);
setResolvedValue(globalPreviewCopy);
setResolvingError(globalErrorCopy);
}
};
}, [JSON.stringify({ currentValue, realState, isFocused })]);
}, [JSON.stringify({ currentValue, realState, isFocused, context })]);
// eslint-disable-next-line react-hooks/exhaustive-deps
// }, [JSON.stringify({ currentValue, realState, isFocused })]);
// }, [JSON.stringify({ currentValue, realState, isFocused, context })]);
function valueChanged(editor, onChange, ignoreBraces) {
if (editor.getValue()?.trim() !== currentValue) {
@ -376,48 +401,74 @@ export function CodeHinter({
className === 'query-hinter' || className === 'custom-component' || undefined ? '' : 'code-hinter';
const ElementToRender = AllElements[TypeMapping[type]];
const [forceCodeBox, setForceCodeBox] = useState(fxActive);
const codeShow = (type ?? 'code') === 'code' || forceCodeBox;
cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : cyLabel;
const fxBtn = () => (
<div className="col-auto pt-0 fx-common">
{!['Type', 'selectRowOnCellEdit', 'Select row on cell edit', ' ', 'Padding', 'Width'].includes(paramLabel) && ( //add some key if these extends
<FxButton
active={codeShow}
onPress={() => {
if (codeShow) {
setForceCodeBox(false);
onFxPress(false);
} else {
setForceCodeBox(true);
onFxPress(true);
}
}}
dataCy={cyLabel}
/>
)}
</div>
);
const _renderFxBtn = () => {
if (inspectorTab === 'styles') {
return isPropertyHovered || codeShow ? fxBtn() : null;
} else {
return fxBtn();
}
};
const onFocusHandler = () => {
setFocused(true);
updatePreview();
};
return (
<div ref={wrapperRef} className={cx({ 'codeShow-active': codeShow })}>
<div className={cx('d-flex align-items-center justify-content-between')}>
{paramLabel === 'Type' && <div className="field-type-vertical-line"></div>}
<div
ref={wrapperRef}
className={cx({ 'codeShow-active': codeShow, 'd-flex': paramLabel == 'Tooltip' })}
onMouseEnter={() => setPropertyHovered(true)}
onMouseLeave={() => setPropertyHovered(false)}
>
<div
className={cx('d-flex justify-content-between', { 'w-full': fieldMeta?.fullWidth })}
style={{
marginRight: paramLabel == 'Tooltip' && '40px',
alignItems: paramLabel == 'Tooltip' ? 'flex-start' : 'center',
}}
>
{paramLabel && !HIDDEN_CODE_HINTER_LABELS.includes(paramLabel) && (
<div className={`field ${options.className}`} data-cy={`${cyLabel}-widget-parameter-label`}>
<ToolTip
label={t(`widget.commonProperties.${camelCase(paramLabel)}`, paramLabel)}
meta={fieldMeta}
labelClass={`tj-text-xsm color-slate12 ${codeShow ? 'mb-2' : 'mb-0'} ${
labelClass={`tj-text-xsm color-slate12 ${codeShow ? 'label-hinter-margin' : 'mb-0'} ${
darkMode && 'color-whitish-darkmode'
}`}
/>
</div>
)}
<div className={`${(type ?? 'code') === 'code' ? 'd-none' : ''} `}>
<div className={cx(`${(type ?? 'code') === 'code' ? 'd-none' : ''}`, { 'w-full': fieldMeta?.fullWidth })}>
<div
style={{ width: width, marginBottom: codeShow ? '0.5rem' : '0px' }}
className="d-flex align-items-center"
className={cx('d-flex align-items-center', { 'w-full': fieldMeta?.fullWidth })}
>
<div className="col-auto pt-0 fx-common">
{paramLabel !== 'Type' && (
<FxButton
active={codeShow}
onPress={() => {
if (codeShow) {
setForceCodeBox(false);
onFxPress(false);
} else {
setForceCodeBox(true);
onFxPress(true);
}
}}
dataCy={cyLabel}
/>
)}
</div>
{!fieldMeta?.isFxNotRequired && _renderFxBtn()}
{!codeShow && (
<ElementToRender
value={resolveReferences(initialValue, realState)}
@ -427,6 +478,12 @@ export function CodeHinter({
setCurrentValue(value);
}
}}
onVisibilityChange={(value) => {
if (value !== currentValue) {
onVisibilityChange(value);
setCurrentValue(value);
}
}}
paramName={paramName}
paramLabel={paramLabel}
forceCodeBox={() => {
@ -435,6 +492,9 @@ export function CodeHinter({
}}
meta={fieldMeta}
cyLabel={cyLabel}
isIcon={isIcon}
staticText={staticText}
component={component}
/>
)}
</div>
@ -442,15 +502,14 @@ export function CodeHinter({
</div>
<div
className={`row${height === '150px' || height === '300px' ? ' tablr-gutter-x-0' : ''} custom-row`}
style={{ width: width, display: codeShow ? 'flex' : 'none' }}
style={{ width: paramLabel == 'Tooltip' ? '100%' : width, display: codeShow ? 'flex' : 'none' }}
>
<div className={`col code-hinter-col`}>
<div className="d-flex">
<div className={`${verticalLine && 'code-hinter-vertical-line'}`}></div>
<div className="code-hinter-wrapper position-relative" style={{ width: '100%' }}>
<div
className={`${defaultClassName} ${className || 'codehinter-default-input'} ${
resolvingError && 'border-danger'
paramName && resolvingError && 'border-danger'
}`}
key={componentName}
style={{
@ -459,6 +518,7 @@ export function CodeHinter({
maxHeight: '320px',
overflow: 'auto',
fontSize: ' .875rem',
maxWidth: paramLabel == 'Tooltip' && '190px',
}}
data-cy={`${cyLabel}-input-field`}
>
@ -489,7 +549,7 @@ export function CodeHinter({
realState={realState}
scrollbarStyle={null}
height={'100%'}
onFocus={() => setFocused(true)}
onFocus={onFocusHandler}
onBlur={(editor, e) => {
e?.stopPropagation();
const value = editor?.getValue()?.trimEnd();
@ -514,11 +574,6 @@ export function CodeHinter({
);
}
// eslint-disable-next-line no-unused-vars
function CodeHinterInputField() {
return <></>;
}
const PopupIcon = ({ callback, icon, tip, transformation = false }) => {
const size = transformation ? 20 : 12;

View file

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const CodeHinterContext = createContext({});

View file

@ -187,7 +187,13 @@ export const BoxShadow = ({ value, onChange, cyLabel }) => {
}}
data-cy={`${cyLabel}-picker-icon`}
></div>
<small className="col p-0" data-cy={`${cyLabel}-value`}>
<small
className="col p-0"
data-cy={`${cyLabel}-value`}
style={{
color: 'var(--slate12)',
}}
>
{_value}
</small>
</div>

View file

@ -0,0 +1,30 @@
import React, { useState, useEffect } from 'react';
function Checkbox({ value, onChange }) {
const [isChecked, setIsChecked] = useState(value); // Initial state of the checkbox
useEffect(() => {
setIsChecked(value);
}, [value]);
return (
<div className="d-flex align-items-center color-slate12" style={{ width: '142px', marginTop: '16px' }}>
<input
data-cy={`auto-width-checkbox`}
type="checkbox"
checked={isChecked}
onChange={() => {
setIsChecked(!isChecked); // Toggle the checkbox state
onChange(!isChecked);
}}
value={isChecked}
style={{ height: '16px', width: '16px' }}
/>
<span className="tj-text-xsm" style={{ marginLeft: '8px' }} data-cy={`auto-width-label`}>
Auto width
</span>
</div>
);
}
export default Checkbox;

View file

@ -2,11 +2,12 @@ import React, { useState } from 'react';
import { SketchPicker } from 'react-color';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import classNames from 'classnames';
export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowPopover = true }) => {
export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowPopover = true, meta }) => {
const [showPicker, setShowPicker] = useState(false);
const darkMode = localStorage.getItem('darkMode') === 'true';
const colorPickerPosition = meta?.colorPickerPosition ?? '';
const coverStyles = {
position: 'fixed',
top: '0px',
@ -38,7 +39,13 @@ export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowP
};
const eventPopover = () => {
return (
<Popover className={`${darkMode && ' dark-theme'}`}>
<Popover
className={classNames(
{ 'dark-theme': darkMode },
// This is fix when color picker don't have much space to open in bottom side
{ 'inspector-color-input-popover': colorPickerPosition === 'top' }
)}
>
<Popover.Body className={!asBoxShadowPopover && 'boxshadow-picker'}>
<>{ColorPicker()}</>
</Popover.Body>
@ -110,7 +117,7 @@ export const Color = ({ value, onChange, pickerStyle = {}, cyLabel, asBoxShadowP
}}
show={showPicker}
trigger="click"
placement={'left'}
placement={!colorPickerPosition ? 'left' : colorPickerPosition}
rootClose={true}
overlay={eventPopover()}
>

View file

@ -0,0 +1,129 @@
import React, { useRef, useState } from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { SearchBox } from '@/_components/SearchBox';
// eslint-disable-next-line import/no-unresolved
import * as Icons from '@tabler/icons-react';
import { VirtuosoGrid } from 'react-virtuoso';
import { Visibility } from './Visibility';
export const Icon = ({ value, onChange, onVisibilityChange, component }) => {
const [searchText, setSearchText] = useState('');
const [showPopOver, setPopOverVisibility] = useState(false);
const iconList = useRef(Object.keys(Icons));
const darkMode = localStorage.getItem('darkMode') === 'true';
const searchIcon = (text) => {
if (searchText === text) return;
setSearchText(text);
};
const filteredIcons =
searchText === ''
? iconList.current
: iconList.current.filter((icon) => icon?.toLowerCase().includes(searchText ? searchText.toLowerCase() : ''));
const onIconSelect = (icon) => {
onChange(icon);
};
const eventPopover = () => {
return (
<Popover
id="popover-basic"
style={{ width: '460px', maxWidth: '460px' }}
className={`icon-widget-popover ${darkMode && 'dark-theme theme-dark'}`}
>
<Popover.Header>
<SearchBox onSubmit={searchIcon} width="100%" />
</Popover.Header>
<Popover.Body>
<div className="row">
{
<VirtuosoGrid
style={{ height: 300 }}
totalCount={filteredIcons.length}
listClassName="icon-list-wrapper"
itemClassName="icon-list"
itemContent={(index) => {
if (filteredIcons[index] === undefined || filteredIcons[index] === 'createReactComponent')
return null;
// eslint-disable-next-line import/namespace
const IconElement = Icons[filteredIcons[index]];
return (
<div
className="icon-element p-2"
onClick={() => {
onIconSelect(filteredIcons[index]);
setPopOverVisibility(false);
}}
>
<IconElement
color={`${darkMode ? '#fff' : '#000'}`}
stroke={1.5}
strokeLinejoin="miter"
style={{ width: '24px', height: '24px' }}
/>
</div>
);
}}
/>
}
</div>
</Popover.Body>
</Popover>
);
};
function RenderIconPicker() {
// eslint-disable-next-line import/namespace
const IconElement = Icons?.[value] ?? Icons?.['IconHome2'];
return (
<>
<div className="color-picker-input icon-style-container">
<div className="p-0">
<div className="field">
<OverlayTrigger
trigger="click"
placement={'left'}
show={showPopOver}
onToggle={(showing) => setPopOverVisibility(showing)}
rootClose={true}
overlay={eventPopover()}
>
<div className="d-flex align-items-center" role="button">
<div className="" style={{ marginRight: '2px' }}>
<IconElement
data-cy={`icon-on-side-panel`}
color={`${darkMode ? '#fff' : '#000'}`}
stroke={1.5}
strokeLinejoin="miter"
style={{ width: '24px', height: '24px' }}
/>
</div>
<div
className="text-truncate tj-text-xsm"
style={{
width: '80px',
color: 'var(--slate12)',
}}
>
{value}
</div>
<Visibility
value={value}
onChange={onChange}
onVisibilityChange={onVisibilityChange}
component={component}
/>
</div>
</OverlayTrigger>
</div>
</div>
</div>
</>
);
}
return <>{RenderIconPicker()}</>;
};

View file

@ -0,0 +1,23 @@
import React from 'react';
export const Input = ({ value, onChange, cyLabel, staticText }) => {
return (
<div className="form-text">
<input
data-cy={`${String(cyLabel)}-input`}
style={{ width: '142px', height: '32px' }}
type="text"
className="tj-input-element tj-text-xsm"
value={value}
placeholder=""
id="labelId"
onChange={(e) => {
onChange(e.target.value);
}}
/>
<label for="labelId" className="static-value tj-text-xsm">
{staticText?.length > 0 ? staticText : staticText?.length == 0 ? '' : 'px'}
</label>
</div>
);
};

View file

@ -0,0 +1,24 @@
import React from 'react';
export const NumberInput = ({ value, onChange, cyLabel, staticText }) => {
return (
<div className="form-text tj-number-input-element">
<input
style={{ width: '142px', height: '32px' }}
data-cy={`${String(cyLabel)}-input`}
type="number"
className="tj-input-element tj-text-xsm"
value={value}
placeholder=""
id="labelId"
onChange={(e) => {
onChange(e.target.value);
}}
autoComplete="off"
/>
<label for="labelId" className="static-value tj-text-xsm">
{staticText?.length > 0 ? staticText : staticText?.length == 0 ? '' : 'px'}
</label>
</div>
);
};

View file

@ -0,0 +1,56 @@
import React, { useState, useEffect } from 'react';
import CustomInput from '@/_ui/CustomInput';
// eslint-disable-next-line import/no-unresolved
import * as Slider from '@radix-ui/react-slider';
import './Slider.scss';
function Slider1({ value, onChange, component }) {
const [sliderValue, setSliderValue] = useState(value ? value : 33); // Initial value of the slider
useEffect(() => {
setSliderValue(value);
}, [value]);
const handleSliderChange = (value) => {
setSliderValue(value);
onChange(value);
};
// Throttle function to handle input changes
const onInputChange = (e) => {
let inputValue = parseInt(e.target.value, 10) || 0;
inputValue = Math.min(inputValue, 100);
setSliderValue(inputValue);
onChange(inputValue);
};
return (
<div className="d-flex flex-column " style={{ width: '142px', position: 'relative' }}>
<CustomInput
disabled={component.component.definition.styles.auto.value}
value={sliderValue}
staticText="% of the field"
onInputChange={onInputChange}
/>
<div style={{ position: 'absolute', top: '34px' }}>
<Slider.Root
className="SliderRoot"
defaultValue={[33]}
min={0}
max={100}
step={1}
value={[sliderValue]}
onValueChange={handleSliderChange}
disabled={component.component.definition.styles.auto.value}
>
<Slider.Track className="SliderTrack">
<Slider.Range className="SliderRange" />
</Slider.Track>
<Slider.Thumb className="SliderThumb" aria-label="Volume" />
</Slider.Root>
</div>
</div>
);
}
export default Slider1;

View file

@ -0,0 +1,45 @@
.SliderRoot {
position: relative;
display: flex;
align-items: center;
user-select: none;
touch-action: none;
width: 142px;
height: 20px;
}
.SliderTrack {
background-color: #E8ECF0;
position: relative;
flex-grow: 1;
border-radius: 9999px;
height: 6px;
}
.SliderRange {
position: absolute;
background-color: #4368E3;
border-radius: 9999px;
height: 100%;
}
.SliderThumb {
display: block;
width: 12px;
height: 12px;
background-color: white;
border-radius: 10px;
border: 1px solid #C1C8CD;
}
.SliderThumb:hover {
background-color: #fff;
}
.SliderThumb:focus {
outline: none;
}
.SliderRange[data-disabled] {
background-color: #C1C8CD;
}

View file

@ -0,0 +1,27 @@
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
import React from 'react';
import cx from 'classnames';
const Switch = ({ value, onChange, cyLabel, meta, paramName, isIcon }) => {
const options = meta?.options;
const defaultValue = value;
return (
<div className={cx({ 'w-full': meta?.fullWidth })}>
<ToggleGroup onValueChange={onChange} defaultValue={defaultValue} className={cx({ 'w-full': meta?.fullWidth })}>
{options.map((option) => (
<ToggleGroupItem
key={option.value}
value={option.value}
isIcon={isIcon}
style={{ width: meta?.fullWidth ? '100%' : '67px' }}
>
{isIcon ? option?.iconName ?? '' : option?.displayName}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
);
};
export default Switch;

View file

@ -0,0 +1,22 @@
import React from 'react';
import SolidIcon from '@/_ui/Icon/SolidIcons';
export const Visibility = ({ value, onVisibilityChange, component }) => {
return (
<div
data-cy={`icon-visibility-button`}
className="cursor-pointer visibility-eye"
style={{ top: component.component.definition.styles.iconVisibility?.value && '42%' }}
onClick={(e) => {
e.stopPropagation();
onVisibilityChange(!component.component.definition.styles?.iconVisibility?.value);
}}
>
<SolidIcon
name={component.component.definition.styles?.iconVisibility?.value ? 'eye1' : 'eyedisable'}
width="20"
fill={'var(--slate8)'}
/>
</div>
);
};

View file

@ -10,4 +10,11 @@ export const TypeMapping = {
number: 'Number',
boxShadow: 'BoxShadow',
clientServerSwitch: 'ClientServerSwitch',
checkbox: 'Checkbox',
slider: 'Slider',
switch: 'Switch',
input: 'Input',
icon: 'Icon',
visibility: 'Visibility',
numberInput: 'NumberInput',
};

View file

@ -71,6 +71,7 @@ export function getSuggestionKeys(refState, refSource) {
const actions = [
'runQuery',
'setVariable',
'getVariable',
'unSetVariable',
'showAlert',
'logout',
@ -81,6 +82,7 @@ export function getSuggestionKeys(refState, refSource) {
'goToApp',
'generateFile',
'setPageVariable',
'getPageVariable',
'unsetPageVariable',
'switchPage',
];

View file

@ -164,7 +164,6 @@ export const DropDown = function DropDown({
boxShadow: state.isFocused ? boxShadow : boxShadow,
borderRadius: Number.parseFloat(borderRadius),
}),
valueContainer: (provided, _state) => ({
...provided,
height: height,

View file

@ -29,6 +29,7 @@ export const Form = function Form(props) {
onEvent,
dataCy,
paramUpdated,
adjustHeightBasedOnAlignment,
} = props;
const { events: allAppEvents } = useAppInfo();
@ -293,6 +294,8 @@ export const Form = function Form(props) {
allComponents={containerProps.allComponents}
sideBarDebugger={containerProps.sideBarDebugger}
childComponents={childComponents}
adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment}
height={item.defaultSize.height}
/>
</div>
);

View file

@ -55,6 +55,7 @@ export function generateUIComponents(JSONSchema, advanced) {
uiComponentsDraft[index * 2 + 1]['definition']['properties']['value']['value'] = value?.value;
if (value?.placeholder)
uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder;
uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = ''; //removing default label in all components with default label to match previous versions
break;
case 'DropDown':
if (value?.styles?.disabled)
@ -155,7 +156,10 @@ export function generateUIComponents(JSONSchema, advanced) {
uiComponentsDraft[index * 2 + 1]['definition']['properties']['minValue']['value'] = value?.minValue;
if (value?.placeholder)
uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder;
uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = '';
break;
case 'PasswordInput':
if (value?.styles?.backgroundColor)
uiComponentsDraft[index * 2 + 1]['definition']['styles']['backgroundColor']['value'] =
@ -184,6 +188,7 @@ export function generateUIComponents(JSONSchema, advanced) {
uiComponentsDraft[index * 2 + 1]['definition']['validation']['regex']['value'] = value?.validation?.regex;
if (value?.placeholder)
uiComponentsDraft[index * 2 + 1]['definition']['properties']['placeholder']['value'] = value?.placeholder;
uiComponentsDraft[index * 2 + 1]['definition']['properties']['label']['value'] = '';
break;
case 'Datepicker':
if (value?.styles?.borderRadius)

View file

@ -65,6 +65,8 @@ export const Modal = function Modal({
const canShowModal = exposedVariables.show ?? false;
setShowModal(exposedVariables.show ?? false);
fireEvent(canShowModal ? 'onOpen' : 'onClose');
const inputRef = document?.getElementsByClassName('tj-text-input-widget')?.[0];
inputRef?.blur();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [exposedVariables.show]);
@ -158,7 +160,7 @@ export const Modal = function Modal({
useEffect(() => {
if (closeOnClickingOutside) {
const handleClickOutside = (event) => {
const modalRef = parentRef.current.parentElement.parentElement.parentElement;
const modalRef = parentRef?.current?.parentElement?.parentElement?.parentElement;
if (modalRef && modalRef === event.target) {
hideModal();
@ -174,7 +176,12 @@ export const Modal = function Modal({
}, [closeOnClickingOutside, parentRef]);
return (
<div className="container" data-disabled={disabledState} data-cy={dataCy}>
<div
className="container d-flex align-items-center"
data-disabled={disabledState}
data-cy={dataCy}
style={{ height }}
>
{useDefaultButton && (
<button
disabled={disabledState}

View file

@ -1,20 +1,74 @@
import React, { useRef, useEffect } from 'react';
import React, { useRef, useEffect, useState } from 'react';
import './numberinput.scss';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { ToolTip } from '@/_components/ToolTip';
import * as Icons from '@tabler/icons-react';
import Loader from '@/ToolJetUI/Loader/Loader';
import { resolveReferences } from '@/_helpers/utils';
import { useCurrentState } from '@/_stores/currentStateStore';
const tinycolor = require('tinycolor2');
import Label from '@/_ui/Label';
export const NumberInput = function NumberInput({
height,
properties,
validate,
styles,
setExposedVariable,
darkMode,
fireEvent,
component,
darkMode,
dataCy,
isResizing,
adjustHeightBasedOnAlignment,
currentLayout,
}) {
const { visibility, borderRadius, borderColor, backgroundColor, boxShadow } = styles;
const { loadingState, tooltip, disabledState, label, placeholder } = properties;
const {
padding,
borderRadius,
borderColor,
backgroundColor,
boxShadow,
width,
alignment,
direction,
color,
auto,
errTextColor,
iconColor,
accentColor,
} = styles;
const textColor = darkMode && ['#232e3c', '#000000ff'].includes(styles.textColor) ? '#fff' : styles.textColor;
const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState) ?? false;
const minValue = resolveReferences(component?.definition?.validation?.minValue?.value, currentState) ?? null;
const maxValue = resolveReferences(component?.definition?.validation?.maxValue?.value, currentState) ?? null;
const [visibility, setVisibility] = useState(properties.visibility);
const [loading, setLoading] = useState(loadingState);
const [showValidationError, setShowValidationError] = useState(false);
const [value, setValue] = React.useState(Number(parseFloat(properties.value).toFixed(properties.decimalPlaces)));
const { isValid, validationError } = validate(value);
const [isFocused, setIsFocused] = useState(false);
const inputRef = useRef(null);
const currentState = useCurrentState();
const [disable, setDisable] = useState(disabledState || loadingState);
const labelRef = useRef();
const _width = (width / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
useEffect(() => {
setExposedVariable('label', label);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [label]);
useEffect(() => {
if (alignment == 'top' && ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)))
adjustHeightBasedOnAlignment(true);
else adjustHeightBasedOnAlignment(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alignment, label?.length, currentLayout, width, auto]);
useEffect(() => {
setValue(Number(parseFloat(value).toFixed(properties.decimalPlaces)));
@ -26,27 +80,12 @@ export const NumberInput = function NumberInput({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
const handleChange = (e) => {
if (
!isNaN(parseFloat(properties.minValue)) &&
!isNaN(parseFloat(properties.maxValue)) &&
parseFloat(properties.minValue) > parseFloat(properties.maxValue)
) {
setValue(Number(parseFloat(properties.maxValue)));
} else if (
!isNaN(parseFloat(properties.maxValue)) &&
parseFloat(e.target.value) > parseFloat(properties.maxValue)
) {
setValue(Number(parseFloat(properties.maxValue)));
} else {
setValue(Number(parseFloat(e.target.value)));
}
fireEvent('onChange');
};
const handleBlur = (e) => {
if (!isNaN(parseFloat(properties.minValue)) && parseFloat(e.target.value) < parseFloat(properties.minValue)) {
setValue(Number(parseFloat(properties.minValue)));
} else setValue(Number(parseFloat(e.target.value ? e.target.value : 0).toFixed(properties.decimalPlaces)));
setValue(Number(parseFloat(e.target.value).toFixed(properties.decimalPlaces)));
setShowValidationError(true);
e.stopPropagation();
fireEvent('onBlur');
setIsFocused(false);
};
useEffect(() => {
@ -56,41 +95,344 @@ export const NumberInput = function NumberInput({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
useEffect(() => {
setExposedVariable('isMandatory', isMandatory);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMandatory]);
useEffect(() => {
setExposedVariable('isLoading', loading);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading]);
useEffect(() => {
setExposedVariable('setLoading', async function (loading) {
setLoading(loading);
setExposedVariable('isLoading', loading);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.loadingState]);
useEffect(() => {
setExposedVariable('isVisible', visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibility]);
useEffect(() => {
setExposedVariable('setVisibility', async function (state) {
setVisibility(state);
setExposedVariable('isVisible', state);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
setExposedVariable('setDisable', async function (disable) {
setDisable(disable);
setExposedVariable('isDisabled', disable);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
setExposedVariable('isDisabled', disable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
useEffect(() => {
if (labelRef?.current) {
const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width;
setLabelWidth(absolutewidth);
} else setLabelWidth(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isResizing,
width,
auto,
defaultAlignment,
component?.definition?.styles?.iconVisibility?.value,
labelRef?.current?.getBoundingClientRect()?.width,
isMandatory,
padding,
direction,
alignment,
]);
useEffect(() => {
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isValid]);
const computedStyles = {
height,
display: visibility ? '' : 'none',
height: height == 36 ? (padding == 'default' ? '36px' : '38px') : padding == 'default' ? height : height + 2,
borderRadius: `${borderRadius}px`,
borderColor,
color: textColor,
backgroundColor: darkMode && ['#ffffff', '#ffffffff'].includes(backgroundColor) ? '#000000' : backgroundColor,
boxShadow,
color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
borderColor: isFocused
? accentColor
: ['#D7DBDF'].includes(borderColor)
? darkMode
? '#6D757D7A'
: '#6A727C47'
: borderColor,
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
backgroundColor:
darkMode && ['#ffffff', '#ffffffff', '#fff'].includes(backgroundColor) ? '#313538' : backgroundColor,
boxShadow: boxShadow,
padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px',
// flex: padding !== 'none' && 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
};
return (
<>
{!properties.loadingState && (
<input
ref={inputRef}
disabled={styles.disabledState}
onChange={handleChange}
onBlur={handleBlur}
type="number"
className="form-control"
placeholder={properties.placeholder}
style={computedStyles}
value={value}
data-cy={dataCy}
min={properties.minValue}
max={properties.maxValue}
/>
)}
{properties.loadingState === true && (
<div style={{ width: '100%' }}>
<center>
<div className="spinner-border" role="status"></div>
</center>
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
const [labelWidth, setLabelWidth] = useState(0);
const iconName = styles.icon; // Replace with the name of the icon you want
// eslint-disable-next-line import/namespace
const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName];
// eslint-disable-next-line import/namespace
const handleChange = (e) => {
setValue(Number(parseFloat(e.target.value)));
if (e.target.value == '') {
setValue(null);
setExposedVariable('value', null).then(fireEvent('onChange'));
}
if (!isNaN(Number(parseFloat(e.target.value)))) {
setExposedVariable('value', Number(parseFloat(e.target.value))).then(fireEvent('onChange'));
}
};
useEffect(() => {
disable !== disabledState && setDisable(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
visibility !== properties.visibility && setVisibility(properties.visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
loading !== loadingState && setLoading(loadingState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
const handleIncrement = (e) => {
e.preventDefault(); // Prevent the default button behavior (form submission, page reload)
const newValue = (value || 0) + 1;
setValue(newValue);
if (!isNaN(newValue)) {
setExposedVariable('value', newValue).then(fireEvent('onChange'));
}
};
const handleDecrement = (e) => {
e.preventDefault();
const newValue = (value || 0) - 1;
setValue(newValue);
if (!isNaN(newValue)) {
setExposedVariable('value', newValue).then(fireEvent('onChange'));
}
};
useEffect(() => {
setExposedVariable('setFocus', async function () {
inputRef.current.focus();
});
setExposedVariable('setBlur', async function () {
inputRef.current.blur();
});
setExposedVariable('setText', async function (text) {
if (text) {
const newValue = Number(parseFloat(text));
setValue(newValue);
setExposedVariable('value', text).then(fireEvent('onChange'));
}
});
setExposedVariable('clear', async function () {
setValue('');
setExposedVariable('value', '').then(fireEvent('onChange'));
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loaderStyle = {
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11 + 20}px` // 23 px usual + 20 for number input arrows
: '31px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
zIndex: 3,
};
const renderInput = () => {
return (
<>
<div
data-cy={`label-${String(component.name).toLowerCase()}`}
data-disabled={disable || loading}
className={`text-input tj-number-input-widget d-flex ${
defaultAlignment === 'top' &&
((width != 0 && label && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
? 'flex-column'
: 'align-items-center '
} ${direction === 'right' && defaultAlignment === 'side' ? 'flex-row-reverse' : ''}
${direction === 'right' && defaultAlignment === 'top' ? 'text-right' : ''}
${visibility || 'invisible'}`}
style={{
position: 'relative',
width: '100%',
display: !visibility ? 'none' : 'flex',
whiteSpace: 'nowrap',
}}
>
<Label
label={label}
width={width}
labelRef={labelRef}
darkMode={darkMode}
color={color}
defaultAlignment={defaultAlignment}
direction={direction}
auto={auto}
isMandatory={isMandatory}
_width={_width}
labelWidth={labelWidth}
/>
{component?.definition?.styles?.iconVisibility?.value && !isResizing && (
<IconElement
data-cy={'text-input-icon'}
style={{
width: '16px',
height: '16px',
left:
direction === 'right'
? '11px'
: defaultAlignment === 'top'
? '11px'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? `${labelWidth + 11}px`
: '11px', //23 :: is 10 px inside the input + 1 px border + 12px margin right
position: 'absolute',
top: `${
defaultAlignment === 'side'
? '50%'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? 'calc(50% + 10px)'
: '50%'
}`,
transform: ' translateY(-50%)',
color: iconColor,
zIndex: 3,
}}
stroke={1.5}
/>
)}
<input
ref={inputRef}
disabled={disable || loading}
onChange={handleChange}
onBlur={handleBlur}
type="number"
className={`${!isValid && showValidationError ? 'is-invalid' : ''} input-number tj-text-input-widget`}
placeholder={placeholder}
style={computedStyles}
value={value}
data-cy={dataCy}
min={minValue}
max={maxValue}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onEnterPressed');
}
}}
onFocus={(e) => {
setIsFocused(true);
e.stopPropagation();
setTimeout(() => {
fireEvent('onFocus');
}, 0);
}}
/>
{!isResizing && (
<>
<div onClick={(e) => handleIncrement(e)}>
<SolidIcon
width={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
height={`${height / 2}px`}
style={{
top: defaultAlignment === 'top' && label?.length > 0 && width > 0 ? '21px' : '1px',
right:
labelWidth == 0
? '1px'
: alignment == 'side' && direction === 'right'
? `${labelWidth + 1}px`
: '1px',
borderLeft: darkMode ? '1px solid #313538' : '1px solid #D7D7D7',
borderBottom: darkMode ? '.5px solid #313538' : '0.5px solid #D7D7D7',
borderTopRightRadius: borderRadius - 1,
backgroundColor: !darkMode ? 'white' : 'black',
zIndex: 3,
}}
className="numberinput-up-arrow arrow"
name="cheveronup"
></SolidIcon>
</div>
<div onClick={(e) => handleDecrement(e)}>
<SolidIcon
style={{
right:
labelWidth == 0
? '1px'
: alignment == 'side' && direction === 'right'
? `${labelWidth + 1}px`
: '1px',
bottom: '1px',
borderLeft: darkMode ? '1px solid #313538' : '1px solid #D7D7D7',
borderTop: darkMode ? '0.5px solid #313538' : '0.5px solid #D7D7D7',
borderBottomRightRadius: borderRadius - 1,
backgroundColor: !darkMode ? 'white' : 'black',
zIndex: 3,
}}
width={padding == 'default' ? `${height / 2 - 1}px` : `${height / 2 + 1}px`}
height={`${height / 2}px`}
className="numberinput-down-arrow arrow"
name="cheverondown"
></SolidIcon>
</div>
</>
)}
{loading && <Loader style={{ ...loaderStyle }} width="16" />}
</div>
)}
</>
);
{showValidationError && visibility && (
<div
className="tj-text-sm"
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
style={{
color: errTextColor,
textAlign: direction == 'left' && 'end',
}}
>
{showValidationError && validationError}
</div>
)}
</>
);
};
return <div>{renderInput()}</div>;
};

View file

@ -1,61 +1,380 @@
import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { resolveReferences } from '@/_helpers/utils';
import { useCurrentState } from '@/_stores/currentStateStore';
import { ToolTip } from '@/_components/ToolTip';
import * as Icons from '@tabler/icons-react';
import Loader from '@/ToolJetUI/Loader/Loader';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import Label from '@/_ui/Label';
export const PasswordInput = ({
export const PasswordInput = function PasswordInput({
height,
validate,
properties,
styles,
setExposedVariable,
darkMode,
component,
fireEvent,
component,
darkMode,
dataCy,
}) => {
const { visibility, disabledState, borderRadius, backgroundColor, boxShadow } = styles;
isResizing,
adjustHeightBasedOnAlignment,
currentLayout,
}) {
const textInputRef = useRef();
const labelRef = useRef();
const placeholder = properties.placeholder;
const { loadingState, tooltip, disabledState, label, placeholder } = properties;
const {
padding,
borderRadius,
borderColor,
backgroundColor,
textColor,
boxShadow,
width,
alignment,
direction,
color,
auto,
errTextColor,
iconColor,
accentColor,
} = styles;
const [passwordValue, setPasswordValue] = useState('');
const [disable, setDisable] = useState(disabledState || loadingState);
const [passwordValue, setPasswordValue] = useState(properties.value);
const [visibility, setVisibility] = useState(properties.visibility);
const { isValid, validationError } = validate(passwordValue);
const [showValidationError, setShowValidationError] = useState(false);
const currentState = useCurrentState();
const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState);
const [labelWidth, setLabelWidth] = useState(0);
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
const [iconVisibility, setIconVisibility] = useState(false);
const [loading, setLoading] = useState(loadingState);
const [isFocused, setIsFocused] = useState(false);
const tinycolor = require('tinycolor2');
const _width = (width / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
const computedStyles = {
height,
display: visibility ? '' : 'none',
height: height == 36 ? (padding == 'default' ? '36px' : '38px') : padding == 'default' ? height : height + 2,
borderRadius: `${borderRadius}px`,
color: darkMode && '#fff',
borderColor: darkMode && '#DADCDE',
backgroundColor: darkMode && ['#ffffff'].includes(backgroundColor) ? '#232e3c' : backgroundColor,
color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
borderColor: isFocused
? accentColor
: ['#D7DBDF'].includes(borderColor)
? darkMode
? '#6D757D7A'
: '#6A727C47'
: borderColor,
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
backgroundColor: darkMode && ['#fff', '#ffffff'].includes(backgroundColor) ? '#313538' : backgroundColor,
boxShadow: boxShadow,
padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px',
// flex: padding !== 'none' && 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
};
React.useEffect(() => {
const loaderStyle = {
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11}px`
: '11px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
zIndex: 3,
};
useEffect(() => {
setExposedVariable('label', label);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [label]);
useEffect(() => {
if (labelRef?.current) {
const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width;
setLabelWidth(absolutewidth);
} else setLabelWidth(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isResizing,
width,
auto,
defaultAlignment,
component?.definition?.styles?.iconVisibility?.value,
label?.length,
isMandatory,
padding,
direction,
alignment,
isMandatory,
labelRef?.current?.getBoundingClientRect()?.width,
]);
useEffect(() => {
disable !== disabledState && setDisable(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
visibility !== properties.visibility && setVisibility(properties.visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
loading !== loadingState && setLoading(loadingState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
useEffect(() => {
setExposedVariable('isValid', isValid);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [passwordValue, isValid]);
}, [isValid]);
return (
<div>
<input
disabled={disabledState}
onChange={(e) => {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onChange');
setShowValidationError(true);
useEffect(() => {
setPasswordValue(properties.value);
setExposedVariable('value', properties?.value ?? '');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.value]);
useEffect(() => {
setExposedVariable('setFocus', async function () {
textInputRef.current.focus();
});
setExposedVariable('setBlur', async function () {
textInputRef.current.blur();
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setExposedVariable('setText', async function (text) {
setPasswordValue(text);
setExposedVariable('value', text).then(fireEvent('onChange'));
});
setExposedVariable('clear', async function () {
setPasswordValue('');
setExposedVariable('value', '').then(fireEvent('onChange'));
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setPasswordValue]);
const iconName = styles.icon; // Replace with the name of the icon you want
// eslint-disable-next-line import/namespace
const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName];
// eslint-disable-next-line import/namespace
useEffect(() => {
if (alignment == 'top' && ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)))
adjustHeightBasedOnAlignment(true);
else adjustHeightBasedOnAlignment(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alignment, label?.length, currentLayout, width, auto]);
useEffect(() => {
setExposedVariable('isMandatory', isMandatory);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMandatory]);
useEffect(() => {
setExposedVariable('isLoading', loading);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading]);
useEffect(() => {
setExposedVariable('setLoading', async function (loading) {
setLoading(loading);
setExposedVariable('isLoading', loading);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.loadingState]);
useEffect(() => {
setExposedVariable('isVisible', visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibility]);
useEffect(() => {
setExposedVariable('setVisibility', async function (state) {
setVisibility(state);
setExposedVariable('isVisible', state);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
setExposedVariable('setDisable', async function (disable) {
setDisable(disable);
setExposedVariable('isDisabled', disable);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
setExposedVariable('isDisabled', disable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
const renderInput = () => (
<>
<div
data-disabled={disable || loading}
className={`text-input d-flex ${
defaultAlignment === 'top' &&
((width != 0 && label && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
? 'flex-column'
: 'align-items-center '
} ${direction === 'right' && defaultAlignment === 'side' ? 'flex-row-reverse' : ''}
${direction === 'right' && defaultAlignment === 'top' ? 'text-right' : ''}
${visibility || 'invisible'}`}
style={{
position: 'relative',
whiteSpace: 'nowrap',
width: '100%',
}}
type={'password'}
className={`form-control ${!isValid && showValidationError ? 'is-invalid' : ''} validation-without-icon ${
darkMode && 'dark-theme-placeholder'
}`}
placeholder={placeholder}
value={passwordValue}
style={computedStyles}
data-cy={dataCy}
/>
<div className="invalid-feedback" data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}>
{showValidationError && validationError}
>
<Label
label={label}
width={width}
labelRef={labelRef}
darkMode={darkMode}
color={color}
defaultAlignment={defaultAlignment}
direction={direction}
auto={auto}
isMandatory={isMandatory}
_width={_width}
labelWidth={labelWidth}
/>
{component?.definition?.styles?.iconVisibility?.value && !isResizing && (
<IconElement
data-cy={'text-input-icon'}
style={{
width: '16px',
height: '16px',
left:
direction === 'right'
? '11px'
: defaultAlignment === 'top'
? '11px'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? `${labelWidth + 11}px`
: '11px', //11 :: is 10 px inside the input + 1 px border + 12px margin right
position: 'absolute',
top: `${
defaultAlignment === 'side'
? '50%'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? 'calc(50% + 10px)'
: '50%'
}`,
transform: ' translateY(-50%)',
color: iconColor,
zIndex: 3,
}}
stroke={1.5}
/>
)}
{!loading && !isResizing && (
<div
onClick={() => {
setIconVisibility(!iconVisibility);
}}
style={{
width: '16px',
height: '16px',
position: 'absolute',
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11}px`
: '11px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
display: 'flex',
zIndex: 3,
}}
stroke={1.5}
>
<SolidIcon width={16} className="password-component-eye" name={!iconVisibility ? 'eye1' : 'eyedisable'} />
</div>
)}
<input
data-cy={`label-${String(component.name).toLowerCase()}`}
className={`tj-text-input-widget ${
!isValid && showValidationError ? 'is-invalid' : ''
} validation-without-icon ${darkMode && 'dark-theme-placeholder'}`}
ref={textInputRef}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setPasswordValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onChange');
}}
onBlur={(e) => {
setIsFocused(false);
setShowValidationError(true);
e.stopPropagation();
fireEvent('onBlur');
}}
onFocus={(e) => {
setIsFocused(true);
e.stopPropagation();
setTimeout(() => {
fireEvent('onFocus');
}, 0);
}}
type={!iconVisibility ? 'password' : 'text'}
placeholder={placeholder}
style={computedStyles}
value={passwordValue}
disabled={disable || loading}
/>
{loading && <Loader style={{ ...loaderStyle }} width="16" />}
</div>
</div>
{showValidationError && visibility && (
<div
className="tj-text-sm"
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
style={{
color: errTextColor,
textAlign: direction === 'left' && 'end',
}}
>
{showValidationError && validationError}
</div>
)}
</>
);
return <div>{renderInput()}</div>;
};

View file

@ -1,16 +1,31 @@
import React from 'react';
import React, { useEffect } from 'react';
import Select from '@/_ui/Select';
import defaultStyles from '@/_ui/Select/styles';
import { useTranslation } from 'react-i18next';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import _ from 'lodash';
import _, { isArray } from 'lodash';
// eslint-disable-next-line import/no-unresolved
import { diff as deepDiff } from 'deep-object-diff';
const FILTER_OPTIONS = [
{ name: 'contains', value: 'contains' },
{ name: 'does not contains', value: 'doesNotContains' },
{ name: 'matches', value: 'matches' },
{ name: 'does not match', value: 'nl' },
{ name: 'equals', value: 'equals' },
{ name: 'does not equal', value: 'ne' },
{ name: 'is empty', value: 'isEmpty' },
{ name: 'is not empty', value: 'isNotEmpty' },
{ name: 'greater than', value: 'gt' },
{ name: 'less than', value: 'lt' },
{ name: 'greater than or equals', value: 'gte' },
{ name: 'less than or equals', value: 'lte' },
];
export function Filter(props) {
const { t } = useTranslation();
const { mergeToFilterDetails, filterDetails, setAllFilters, fireEvent, darkMode } = props;
const { mergeToFilterDetails, filterDetails, setAllFilters, fireEvent, darkMode, setExposedVariable } = props;
const { filters } = filterDetails;
const [activeFilters, set] = React.useState(filters);
@ -37,13 +52,16 @@ export function Filter(props) {
if (value === 'isEmpty' || value === 'isNotEmpty') {
newFilters[index].value.value = '';
}
mergeToFilterDetails({
filters: newFilters,
});
setAllFilters(newFilters.filter((filter) => filter.id !== ''));
}
const debouncedFilterChanged = _.debounce((newFilters) => {
setAllFilters(newFilters.filter((filter) => filter.id !== ''));
}, 500);
function filterValueChanged(index, value) {
const newFilters = filters;
newFilters[index].value = {
@ -53,7 +71,7 @@ export function Filter(props) {
mergeToFilterDetails({
filters: newFilters,
});
setAllFilters(newFilters.filter((filter) => filter.id !== ''));
debouncedFilterChanged(newFilters);
}
function addFilter() {
@ -82,7 +100,7 @@ export function Filter(props) {
setTimeout(() => fireEvent('onFilterChanged'), 0);
}
React.useEffect(() => {
useEffect(() => {
if (filters.length > 0) {
const tableFilters = JSON.parse(JSON.stringify(filters));
const shouldFire = findFilterDiff(activeFilters, tableFilters);
@ -93,6 +111,45 @@ export function Filter(props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(filters)]);
useEffect(() => {
// Add CSA to set filters
setExposedVariable('setFilters', async function (_filters) {
if (!isArray(_filters)) return;
const filterArr = [];
_filters.forEach((_filter) => {
const { column = '', value = '', condition = '' } = _filter;
const columnId = props.columns.find((col) => col.name === column)?.value;
const isCorrectCondition = FILTER_OPTIONS.some((option) => option.value === condition);
if (columnId && isCorrectCondition) {
const filterObj = {
id: columnId,
value: {
column,
condition,
value,
},
};
filterArr.push(filterObj);
}
});
if (filterArr.length) {
setAllFilters(filterArr.filter((filter) => filter.id !== ''));
mergeToFilterDetails({
filters: filterArr,
});
}
});
// Add CSA to clear filters
setExposedVariable('clearFilters', async function (_filters) {
setAllFilters([]);
mergeToFilterDetails({
filters: [],
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(props.columns)]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceFn = React.useCallback(
_.debounce(() => {
@ -110,6 +167,10 @@ export function Filter(props) {
};
};
if (!filterDetails.filtersVisible) {
return null;
}
return (
<div className={`table-filters card ${darkMode && 'dark-theme'}`}>
<div className="card-header row">
@ -150,20 +211,7 @@ export function Filter(props) {
</div>
<div data-cy={`select-operation-dropdown-${index ?? ''}`} className="col" style={{ maxWidth: '180px' }}>
<Select
options={[
{ name: 'contains', value: 'contains' },
{ name: 'does not contains', value: 'doesNotContains' },
{ name: 'matches', value: 'matches' },
{ name: 'does not match', value: 'nl' },
{ name: 'equals', value: 'equals' },
{ name: 'does not equal', value: 'ne' },
{ name: 'is empty', value: 'isEmpty' },
{ name: 'is not empty', value: 'isNotEmpty' },
{ name: 'greater than', value: 'gt' },
{ name: 'less than', value: 'lt' },
{ name: 'greater than or equals', value: 'gte' },
{ name: 'less than or equals', value: 'lte' },
]}
options={FILTER_OPTIONS}
value={filter.value.condition}
search={true}
onChange={(value) => {
@ -184,7 +232,7 @@ export function Filter(props) {
value={filter.value.value}
placeholder="value"
className="form-control"
onChange={(e) => _.debounce(filterValueChanged(index, e.target.value), 500)}
onChange={(e) => filterValueChanged(index, e.target.value)}
/>
)}
</div>
@ -252,8 +300,8 @@ const findFilterDiff = (oldFilters, newFilters) => {
};
const diff = Object.entries(filterDiff).reduce((acc, [key, value]) => {
const type = getType(value.value);
return (acc = { ...acc, keyIndex: key, type: type, diff: value.value[type] });
const type = getType(value?.value);
return (acc = { ...acc, keyIndex: key, type: type, diff: value?.value?.[type] });
}, {});
return shouldFireEvent(diff, newFilters);

View file

@ -1,6 +1,6 @@
import React from 'react';
const IndeterminateCheckbox = React.forwardRef(({ indeterminate, ...rest }, ref) => {
const IndeterminateCheckbox = React.forwardRef(({ indeterminate, fireEvent, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
@ -14,7 +14,12 @@ const IndeterminateCheckbox = React.forwardRef(({ indeterminate, ...rest }, ref)
data-cy={`checkbox-input`}
type="checkbox"
ref={resolvedRef}
onClick={(event) => event.stopPropagation()}
onClick={(event) => {
if (fireEvent) {
fireEvent('onRowClicked');
}
event.stopPropagation();
}}
{...rest}
style={{
width: 16,

View file

@ -132,6 +132,7 @@ export function Table({
showAddNewRowButton,
allowSelection,
enablePagination,
selectRowOnCellEdit,
} = loadPropertiesAndStyles(properties, styles, darkMode, component);
const updatedDataReference = useRef([]);
@ -404,8 +405,10 @@ export function Table({
const tableRef = useRef();
const columnProperties = useDynamicColumn ? generatedColumn : component.definition.properties.columns.value;
let columnData = generateColumnsData({
columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value,
columnProperties,
columnSizes,
currentState,
handleCellValueChange: handleExistingRowCellValueChange,
@ -433,6 +436,32 @@ export function Table({
[columnData, currentState]
);
const transformations = columnProperties
.filter((column) => column.transformation && column.transformation != '{{cellValue}}')
.map((column) => ({
key: column.key ? column.key : column.name,
transformation: column.transformation,
}));
tableData = useMemo(() => {
return tableData.map((row) => ({
...row,
...Object.fromEntries(
transformations.map((t) => [
t.key,
resolveReferences(t.transformation, currentState, row[t.key], { cellValue: row[t.key], rowData: row }),
])
),
}));
}, [JSON.stringify([transformations, currentState])]);
useEffect(() => {
setExposedVariables({
currentData: tableData,
updatedData: tableData,
});
}, [JSON.stringify(tableData)]);
const columnDataForAddNewRows = generateColumnsData({
columnProperties: useDynamicColumn ? generatedColumn : component.definition.properties.columns.value,
columnSizes,
@ -512,7 +541,7 @@ export function Table({
}
}
return _.isEmpty(updatedDataReference.current) ? tableData : updatedDataReference.current;
}, [tableData.length, component.definition.properties.data.value, JSON.stringify(properties.data)]);
}, [tableData.length, component.definition.properties.data.value, JSON.stringify([properties.data, tableData])]);
useEffect(() => {
if (
@ -626,7 +655,7 @@ export function Table({
Cell: ({ row }) => {
return (
<div className="d-flex flex-column align-items-center">
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} fireEvent={fireEvent} />
</div>
);
},
@ -716,6 +745,7 @@ export function Table({
}
});
}, [JSON.stringify(tableData), JSON.stringify(tableDetails.changeSet)]);
useEffect(() => {
setExposedVariable('discardNewlyAddedRows', async function () {
if (
@ -883,6 +913,26 @@ export function Table({
//hack : in the initial render, data is undefined since, upon feeding data to the table from some query, query inside current state is {}. Hence we added data in the dependency array, now question is should we add data or rows?
}, [JSON.stringify(defaultSelectedRow), JSON.stringify(data)]);
useEffect(() => {
// csa for select all rows in table
setExposedVariable('selectAllRows', async function () {
if (showBulkSelector) {
await toggleAllRowsSelected(true);
}
});
// csa for deselect all rows in table
setExposedVariable('deselectAllRows', async function () {
if (showBulkSelector) {
await toggleAllRowsSelected(false);
}
});
}, [JSON.stringify(tableDetails.selectedRowsDetails)]);
const pageData = page.map((row) => row.original);
useEffect(() => {
setExposedVariable('currentPageData', pageData);
}, [JSON.stringify(pageData)]);
function downlaodPopover() {
const options = [
{ dataCy: 'option-download-CSV', text: 'Download as CSV', value: 'csv' },
@ -1013,7 +1063,6 @@ export function Table({
)}
{showFilterButton && !loadingState && (
<div className="position-relative">
{''}
<Tooltip id="tooltip-for-filter-data" className="tooltip" />
<ButtonSolid
variant="tertiary"
@ -1427,6 +1476,14 @@ export function Table({
{...cellProps}
style={{ ...cellProps.style, backgroundColor: cellBackgroundColor ?? 'inherit' }}
onClick={(e) => {
if (
(isEditable || ['rightActions', 'leftActions'].includes(cell.column.id)) &&
allowSelection &&
!selectRowOnCellEdit
) {
// to avoid on click event getting propagating to row when td is editable or has action button and allowSelection is true and selectRowOnCellEdit is false
e.stopPropagation();
}
setExposedVariable('selectedCell', {
columnName: cell.column.exportValue,
columnKey: cell.column.key,
@ -1683,20 +1740,19 @@ export function Table({
</div>
</div>
)}
{tableDetails.filterDetails.filtersVisible && (
<Filter
hideFilters={hideFilters}
filters={tableDetails.filterDetails.filters}
columns={columnData.map((column) => {
return { name: column.Header, value: column.id };
})}
mergeToFilterDetails={mergeToFilterDetails}
filterDetails={tableDetails.filterDetails}
darkMode={darkMode}
setAllFilters={setAllFilters}
fireEvent={fireEvent}
/>
)}
<Filter
hideFilters={hideFilters}
filters={tableDetails.filterDetails.filters}
columns={columnData.map((column) => {
return { name: column.Header, value: column.id };
})}
mergeToFilterDetails={mergeToFilterDetails}
filterDetails={tableDetails.filterDetails}
darkMode={darkMode}
setAllFilters={setAllFilters}
fireEvent={fireEvent}
setExposedVariable={setExposedVariable}
/>
{tableDetails.addNewRowsDetails.addingNewRows && (
<AddNewRowComponent
hideAddNewRowPopup={hideAddNewRowPopup}

View file

@ -14,8 +14,8 @@ export default function autogenerateColumns(
if (dynamicColumn.length > 0 && dynamicColumn[0].name) {
const generatedColumns = dynamicColumn.map((item) => {
return {
...item,
id: uuidv4(),
...item,
name: item?.name,
key: item?.key || item?.name,
autogenerated: true,

View file

@ -219,7 +219,6 @@ export default function generateColumnsData({
});
const { isValid, validationError } = validationData;
console.log('validationData', column.minValue, column.maxValue, validationData);
const cellStyles = {
color: textColor ?? '',
};

View file

@ -76,6 +76,8 @@ export default function loadPropertiesAndStyles(properties, styles, darkMode, co
const showAddNewRowButton = properties?.showAddNewRowButton ?? true;
const allowSelection = properties?.allowSelection ?? (showBulkSelector || highlightSelectedRow) ? true : false;
const defaultSelectedRow = properties?.defaultSelectedRow ?? { id: 1 };
const selectRowOnCellEdit = properties?.selectRowOnCellEdit ?? true;
return {
color,
serverSidePagination,
@ -107,5 +109,6 @@ export default function loadPropertiesAndStyles(properties, styles, darkMode, co
defaultSelectedRow,
showAddNewRowButton,
allowSelection,
selectRowOnCellEdit,
};
}

View file

@ -1,15 +1,16 @@
import React, { useState, useEffect } from 'react';
import DOMPurify from 'dompurify';
import Markdown from 'react-markdown';
import './text.scss';
import Loader from '@/ToolJetUI/Loader/Loader';
export const Text = function Text({
height,
properties,
styles,
darkMode,
setExposedVariable,
setExposedVariables,
dataCy,
}) {
const VERTICAL_ALIGNMENT_VS_CSS_VALUE = {
top: 'flex-start',
center: 'center',
bottom: 'flex-end',
};
export const Text = function Text({ height, properties, fireEvent, styles, darkMode, setExposedVariable, dataCy }) {
let {
textSize,
textColor,
@ -24,50 +25,85 @@ export const Text = function Text({
letterSpacing,
wordSpacing,
fontVariant,
disabledState,
boxShadow,
verticalAlignment,
borderColor,
borderRadius,
isScrollRequired,
} = styles;
const { loadingState } = properties;
const { loadingState, textFormat, disabledState } = properties;
const [text, setText] = useState(() => computeText());
const [visibility, setVisibility] = useState(styles.visibility);
const [visibility, setVisibility] = useState(properties.visibility);
const [isLoading, setLoading] = useState(loadingState);
const [isDisabled, setIsDisabled] = useState(disabledState);
const color = ['#000', '#000000'].includes(textColor) ? (darkMode ? '#fff' : '#000') : textColor;
useEffect(() => {
if (visibility !== styles.visibility) setVisibility(styles.visibility);
if (visibility !== properties.visibility) setVisibility(properties.visibility);
if (isLoading !== loadingState) setLoading(loadingState);
if (isDisabled !== disabledState) setIsDisabled(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [styles.visibility]);
}, [properties.visibility, loadingState, disabledState]);
useEffect(() => {
const text = computeText();
setText(text);
setExposedVariable('text', text);
const exposedVariables = {
text: text,
setText: async function (text) {
setText(text);
setExposedVariable('text', text);
},
visibility: async function (value) {
setVisibility(value);
},
};
setExposedVariables(exposedVariables);
setExposedVariable('setText', async function (text) {
setText(text);
setExposedVariable('text', text);
});
setExposedVariable('clear', async function (text) {
setText('');
setExposedVariable('text', '');
});
setExposedVariable('isVisible', properties.visibility);
setExposedVariable('isLoading', loadingState);
setExposedVariable('isDisabled', disabledState);
setExposedVariable('visibility', async function (value) {
setVisibility(value);
});
setExposedVariable('setVisibility', async function (value) {
setVisibility(value);
});
setExposedVariable('setLoading', async function (value) {
setLoading(value);
});
setExposedVariable('setDisable', async function (value) {
setIsDisabled(value);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.text, setText, setVisibility]);
}, [
properties.text,
setText,
setVisibility,
properties.visibility,
loadingState,
disabledState,
setIsDisabled,
setLoading,
]);
function computeText() {
return properties.text === 0 || properties.text === false ? properties.text?.toString() : properties.text;
}
const handleClick = () => {
fireEvent('onClick');
};
const computedStyles = {
backgroundColor,
height: `${height}px`,
backgroundColor: darkMode && ['#edeff5'].includes(backgroundColor) ? '#2f3c4c' : backgroundColor,
color,
height,
display: visibility ? 'flex' : 'none',
alignItems: 'center',
textAlign,
fontWeight: fontWeight ? fontWeight : fontWeight === '0' ? 0 : 'normal',
lineHeight: lineHeight ?? 1.5,
textDecoration: decoration ?? 'none',
@ -78,20 +114,51 @@ export const Text = function Text({
letterSpacing: `${letterSpacing}px` ?? '0px',
wordSpacing: `${wordSpacing}px` ?? '0px',
boxShadow,
border: '1px solid',
borderColor: darkMode && ['#f2f2f5'].includes(borderColor) ? '#2f3c4c' : borderColor ? borderColor : 'transparent',
borderRadius: borderRadius ? `${borderRadius}px` : '0px',
fontSize: `${textSize}px`,
};
const commonStyles = {
width: '100%',
height: '100%',
overflowY: isScrollRequired == 'enabled' ? 'auto' : 'hidden',
display: 'flex',
flexDirection: 'column',
justifyContent: VERTICAL_ALIGNMENT_VS_CSS_VALUE[verticalAlignment],
textAlign,
overflowX: isScrollRequired === 'disabled' && 'hidden',
};
return (
<div data-disabled={disabledState} className="text-widget" style={computedStyles} data-cy={dataCy}>
{!loadingState && (
<div
style={{ width: '100%', fontSize: textSize }}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(text || '0') }}
/>
<div
data-disabled={isDisabled}
className="text-widget"
style={computedStyles}
data-cy={dataCy}
onMouseOver={() => {
fireEvent('onHover');
}}
onClick={handleClick}
>
{!isLoading && (
<div style={commonStyles} className="text-widget-section">
{textFormat === 'plainText' && <div>{text}</div>}
{textFormat === 'markdown' && <Markdown className={'reactMarkdown'}>{text}</Markdown>}
{(textFormat === 'html' || !textFormat) && (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(text || ''),
}}
/>
)}
</div>
)}
{loadingState === true && (
<div style={{ width: '100%' }}>
{isLoading === true && (
<div style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<center>
<div className="spinner-border" role="status"></div>
<Loader width="16" absolute={false} />
</center>
</div>
)}

View file

@ -1,4 +1,11 @@
import React, { useEffect, useRef, useState } from 'react';
import { resolveReferences } from '@/_helpers/utils';
import { useCurrentState } from '@/_stores/currentStateStore';
import { ToolTip } from '@/_components/ToolTip';
import * as Icons from '@tabler/icons-react';
import Loader from '@/ToolJetUI/Loader/Loader';
const tinycolor = require('tinycolor2');
import Label from '@/_ui/Label';
export const TextInput = function TextInput({
height,
@ -11,33 +18,122 @@ export const TextInput = function TextInput({
component,
darkMode,
dataCy,
isResizing,
adjustHeightBasedOnAlignment,
currentLayout,
}) {
const textInputRef = useRef();
const labelRef = useRef();
const [disable, setDisable] = useState(styles.disabledState);
const { loadingState, tooltip, disabledState, label, placeholder } = properties;
const {
padding,
borderRadius,
borderColor,
backgroundColor,
textColor,
boxShadow,
width,
alignment,
direction,
color,
auto,
errTextColor,
iconColor,
accentColor,
} = styles;
const [disable, setDisable] = useState(disabledState || loadingState);
const [value, setValue] = useState(properties.value);
const [visibility, setVisibility] = useState(styles.visibility);
const [visibility, setVisibility] = useState(properties.visibility);
const { isValid, validationError } = validate(value);
const [showValidationError, setShowValidationError] = useState(false);
const currentState = useCurrentState();
const isMandatory = resolveReferences(component?.definition?.validation?.mandatory?.value, currentState);
const [labelWidth, setLabelWidth] = useState(0);
const defaultAlignment = alignment === 'side' || alignment === 'top' ? alignment : 'side';
const [loading, setLoading] = useState(loadingState);
const [isFocused, setIsFocused] = useState(false);
const _width = (width / 100) * 70; // Max width which label can go is 70% for better UX calculate width based on this value
const computedStyles = {
height,
borderRadius: `${styles.borderRadius}px`,
color: darkMode && styles.textColor === '#000' ? '#fff' : styles.textColor,
borderColor: styles.borderColor,
backgroundColor: darkMode && ['#fff'].includes(styles.backgroundColor) ? '#232e3c' : styles.backgroundColor,
boxShadow: styles.boxShadow,
height: height == 36 ? (padding == 'default' ? '36px' : '38px') : padding == 'default' ? height : height + 2,
borderRadius: `${borderRadius}px`,
color: darkMode && textColor === '#11181C' ? '#ECEDEE' : textColor,
borderColor: isFocused
? accentColor
: ['#D7DBDF'].includes(borderColor)
? darkMode
? '#6D757D7A'
: '#6A727C47'
: borderColor,
'--tblr-input-border-color-darker': tinycolor(borderColor).darken(24).toString(),
backgroundColor: darkMode && ['#fff'].includes(backgroundColor) ? '#313538' : backgroundColor,
boxShadow: boxShadow,
padding: styles.iconVisibility ? '8px 10px 8px 29px' : '8px 10px 8px 10px',
// flex: padding !== 'none' && 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
};
const loaderStyle = {
right:
direction === 'right' &&
defaultAlignment === 'side' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0))
? `${labelWidth + 11}px`
: '11px',
top: `${
defaultAlignment === 'top'
? ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
'calc(50% + 10px)'
: ''
}`,
transform:
defaultAlignment === 'top' &&
((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)) &&
' translateY(-50%)',
zIndex: 3,
};
useEffect(() => {
if (labelRef?.current) {
const absolutewidth = labelRef?.current?.getBoundingClientRect()?.width;
setLabelWidth(absolutewidth);
} else setLabelWidth(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isResizing,
width,
auto,
defaultAlignment,
component?.definition?.styles?.iconVisibility?.value,
label?.length,
isMandatory,
padding,
direction,
alignment,
labelRef?.current?.getBoundingClientRect()?.width,
]);
useEffect(() => {
disable !== styles.disabledState && setDisable(styles.disabledState);
setExposedVariable('label', label);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [styles.disabledState]);
}, [label]);
useEffect(() => {
visibility !== styles.visibility && setVisibility(styles.visibility);
disable !== disabledState && setDisable(disabledState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [styles.visibility]);
}, [disabledState]);
useEffect(() => {
visibility !== properties.visibility && setVisibility(properties.visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
loading !== loadingState && setLoading(loadingState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loadingState]);
useEffect(() => {
setExposedVariable('isValid', isValid);
@ -66,7 +162,6 @@ export const TextInput = function TextInput({
},
};
setExposedVariables(exposedVariables);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -84,48 +179,176 @@ export const TextInput = function TextInput({
setExposedVariables(exposedVariables);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setValue]);
const iconName = styles.icon; // Replace with the name of the icon you want
// eslint-disable-next-line import/namespace
const IconElement = Icons[iconName] == undefined ? Icons['IconHome2'] : Icons[iconName];
// eslint-disable-next-line import/namespace
return (
<div data-disabled={disable} className={`text-input ${visibility || 'invisible'}`}>
<input
ref={textInputRef}
onKeyUp={(e) => {
if (e.key == 'Enter') {
useEffect(() => {
if (alignment == 'top' && ((label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)))
adjustHeightBasedOnAlignment(true);
else adjustHeightBasedOnAlignment(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alignment, label?.length, currentLayout]);
useEffect(() => {
setExposedVariable('isMandatory', isMandatory);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMandatory]);
useEffect(() => {
setExposedVariable('isLoading', loading);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [loading]);
useEffect(() => {
setExposedVariable('setLoading', async function (loading) {
setLoading(loading);
setExposedVariable('isLoading', loading);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.loadingState]);
useEffect(() => {
setExposedVariable('isVisible', visibility);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibility]);
useEffect(() => {
setExposedVariable('setVisibility', async function (state) {
setVisibility(state);
setExposedVariable('isVisible', state);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [properties.visibility]);
useEffect(() => {
setExposedVariable('setDisable', async function (disable) {
setDisable(disable);
setExposedVariable('isDisabled', disable);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disabledState]);
useEffect(() => {
setExposedVariable('isDisabled', disable);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [disable]);
const renderInput = () => (
<>
<div
data-disabled={disable || loading}
className={`text-input d-flex ${
defaultAlignment === 'top' &&
((width != 0 && label?.length != 0) || (auto && width == 0 && label && label?.length != 0))
? 'flex-column'
: 'align-items-center '
} ${direction === 'right' && defaultAlignment === 'side' ? 'flex-row-reverse' : ''}
${direction === 'right' && defaultAlignment === 'top' ? 'text-right' : ''}
${visibility || 'invisible'}`}
style={{
position: 'relative',
whiteSpace: 'nowrap',
width: '100%',
}}
>
<Label
label={label}
width={width}
labelRef={labelRef}
darkMode={darkMode}
color={color}
defaultAlignment={defaultAlignment}
direction={direction}
auto={auto}
isMandatory={isMandatory}
_width={_width}
/>
{component?.definition?.styles?.iconVisibility?.value && !isResizing && (
<IconElement
data-cy={'text-input-icon'}
style={{
width: '16px',
height: '16px',
left:
direction === 'right'
? '11px'
: defaultAlignment === 'top'
? '11px'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? `${labelWidth + 11}px`
: '11px', //23 :: is 10 px inside the input + 1 px border + 12px margin right
position: 'absolute',
top: `${
defaultAlignment === 'side'
? '50%'
: (label?.length > 0 && width > 0) || (auto && width == 0 && label && label?.length != 0)
? 'calc(50% + 10px)'
: '50%'
}`,
transform: ' translateY(-50%)',
color: iconColor,
zIndex: 3,
}}
stroke={1.5}
/>
)}
<input
data-cy={`label-${String(component.name).toLowerCase()}`}
ref={textInputRef}
className={`tj-text-input-widget ${
!isValid && showValidationError ? 'is-invalid' : ''
} validation-without-icon ${darkMode && 'dark-theme-placeholder'}`}
onKeyUp={(e) => {
if (e.key === 'Enter') {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onEnterPressed');
}
}}
onChange={(e) => {
setValue(e.target.value);
setExposedVariable('value', e.target.value);
fireEvent('onChange');
}}
onBlur={(e) => {
setShowValidationError(true);
e.stopPropagation();
fireEvent('onBlur');
}}
onFocus={(e) => {
e.stopPropagation();
fireEvent('onFocus');
}}
type="text"
className={`form-control ${!isValid ? 'is-invalid' : ''} validation-without-icon ${
darkMode && 'dark-theme-placeholder'
}`}
placeholder={properties.placeholder}
style={computedStyles}
value={value}
data-cy={dataCy}
/>
<div
className="invalid-feedback"
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
style={{ color: styles.errTextColor }}
>
{showValidationError && validationError}
fireEvent('onChange');
}}
onBlur={(e) => {
setShowValidationError(true);
setIsFocused(false);
e.stopPropagation();
fireEvent('onBlur');
setIsFocused(false);
}}
onFocus={(e) => {
setIsFocused(true);
e.stopPropagation();
setTimeout(() => {
fireEvent('onFocus');
}, 0);
}}
type="text"
placeholder={placeholder}
style={computedStyles}
value={value}
disabled={disable || loading}
/>
{loading && <Loader style={{ ...loaderStyle }} width="16" />}
</div>
</div>
{showValidationError && visibility && (
<div
className="tj-text-sm"
data-cy={`${String(component.name).toLowerCase()}-invalid-feedback`}
style={{
color: errTextColor,
textAlign: direction == 'left' && 'end',
}}
>
{showValidationError && validationError}
</div>
)}
</>
);
return <>{renderInput()}</>;
};

View file

@ -0,0 +1,39 @@
.arrow {
cursor: pointer;
user-select: none;
position: absolute;
font-size: 12px;
}
.numberinput-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.input-number::-webkit-inner-spin-button,
.input-number::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.input-number {
align-items: center;
-webkit-appearance: none !important;
/* Remove clear button on Safari */
-moz-appearance: textfield !important;
/* Remove clear button on Firefox */
appearance: none !important;
/* Remove clear button on modern browsers */
}
.numberinput-down-arrow {
position: absolute;
width: 20px;
}
.numberinput-up-arrow {
position: absolute;
width: 20px;
}

View file

@ -0,0 +1,25 @@
.reactMarkdown {
p,h1,h2,h3,h4,h5,h6 {
margin-bottom: 0px;
}
}
.text-widget-section {
scrollbar-color: transparent transparent;
scrollbar-width: thin;
&::-webkit-scrollbar {
background-color: transparent;
width: 6px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
&:hover{
scrollbar-color: #a0a6ae transparent;
}
}

View file

@ -13,13 +13,14 @@ export const ConfigHandle = function ConfigHandle({
customClassName = '',
configWidgetHandlerForModalComponent = false,
isVersionReleased,
isVerticalResizingAllowed,
}) {
return (
<div
className={`config-handle ${customClassName}`}
ref={dragRef}
style={{
top: position === 'top' ? '-22px' : widgetTop + widgetHeight - 10,
top: position === 'top' ? (!isVerticalResizingAllowed() ? '-20px' : '-22px') : widgetTop + widgetHeight - 10,
}}
>
<span

View file

@ -50,7 +50,6 @@ export const Container = ({
// Dont update first time to skip
// redundant save on app definition load
const firstUpdate = useRef(true);
const { showComments, currentLayout } = useEditorStore(
(state) => ({
showComments: state?.showComments,
@ -74,7 +73,7 @@ export const Container = ({
const gridWidth = canvasWidth / NO_OF_GRIDS;
const styles = {
width: currentLayout === 'mobile' ? deviceWindowWidth : '100%',
maxWidth: `${canvasWidth}px`,
maxWidth: currentLayout === 'mobile' ? deviceWindowWidth : `${canvasWidth}px`,
backgroundSize: `${gridWidth}px 10px`,
};
@ -240,7 +239,6 @@ export const Container = ({
const bottomPadding = mode === 'view' ? 100 : 300;
const frameHeight = mode === 'view' ? 45 : 85;
setCanvasHeight(`max(100vh - ${frameHeight}px, ${maxHeight + bottomPadding}px)`);
},
[setCanvasHeight, currentLayout, mode]
@ -562,7 +560,6 @@ export const Container = ({
},
[setIsDragging]
);
const containerProps = useMemo(() => {
return {
mode,

View file

@ -24,33 +24,6 @@ const resizerClasses = {
topLeft: 'top-left',
};
const resizerStyles = {
topRight: {
width: '8px',
height: '8px',
right: '-4px',
top: '-4px',
},
bottomRight: {
width: '8px',
height: '8px',
right: '-4px',
bottom: '-4px',
},
bottomLeft: {
width: '8px',
height: '8px',
left: '-4px',
bottom: '-4px',
},
topLeft: {
width: '8px',
height: '8px',
left: '-4px',
top: '-4px',
},
};
function computeWidth(currentLayoutOptions) {
return `${currentLayoutOptions?.width}%`;
}
@ -121,6 +94,70 @@ export const DraggableBox = React.memo(
shallow
);
const currentState = useCurrentState();
const [boxHeight, setboxHeight] = useState(layoutData?.height); // height for layouting with top and side values
const resizerStyles = {
topRight: {
width: '8px',
height: '8px',
right: '-4px',
top: '-4px',
},
bottomRight: {
width: '8px',
height: '8px',
right: '-4px',
bottom: '-4px',
},
bottomLeft: {
width: '8px',
height: '8px',
left: '-4px',
bottom: '-4px',
},
topLeft: {
width: '8px',
height: '8px',
left: '-4px',
top: '-4px',
},
};
if (!isVerticalResizingAllowed()) {
resizerStyles.right = {
position: 'absolute',
height: '20px',
width: '5px',
right: '-3px',
background: '#4368E3',
borderRadius: '8px',
top: '50%',
transform: 'translateY(-50%)',
display:
(mode === 'edit' && !readOnly && mouseOver) || isResizing || isDragging2 || isSelectedComponent
? isVerticalResizingAllowed()
? 'none'
: 'block'
: 'none',
zIndex: 5,
};
resizerStyles.left = {
position: 'absolute',
height: '20px',
width: '5px',
left: '-3px',
background: '#4368E3',
borderRadius: '8px',
top: '50%',
transform: 'translateY(-50%)',
display:
(mode === 'edit' && !readOnly && mouseOver) || isResizing || isDragging2 || isSelectedComponent
? isVerticalResizingAllowed()
? 'none'
: 'block'
: 'none',
zIndex: 5,
};
}
const [{ isDragging }, drag, preview] = useDrag(
() => ({
type: ItemTypes.BOX,
@ -165,7 +202,6 @@ export const DraggableBox = React.memo(
display: 'inline-block',
alignItems: 'center',
justifyContent: 'center',
padding: '0px',
};
let _refProps = {};
@ -189,11 +225,9 @@ export const DraggableBox = React.memo(
width: 445,
height: 500,
};
const layoutData = inCanvas ? layouts[currentLayout] || defaultData : defaultData;
const gridWidth = canvasWidth / NO_OF_GRIDS;
const width = (canvasWidth * layoutData.width) / NO_OF_GRIDS;
const configWidgetHandlerForModalComponent =
!isSelectedComponent &&
component.component === 'Modal' &&
@ -203,7 +237,21 @@ export const DraggableBox = React.memo(
if (selectionInProgress) return;
setHoveredComponent(id);
};
function isVerticalResizingAllowed() {
// Return true if vertical resizing is allowed, false otherwise
return (
mode === 'edit' &&
component.component !== 'TextInput' &&
component.component !== 'PasswordInput' &&
component.component !== 'NumberInput' &&
!readOnly
);
}
const adjustHeightBasedOnAlignment = (increase) => {
if (increase) return setboxHeight(layoutData?.height + 20);
else return setboxHeight(layoutData?.height);
};
return (
<div
className={
@ -240,7 +288,7 @@ export const DraggableBox = React.memo(
dragGrid={[gridWidth, 10]}
size={{
width: width,
height: layoutData.height,
height: isVerticalResizingAllowed() ? layoutData.height : boxHeight,
}}
position={{
x: layoutData ? (layoutData.left * canvasWidth) / 100 : 0,
@ -260,7 +308,16 @@ export const DraggableBox = React.memo(
}}
resizeHandleClasses={isSelectedComponent || mouseOver ? resizerClasses : {}}
resizeHandleStyles={resizerStyles}
enableResizing={mode === 'edit' && !readOnly}
enableResizing={{
top: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
right: mode == 'edit' && !readOnly && true,
bottom: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
left: mode == 'edit' && !readOnly && true,
topRight: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
bottomRight: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
bottomLeft: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
topLeft: mode == 'edit' && !readOnly && isVerticalResizingAllowed(),
}}
disableDragging={mode !== 'edit' || readOnly}
onDragStop={(e, direction) => {
setDragging(false);
@ -288,6 +345,7 @@ export const DraggableBox = React.memo(
widgetHeight={layoutData.height}
isMultipleComponentsSelected={isMultipleComponentsSelected}
configWidgetHandlerForModalComponent={configWidgetHandlerForModalComponent}
isVerticalResizingAllowed={isVerticalResizingAllowed}
/>
)}
{/* Adding a sentry's error boundary to differentiate between our generic error boundary and one from editor's component */}
@ -320,6 +378,9 @@ export const DraggableBox = React.memo(
allComponents={allComponents}
sideBarDebugger={sideBarDebugger}
childComponents={childComponents}
isResizing={isResizing}
adjustHeightBasedOnAlignment={adjustHeightBasedOnAlignment}
currentLayout={currentLayout}
/>
</Sentry.ErrorBoundary>
</div>

View file

@ -538,6 +538,8 @@ const EditorComponent = (props) => {
};
const onNameChanged = (newName) => {
app.name = newName;
updateState({ appName: newName, app: app });
updateState({ appName: newName });
setWindowTitle({ page: pageTitles.EDITOR, appName: newName });
};

View file

@ -5,7 +5,15 @@ import { useEditorStore } from '@/_stores/editorStore';
import { shallow } from 'zustand/shallow';
import SolidIcon from '@/_ui/Icon/SolidIcons';
function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo }) {
function HeaderActions({
handleUndo,
canUndo,
handleRedo,
canRedo,
showToggleLayoutBtn,
showUndoRedoBtn,
showFullWidth,
}) {
const darkMode = localStorage.getItem('darkMode') === 'true';
const { currentLayout, toggleCurrentLayout } = useEditorStore(
(state) => ({
@ -15,94 +23,100 @@ function HeaderActions({ handleUndo, canUndo, handleRedo, canRedo }) {
shallow
);
return (
<div className="editor-header-actions">
<div style={{ borderRadius: 6 }}>
<div
className="d-flex align-items-center p-1 current-layout"
style={{ height: 28, background: darkMode ? '#202425' : '#F1F3F5', borderRadius: 6 }}
role="tablist"
aria-orientation="horizontal"
>
<button
className={cx('btn border-0 p-1', {
'bg-transparent': currentLayout !== 'desktop',
'bg-white': currentLayout === 'desktop',
'opacity-100': currentLayout === 'desktop',
})}
style={{ height: 20 }}
role="tab"
type="button"
aria-selected="true"
tabIndex="0"
onClick={() => toggleCurrentLayout('desktop')}
data-cy={`button-change-layout-to-desktop`}
<div className={cx('editor-header-actions', { 'w-100': showFullWidth })}>
{showToggleLayoutBtn && (
<div style={{ borderRadius: 6 }} className={cx({ 'w-100': showFullWidth })}>
<div
className="d-flex align-items-center p-1 current-layout"
style={{ height: 28, background: darkMode ? '#202425' : '#F1F3F5', borderRadius: 6 }}
role="tablist"
aria-orientation="horizontal"
>
<button
className={cx('btn border-0 p-1', {
'bg-transparent': currentLayout !== 'desktop',
'bg-white opacity-100': currentLayout === 'desktop',
'w-100': showFullWidth,
})}
style={{ height: 20 }}
role="tab"
type="button"
aria-selected="true"
tabIndex="0"
onClick={() => toggleCurrentLayout('desktop')}
data-cy={`button-change-layout-to-desktop`}
>
<SolidIcon
name="computer"
width="14"
fill={currentLayout === 'desktop' ? 'var(--slate12)' : 'var(--slate8)'}
/>
</button>
<button
className={cx('btn border-0 p-1', {
'bg-transparent': currentLayout !== 'mobile',
'bg-white opacity-100': currentLayout === 'mobile',
'w-100': showFullWidth,
})}
role="tab"
type="button"
style={{ height: 20 }}
aria-selected="false"
tabIndex="-1"
onClick={() => {
toggleCurrentLayout('mobile');
}}
data-cy={`button-change-layout-to-mobile`}
>
<SolidIcon
name="mobile"
width="14"
fill={currentLayout !== 'desktop' ? 'var(--slate12)' : 'var(--slate8)'}
/>
</button>
</div>
</div>
)}
{showUndoRedoBtn && (
<div className="undo-redo-container">
<div
onClick={handleUndo}
className="tj-ghost-black-btn"
data-tooltip-id="tooltip-for-undo"
data-tooltip-content="Undo"
data-cy={`editor-undo-button`}
>
<SolidIcon
name="computer"
width="14"
fill={currentLayout === 'desktop' ? 'var(--slate12)' : 'var(--slate8)'}
width="16"
height="16"
viewBox="0 0 16 16"
fill={darkMode ? '#fff' : '#2c3e50'}
name="arrowforwardup"
className={cx('cursor-pointer', {
disabled: !canUndo,
})}
/>
</button>
<button
className={cx('btn border-0 p-1', {
'bg-transparent': currentLayout !== 'mobile',
'bg-white': currentLayout === 'mobile',
'opacity-100': currentLayout === 'mobile',
})}
role="tab"
type="button"
style={{ height: 20 }}
aria-selected="false"
tabIndex="-1"
onClick={() => toggleCurrentLayout('mobile')}
data-cy={`button-change-layout-to-mobile`}
</div>
<div
onClick={handleRedo}
className="tj-ghost-black-btn"
data-tooltip-id="tooltip-for-redo"
data-tooltip-content="Redo"
data-cy={`editor-redo-button`}
>
<SolidIcon
name="mobile"
width="14"
fill={currentLayout !== 'desktop' ? 'var(--slate12)' : 'var(--slate8)'}
width="16"
height="16"
viewBox="0 0 16 16"
fill={darkMode ? '#fff' : '#2c3e50'}
name="arrowbackup"
className={cx('cursor-pointer', {
disabled: !canRedo,
})}
/>
</button>
</div>
</div>
</div>
<div className="undo-redo-container">
<div
onClick={handleUndo}
className="tj-ghost-black-btn"
data-tooltip-id="tooltip-for-undo"
data-tooltip-content="Undo"
data-cy={`editor-undo-button`}
>
<SolidIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill={darkMode ? '#fff' : '#2c3e50'}
name="arrowforwardup"
className={cx('cursor-pointer', {
disabled: !canUndo,
})}
/>
</div>
<div
onClick={handleRedo}
className="tj-ghost-black-btn"
data-tooltip-id="tooltip-for-redo"
data-tooltip-content="Redo"
data-cy={`editor-redo-button`}
>
<SolidIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill={darkMode ? '#fff' : '#2c3e50'}
name="arrowbackup"
className={cx('cursor-pointer', {
disabled: !canRedo,
})}
/>
</div>
</div>
)}
<Tooltip id="tooltip-for-undo" className="tooltip" />
<Tooltip id="tooltip-for-redo" className="tooltip" />
</div>

View file

@ -4,7 +4,7 @@ import AppLogo from '@/_components/AppLogo';
import EditAppName from './EditAppName';
import HeaderActions from './HeaderActions';
import RealtimeAvatars from '../RealtimeAvatars';
import { AppVersionsManager } from '../AppVersionsManager/List';
import { AppVersionsManager } from '@/Editor/AppVersionsManager/AppVersionsManager';
import { ManageAppUsers } from '../ManageAppUsers';
import { ReleaseVersionButton } from '../ReleaseVersionButton';
import cx from 'classnames';
@ -110,7 +110,14 @@ export default function EditorHeader({
<div className="global-settings-app-wrapper p-0 m-0 ">
<EditAppName appId={appId} appName={appName} onNameChanged={onNameChanged} />
</div>
<HeaderActions canUndo={canUndo} canRedo={canRedo} handleUndo={handleUndo} handleRedo={handleRedo} />
<HeaderActions
canUndo={canUndo}
canRedo={canRedo}
handleUndo={handleUndo}
handleRedo={handleRedo}
showToggleLayoutBtn
showUndoRedoBtn
/>
<div className="d-flex align-items-center">
<div style={{ width: '100px', marginRight: '20px' }}>
<span
@ -140,18 +147,15 @@ export default function EditorHeader({
</div>
</div>
<div className="navbar-seperator"></div>
<div className="d-flex align-items-center p-0" style={{ marginRight: '12px' }}>
<div className="d-flex version-manager-container p-0">
{editingVersion && (
<AppVersionsManager
appId={appId}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
onVersionDelete={onVersionDelete}
isPublic={isPublic ?? false}
/>
)}
</div>
</div>
{editingVersion && (
<AppVersionsManager
appId={appId}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
onVersionDelete={onVersionDelete}
isPublic={isPublic ?? false}
/>
)}
</div>
<div
className="d-flex justify-content-end navbar-right-section"

View file

@ -5,6 +5,15 @@ import { renderElement } from '../Utils';
// eslint-disable-next-line import/no-unresolved
import i18next from 'i18next';
import { resolveReferences } from '@/_helpers/utils';
import { AllComponents } from '@/Editor/Box';
const SHOW_ADDITIONAL_ACTIONS = ['Text', 'TextInput', 'NumberInput', 'PasswordInput'];
const PROPERTIES_VS_ACCORDION_TITLE = {
Text: 'Data',
TextInput: 'Data',
PasswordInput: 'Data',
NumberInput: 'Data',
};
export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => {
const {
@ -19,9 +28,17 @@ export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => {
pages,
} = restProps;
const properties = Object.keys(componentMeta.properties);
const events = Object.keys(componentMeta.events);
const validations = Object.keys(componentMeta.validation || {});
let properties = [];
let additionalActions = [];
for (const [key] of Object.entries(componentMeta?.properties)) {
if (componentMeta?.properties[key]?.section === 'additionalActions') {
additionalActions.push(key);
} else {
properties.push(key);
}
}
const accordionItems = baseComponentProperties(
properties,
@ -37,7 +54,8 @@ export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => {
components,
validations,
darkMode,
pages
pages,
additionalActions
);
return <Accordion items={accordionItems} />;
@ -57,14 +75,18 @@ export const baseComponentProperties = (
allComponents,
validations,
darkMode,
pages
pages,
additionalActions
) => {
// Add widget title to section key to filter that property section from specified widgets' settings
const accordionFilters = {
Properties: [],
Events: [],
Validation: [],
General: ['Modal'],
'Additional Actions': Object.keys(AllComponents).filter(
(component) => !SHOW_ADDITIONAL_ACTIONS.includes(component)
),
General: ['Modal', 'TextInput', 'PasswordInput', 'NumberInput', 'Text'],
Layout: [],
};
if (component.component.component === 'Listview') {
@ -75,7 +97,9 @@ export const baseComponentProperties = (
let items = [];
if (properties.length > 0) {
items.push({
title: `${i18next.t('widget.common.properties', 'Properties')}`,
title:
PROPERTIES_VS_ACCORDION_TITLE[component?.component?.component] ??
`${i18next.t('widget.common.properties', 'Properties')}`,
children: properties.map((property) =>
renderElement(
component,
@ -113,7 +137,6 @@ export const baseComponentProperties = (
),
});
}
if (validations.length > 0) {
items.push({
title: `${i18next.t('widget.common.validation', 'Validation')}`,
@ -127,7 +150,8 @@ export const baseComponentProperties = (
'validation',
currentState,
allComponents,
darkMode
darkMode,
componentMeta.validation?.[property]?.placeholder
)
),
});
@ -153,7 +177,27 @@ export const baseComponentProperties = (
});
items.push({
title: `${i18next.t('widget.common.layout', 'Layout')}`,
title: `${i18next.t('widget.common.additionalActions', 'Additional Actions')}`,
isOpen: true,
children: additionalActions?.map((property) => {
const paramType = property === 'Tooltip' ? 'general' : 'properties';
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
paramType,
currentState,
allComponents,
darkMode,
componentMeta.properties?.[property]?.placeholder
);
}),
});
items.push({
title: `${i18next.t('widget.common.devices', 'Devices')}`,
isOpen: true,
children: (
<>

View file

@ -1,3 +1,4 @@
import { resolveReferences } from '@/_helpers/utils';
import React from 'react';
import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
@ -13,6 +14,7 @@ export const ProgramaticallyHandleProperties = ({
paramMeta,
// eslint-disable-next-line no-unused-vars
paramType,
currentState,
}) => {
const getValueBasedOnProperty = (property, props) => {
switch (property) {
@ -45,6 +47,52 @@ export const ProgramaticallyHandleProperties = ({
const initialValue = getInitialValue(property, definition);
const options = {};
const calcFxActiveState = (props, property) => {
const fxActiveFieldsPresent = props?.hasOwnProperty('fxActiveFields');
if (!fxActiveFieldsPresent) {
return props?.fxActive ?? false;
}
const fxActiveFields = props.fxActiveFields;
const propertyPresentInFxActiveFields = fxActiveFields.length >= 1 && fxActiveFields.includes(property);
return propertyPresentInFxActiveFields;
};
const calculateFxActiveFields = (active, props, property) => {
const fxActiveFieldsPropExists = props?.hasOwnProperty('fxActiveFields') ?? false;
//to support backward compatibility, when fxActive is true for a particular column, we are passing all possible combinations which should render codehinter
const fxActive =
props?.fxActive && resolveReferences(props.fxActive, currentState)
? ['isEditable', 'columnVisibility', 'linkTarget']
: [];
const checkFxActiveFieldIsArrray = (fxActiveFieldsProperty) => {
// adding error handling mechanism for fxActiveFieldsProperty , if props.fxActiveFields is array , then return props.fxActiveFields or else return [], this will make sure, fxActiveFields wil always be array
return Array.isArray(fxActiveFieldsProperty) ? fxActiveFieldsProperty : [];
};
const fxActiveFields = fxActiveFieldsPropExists ? checkFxActiveFieldIsArrray(props.fxActiveFields) : fxActive;
const findIndexOfPropertyInFxActiveFields = (property, fxActiveFields) => {
// checking if particular property is already present in the fxActiveFields or not
const index = fxActiveFields.findIndex((prop) => prop === property);
return index;
};
const indexOfProp = findIndexOfPropertyInFxActiveFields(property, fxActiveFields);
// removing or addding property for which we want to render codehinter
if (active && indexOfProp === -1) {
//if active is true and property does not present in the fxActiveField before hand, if both conditions are satisfied, we add it to the array of fxActiveField.
fxActiveFields.push(property);
} else if (!active && indexOfProp !== -1) {
// if active is falsy and particular property is present in the fxActiveField , we remove that particular property from the array
fxActiveFields.splice(indexOfProp, 1);
}
return fxActiveFields;
};
return (
<div className={`mb-2 field ${options.className}`} onClick={(e) => e.stopPropagation()}>
<CodeHinter
@ -60,9 +108,10 @@ export const ProgramaticallyHandleProperties = ({
paramLabel={paramMeta.displayName}
fieldMeta={paramMeta}
onFxPress={(active) => {
callbackFunction(index, 'fxActive', active);
const resultFxActiveFields = calculateFxActiveFields(active, props, property);
callbackFunction(index, 'fxActiveFields', resultFxActiveFields);
}}
fxActive={props?.fxActive ?? false}
fxActive={calcFxActiveState(props, property)}
component={component.component}
className={options.className}
/>

View file

@ -268,6 +268,25 @@ class TableComponent extends React.Component {
}}
/>
</div>
<div data-cy={`transformation-field`} className="field mb-2 mt-1">
<label className="form-label">{this.props.t('widget.Table.transformationField', 'Transformation')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column?.transformation ?? '{{cellValue}}'}
theme={this.props.darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={column.name}
onChange={(value) => this.onColumnItemChange(index, 'transformation', value)}
componentName={this.getPopoverFieldSource(column.columnType, 'transformation')}
popOverCallback={(showing) => {
this.setColumnPopoverRootCloseBlocker('transformation', showing);
}}
enablePreview={false}
/>
</div>
<div className="field mb-2">
<label className="form-label">
{this.props.t('widget.Table.horizontalAlignment', 'Horizontal Alignment')}
@ -908,7 +927,7 @@ class TableComponent extends React.Component {
addNewColumn = () => {
const columns = this.props.component.component.definition.properties.columns;
const newValue = columns.value;
newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4() });
newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4(), fxActiveFields: [] });
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true);
};
@ -1133,7 +1152,9 @@ class TableComponent extends React.Component {
const rowSelectionsOptions = [
'allowSelection',
...(allowSelection ? ['highlightSelectedRow', 'showBulkSelector', 'defaultSelectedRow'] : []),
...(allowSelection
? ['highlightSelectedRow', 'showBulkSelector', 'defaultSelectedRow', 'selectRowOnCellEdit']
: []),
];
const searchSortFilterOptions = [
...(displaySearchBox ? ['displaySearchBox'] : []),

View file

@ -18,6 +18,8 @@ export const Code = ({
fxActive,
component,
verticalLine,
accordian,
placeholder,
}) => {
const currentState = useCurrentState();
@ -31,6 +33,13 @@ export const Code = ({
if (isPaginationEnabled) return '{{true}}';
return '{{false}}';
}
// Following condition is needed to support older Text component not having textFormat switch
if (component?.component?.component === 'Text' && param === 'textFormat') {
const doTextFormatAlreadyExist = component?.component?.definition?.properties?.textFormat;
if (!doTextFormatAlreadyExist) {
return 'html';
}
}
if (['showAddNewRowButton', 'allowSelection', 'defaultSelectedRow'].includes(param)) {
if (param === 'allowSelection') {
const highlightSelectedRow = component?.component?.definition?.properties?.highlightSelectedRow?.value ?? false;
@ -44,14 +53,18 @@ export const Code = ({
} else {
return '{{true}}';
}
} else {
return '';
}
if (param === 'selectRowOnCellEdit') {
const selectRowOnCellEdit =
component?.component?.definition?.properties?.selectRowOnCellEdit?.value ?? '{{true}}';
return selectRowOnCellEdit;
}
return '';
};
let initialValue = !_.isEmpty(definition) ? definition.value : getDefinitionForNewProps(param.name);
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
const paramMeta = accordian ? componentMeta[paramType]?.[param.name] : componentMeta[paramType][param.name];
const displayName = paramMeta?.displayName || param?.name;
/*
following block is written for cellSize Prop to support backward compatibility,
@ -75,13 +88,18 @@ export const Code = ({
onChange(param, 'value', value, paramType);
}
const options = paramMeta.options || {};
function onVisibilityChange(value) {
onChange({ name: 'iconVisibility' }, 'value', value, 'styles');
}
const options = paramMeta?.options || {};
const getfieldName = React.useMemo(() => {
return param.name;
}, [param]);
return (
<div className={`field ${options.className}`} style={{ marginBottom: '20px' }}>
<div className={`field ${options.className}`} style={{ marginBottom: '8px' }}>
<CodeHinter
enablePreview={true}
initialValue={initialValue}
@ -90,15 +108,21 @@ export const Code = ({
lineWrapping={true}
className={options.className}
onChange={(value) => handleCodeChanged(value)}
onVisibilityChange={(value) => onVisibilityChange(value)}
componentName={`component/${componentName}::${getfieldName}`}
type={paramMeta.type}
type={paramMeta?.type}
paramName={param.name}
paramLabel={displayName}
paramLabel={paramMeta?.showLabel !== false ? displayName : ' '}
fieldMeta={paramMeta}
onFxPress={onFxPress}
fxActive={CLIENT_SERVER_TOGGLE_FIELDS.includes(param.name) ? false : fxActive} // Client Server Toggle don't support Fx
component={component}
verticalLine={verticalLine}
isIcon={paramMeta?.isIcon}
staticText={paramMeta?.staticText}
placeholder={placeholder}
inspectorTab={paramType}
bold={true}
/>
</div>
);

View file

@ -7,7 +7,7 @@ const tooltipStyle = {
textDecorationStyle: 'dashed',
};
export const ToolTip = ({ label, meta, labelClass }) => {
export const ToolTip = ({ label, meta, labelClass, bold = false }) => {
function renderTooltip(props) {
return (
<Tooltip id="button-tooltip" {...props}>
@ -31,6 +31,7 @@ export const ToolTip = ({ label, meta, labelClass }) => {
.toLowerCase()
.replace(/\s+/g, '-')}`}
className={labelClass || 'form-label'}
style={{ fontWeight: bold ? 500 : 400 }}
>
{label}
</label>

View file

@ -48,10 +48,20 @@ export const EventManager = ({
appId,
apps,
events: allAppEvents,
eventsUpdatedLoader,
eventsCreatedLoader,
actionsUpdatedLoader,
eventToDeleteLoaderIndex,
setEventToDeleteLoaderIndex,
} = useAppDataStore((state) => ({
appId: state.appId,
apps: state.apps,
events: state.events,
eventsUpdatedLoader: state.eventsUpdatedLoader,
eventsCreatedLoader: state.eventsCreatedLoader,
actionsUpdatedLoader: state.actionsUpdatedLoader,
eventToDeleteLoaderIndex: state.eventToDeleteLoaderIndex,
setEventToDeleteLoaderIndex: state.actions.setEventToDeleteLoaderIndex,
}));
const { handleYmapEventUpdates } = useContext(EditorContext) || {};
@ -71,6 +81,7 @@ export const EventManager = ({
const [events, setEvents] = useState([]);
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
const { t } = useTranslation();
useEffect(() => {
@ -276,6 +287,11 @@ export const EventManager = ({
let updatedEvent = newEvents[index];
updatedEvent.event[param] = value;
// Remove debounce key if it's empty
if (param === 'debounce' && value === '') {
delete updatedEvent.event.debounce;
}
if (param === 'componentSpecificActionHandle') {
const getDefault = getComponentActionDefaultParams(updatedEvent.event?.componentId, value);
updatedEvent.event['componentSpecificActionParams'] = getDefault;
@ -290,7 +306,8 @@ export const EventManager = ({
diff: updatedEvent,
},
],
'update'
'update',
param
);
}
@ -298,14 +315,13 @@ export const EventManager = ({
const eventsHandler = _.cloneDeep(events);
const eventId = eventsHandler[index].id;
setEventToDeleteLoaderIndex(index);
deleteAppVersionEventHandler(eventId);
}
function addHandler() {
let newEvents = events;
const eventIndex = newEvents.length;
createAppVersionEventHandlers({
event: {
eventId: Object.keys(eventMetaDefinition?.events)[0],
@ -876,7 +892,7 @@ export const EventManager = ({
enablePreview={true}
type={param?.type}
fieldMeta={{ options: param?.options }}
cyLabel={param.displayName}
cyLabel={`event-${param.displayName}`}
component={component}
/>
</div>
@ -932,7 +948,6 @@ export const EventManager = ({
};
const renderDraggable = useDraggableInPortal();
const renderHandlers = (events) => {
return (
<DragDropContext
@ -982,6 +997,11 @@ export const EventManager = ({
removeHandler={removeHandler}
index={index}
darkMode={darkMode}
actionsUpdatedLoader={index === focusedEventIndex ? actionsUpdatedLoader : false}
eventsUpdatedLoader={index === focusedEventIndex ? eventsUpdatedLoader : false}
eventsDeletedLoader={
index === eventToDeleteLoaderIndex ? !!eventToDeleteLoaderIndex : false
}
/>
</div>
</OverlayTrigger>
@ -1000,7 +1020,7 @@ export const EventManager = ({
const renderAddHandlerBtn = () => {
return (
<AddNewButton onClick={addHandler} dataCy="add-event-handler" className="mt-0">
<AddNewButton onClick={addHandler} dataCy="add-event-handler" className="mt-0" isLoading={eventsCreatedLoader}>
{t('editor.inspector.eventManager.addHandler', 'New event handler')}
</AddNewButton>
);

View file

@ -3,7 +3,7 @@ import { componentTypes } from '../WidgetManager/components';
import { Table } from './Components/Table/Table.jsx';
import { Chart } from './Components/Chart';
import { Form } from './Components/Form';
import { renderElement } from './Utils';
import { renderElement, renderCustomStyles } from './Utils';
import { toast } from 'react-hot-toast';
import { validateQueryName, convertToKebabCase, resolveReferences } from '@/_helpers/utils';
import { ConfirmDialog } from '@/_components';
@ -83,6 +83,8 @@ export const Inspector = ({
const [inputRef, setInputFocus] = useFocus();
const [showHeaderActionsMenu, setShowHeaderActionsMenu] = useState(false);
const shouldAddBoxShadow = ['TextInput', 'PasswordInput', 'NumberInput', 'Text'];
const { isVersionReleased } = useAppVersionStore(
(state) => ({
isVersionReleased: state.isVersionReleased,
@ -310,7 +312,15 @@ export const Inspector = ({
);
const stylesTab = (
<div style={{ marginBottom: '6rem' }} className={`${isVersionReleased && 'disabled'}`}>
<div className="p-3">
<div
className={
component.component.component !== 'TextInput' &&
component.component.component !== 'PasswordInput' &&
component.component.component !== 'NumberInput' &&
component.component.component !== 'Text' &&
'p-3'
}
>
<Inspector.RenderStyleOptions
componentMeta={componentMeta}
component={component}
@ -320,7 +330,7 @@ export const Inspector = ({
allComponents={allComponents}
/>
</div>
{buildGeneralStyle()}
{!shouldAddBoxShadow.includes(component.component.component) && buildGeneralStyle()}
</div>
);
@ -353,7 +363,11 @@ export const Inspector = ({
<div>
<div className="row inspector-component-title-input-holder">
<div className="col-1" onClick={() => setSelectedComponents(EMPTY_ARRAY)}>
<span data-cy={`inspector-close-icon`} className="cursor-pointer">
<span
data-cy={`inspector-close-icon`}
className="cursor-pointer d-flex align-items-center "
style={{ height: '28px', width: '28px' }}
>
<ArrowLeft fill={'var(--slate12)'} width={'14'} />
</span>
</div>
@ -370,7 +384,7 @@ export const Inspector = ({
/>
</div>
</div>
<div className="col-2">
<div className="col-2" data-cy={'component-inspector-options'}>
<OverlayTrigger
trigger={'click'}
placement={'bottom-end'}
@ -381,6 +395,7 @@ export const Inspector = ({
<Popover.Body bsPrefix="list-item-popover-body">
{INSPECTOR_HEADER_OPTIONS.map((option) => (
<div
data-cy={`component-inspector-${String(option?.value).toLowerCase()}-button`}
className="list-item-popover-option"
key={option?.value}
onClick={(e) => {
@ -454,10 +469,41 @@ const widgetsWithStyleConditions = {
],
},
};
const styleGroupedComponentTypes = ['TextInput', 'NumberInput', 'PasswordInput'];
const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQueries, currentState, allComponents }) => {
return Object.keys(componentMeta.styles).map((style) => {
const conditionWidget = widgetsWithStyleConditions[component?.component?.component] ?? null;
// Initialize an object to group properties by "accordian"
const groupedProperties = {};
if (
component.component.component === 'TextInput' ||
component.component.component === 'PasswordInput' ||
component.component.component === 'NumberInput' ||
component.component.component === 'Text'
) {
// Iterate over the properties in componentMeta.styles
for (const key in componentMeta.styles) {
const property = componentMeta.styles[key];
const accordian = property.accordian;
// Check if the "accordian" key exists in groupedProperties
if (!groupedProperties[accordian]) {
groupedProperties[accordian] = {}; // Create an empty object for the "accordian" key if it doesn't exist
}
// Add the property to the corresponding "accordian" object
groupedProperties[accordian][key] = property;
}
}
return Object.keys(
component.component.component === 'TextInput' ||
component.component.component === 'PasswordInput' ||
component.component.component === 'NumberInput' ||
component.component.component === 'Text'
? groupedProperties
: componentMeta.styles
).map((style) => {
const conditionWidget = widgetsWithStyleConditions[component.component.component] ?? null;
const condition = conditionWidget?.conditions.find((condition) => condition.property) ?? {};
if (conditionWidget && conditionWidget.conditions.find((condition) => condition.conditionStyles.includes(style))) {
@ -477,16 +523,43 @@ const RenderStyleOptions = ({ componentMeta, component, paramUpdated, dataQuerie
);
}
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
style,
'styles',
currentState,
allComponents
);
const items = [];
if (
component.component.component === 'TextInput' ||
component.component.component === 'PasswordInput' ||
component.component.component === 'NumberInput' ||
component.component.component === 'Text'
) {
items.push({
title: `${style}`,
children: Object.entries(groupedProperties[style]).map(([key, value]) => ({
...renderCustomStyles(
component,
componentMeta,
paramUpdated,
dataQueries,
key,
'styles',
currentState,
allComponents,
value.accordian
),
})),
});
return <Accordion key={style} items={items} />;
} else {
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
style,
'styles',
currentState,
allComponents
);
}
});
};

View file

@ -4,10 +4,18 @@ import AddRectangle from '@/_ui/Icon/solidIcons/AddRectangle';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SortableList from '@/_components/SortableList';
import { Spinner } from 'react-bootstrap';
const ManageEventButton = ({ eventDisplayName = 'Upon events', actionName, index, removeHandler }) => {
const ManageEventButton = ({
eventDisplayName = 'Upon events',
actionName,
index,
removeHandler,
actionsUpdatedLoader,
eventsUpdatedLoader,
eventsDeletedLoader,
}) => {
const [isHovered, setIsHovered] = useState(false);
return (
<div style={{ marginBottom: '8px' }}>
<div
@ -15,52 +23,67 @@ const ManageEventButton = ({ eventDisplayName = 'Upon events', actionName, index
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div data-cy="event-handler-card" className="d-flex">
<span className="d-flex align-items-center px-2">
<SortableList.DragHandle show />
</span>
<div
className="d-flex justify-content-between"
role="button"
style={{ padding: '6px 12px 6px 8px', width: '100%' }}
>
<div className="text-truncate event-handler-text" data-cy="event-handler">
{eventDisplayName}
</div>
<div className="text-truncate event-name-text" data-cy="event-name">
<small className="event-action font-weight-light text-truncate">
{actionName ? actionName : 'Select action'}
</small>
{!actionName && <AddRectangle width={13.33} />}
<span>
<span>
{isHovered && (
<ButtonSolid
variant="tertiary"
size="xs"
className={'list-menu-option-btn'}
onClick={(event) => {
event.stopPropagation();
}}
>
<div
className="list-item-popover-option"
onClick={(e) => {
e.stopPropagation();
removeHandler(index);
}}
>
<div className="d-flex align-center">
<Trash fill={'#E54D2E'} width={12} />
</div>
</div>
</ButtonSolid>
)}
</span>
</span>
{eventsDeletedLoader ? (
<div className="d-flex justify-content-center p-2">
{' '}
<Spinner style={{ width: '16px', height: '16px', color: 'var(--indigo9)' }} />
</div>
) : (
<div data-cy="event-handler-card" className="d-flex">
<span className="d-flex align-items-center px-2">
<SortableList.DragHandle show />
</span>
<div
className="d-flex justify-content-between"
role="button"
style={{ padding: '6px 12px 6px 8px', width: '100%' }}
>
<div className="text-truncate event-handler-text" data-cy="event-handler">
{!eventsUpdatedLoader ? (
eventDisplayName
) : (
<Spinner style={{ width: '16px', height: '16px', color: 'var(--indigo9)' }} />
)}
</div>
{!actionsUpdatedLoader ? (
<div className="text-truncate event-name-text" data-cy="event-name">
<small className="event-action font-weight-light text-truncate">
{actionName ? actionName : 'Select action'}
</small>
{!actionName && <AddRectangle width={13.33} />}
<span>
<span>
{isHovered && (
<ButtonSolid
variant="tertiary"
size="xs"
className={'list-menu-option-btn'}
onClick={(event) => {
event.stopPropagation();
}}
>
<div
className="list-item-popover-option"
onClick={(e) => {
e.stopPropagation();
removeHandler(index);
}}
>
<div className="d-flex align-center">
<Trash fill={'#E54D2E'} width={12} />
</div>
</div>
</ButtonSolid>
)}
</span>
</span>
</div>
) : (
<Spinner style={{ width: '16px', height: '16px', color: 'var(--indigo9)' }} />
)}
</div>
</div>
</div>
)}
</div>
</div>
);

View file

@ -17,6 +17,75 @@ export function renderQuerySelector(component, dataQueries, eventOptionUpdated,
/>
);
}
export function renderCustomStyles(
component,
componentMeta,
paramUpdated,
dataQueries,
param,
paramType,
currentState,
components = {},
accordian,
darkMode = false,
verticalLine = true,
placeholder = ''
) {
const componentConfig = component.component;
const componentDefinition = componentConfig.definition;
const paramTypeDefinition = componentDefinition[paramType] || {};
const definition = paramTypeDefinition[param] || {};
const meta = componentMeta[paramType]?.[accordian]?.[param];
if (
componentConfig.component == 'DropDown' ||
componentConfig.component == 'Form' ||
componentConfig.component == 'Listview' ||
componentConfig.component == 'TextInput' ||
componentConfig.component == 'NumberInput' ||
componentConfig.component == 'PasswordInput'
) {
const paramTypeConfig = componentMeta[paramType] || {};
const paramConfig = paramTypeConfig[param] || {};
const { conditionallyRender = null } = paramConfig;
if (conditionallyRender) {
const { key, value } = conditionallyRender;
if (paramTypeDefinition?.[key] ?? value) {
const resolvedValue = paramTypeDefinition?.[key] && resolveReferences(paramTypeDefinition?.[key], currentState);
if (resolvedValue?.value !== value) {
return;
}
}
}
}
return (
<>
<Code
param={{ name: param, ...component.component.properties[param] }}
definition={definition}
dataQueries={dataQueries}
onChange={paramUpdated}
paramType={paramType}
components={components}
componentMeta={componentMeta}
darkMode={darkMode}
componentName={component.component.name || null}
type={meta?.type}
fxActive={definition.fxActive ?? false}
onFxPress={(active) => {
paramUpdated({ name: param, ...component.component.properties[param] }, 'fxActive', active, paramType);
}}
component={component}
verticalLine={verticalLine}
accordian={accordian}
placeholder={placeholder}
/>
</>
);
}
export function renderElement(
component,
@ -28,6 +97,7 @@ export function renderElement(
currentState,
components = {},
darkMode = false,
placeholder = '',
verticalLine = true
) {
const componentConfig = component.component;
@ -72,6 +142,7 @@ export function renderElement(
}}
component={component}
verticalLine={verticalLine}
placeholder={placeholder}
/>
);
}

View file

@ -4,7 +4,7 @@ import JSONTreeViewer from '@/_ui/JSONTreeViewer';
import _, { isEmpty } from 'lodash';
import { toast } from 'react-hot-toast';
import { getSvgIcon } from '@/_helpers/appUtils';
import Icon from '@/_ui/Icon/solidIcons/index';
import { useGlobalDataSources } from '@/_stores/dataSourcesStore';
import { useDataQueries } from '@/_stores/dataQueriesStore';
import { useCurrentState } from '@/_stores/currentStateStore';
@ -126,9 +126,58 @@ export const LeftSidebarInspector = ({
};
}
});
const exposedVariablesIcon = Object.entries(currentState['components'])
.map(([key, value]) => {
const component = componentDefinitions[value.id]?.component ?? {};
const componentExposedVariables = value;
const iconsList = useMemo(() => [...queryIcons, ...componentIcons], [queryIcons, componentIcons]);
if (!_.isEmpty(component) && component.component === 'TextInput') {
const icons = [];
if (componentExposedVariables.disable) {
icons.push({
iconName: 'disable',
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
className: 'component-icon',
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
isInfoIcon: true,
});
}
if (componentExposedVariables.visibility) {
icons.push({
iconName: 'visibility',
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
className: 'component-icon',
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
isInfoIcon: true,
});
}
return icons;
}
if (!_.isEmpty(component) && component.component === 'Text' && componentExposedVariables?.visibility) {
return [
{
iconName: 'visibility',
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
className: 'component-icon',
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
isInfoIcon: true,
},
];
}
return [];
})
.flat()
.filter((value) => value !== undefined); // Remove undefined values
const iconsList = useMemo(
() => [...queryIcons, ...componentIcons, ...exposedVariablesIcon],
[queryIcons, componentIcons, exposedVariablesIcon]
);
const handleRemoveComponent = (component) => {
removeComponent(component.id);
};

View file

@ -71,23 +71,26 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
</Popover>
}
>
<span>
<span className="parameterItem">
{isEdit ? (
<PillButton name={name} onClick={() => setShowModal(true)} onRemove={onRemove} />
<PillButton
className="parameterItemPillButton"
name={name}
onClick={() => setShowModal(true)}
onRemove={onRemove}
/>
) : (
<ButtonSolid
variant="ghostBlue"
size="sm"
<button
onClick={() => setShowModal((show) => !show)}
className="ms-2"
id="runjs-param-add-btn"
data-cy={`runjs-add-param-button`}
style={{ background: 'none' }}
>
<span className="m-0">
<PlusRectangle fill={'#3E63DD'} width={15} />
<PlusRectangle fill={darkMode ? '#9BA1A6' : '#687076'} width={15} />
</span>
Add
</ButtonSolid>
</button>
)}
</span>
</OverlayTrigger>
@ -97,23 +100,22 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
export const PillButton = ({ name, onClick, onRemove, marginBottom, className, size }) => (
<ButtonGroup
aria-label="Parameter"
className={cx('ms-2 bg-slate3', { 'mb-2': marginBottom, ...(className && { [className]: true }) })}
style={{ borderRadius: '15px' }}
className={cx({ 'mb-2': marginBottom, ...(className && { [className]: true }) })}
style={{ borderRadius: '6px', marginLeft: '6px', height: '24px', background: '#A1A7AE1F' }}
>
<Button
size="sm"
className={cx('bg-transparent color-slate12 runjs-parameter-badge', { 'py-0 px-2': size === 'sm' })}
onClick={onClick}
style={{
borderTopLeftRadius: '15px',
borderBottomLeftRadius: '15px',
borderTopLeftRadius: '6px',
borderBottomLeftRadius: '6px',
textTransform: 'none',
padding: '0.8rem',
fontWeight: 500,
...(!onRemove && { borderRadius: '15px' }),
...(!onRemove && { borderRadius: '6px' }),
}}
>
<span data-cy={`query-param-${String(name).toLowerCase()}`} className="text-truncate">
<span data-cy={`query-param-${String(name).toLowerCase()}`} className="text-truncate query-param-text">
{name}
</span>
</Button>
@ -122,15 +124,16 @@ export const PillButton = ({ name, onClick, onRemove, marginBottom, className, s
data-cy={`query-param-${String(name).toLowerCase()}-remove-button`}
onClick={onRemove}
size="sm"
className={cx('bg-transparent color-slate12', { 'p-0 pe-1': size === 'sm' })}
className={cx('bg-transparent color-slate12', { 'p-0': size === 'sm' })}
style={{
borderTopRightRadius: '15px',
borderBottomRightRadius: '15px',
paddingLeft: 0,
paddingRight: '0.75rem',
borderTopRightRadius: '6px',
borderBottomRightRadius: '6px',
paddingRight: '6px',
paddingLeft: '0px',
height: '24px',
}}
>
<Remove fill="var(--slate12)" />
<Remove fill="#6A727CCC" height={20} width={20} />
</Button>
)}
</ButtonGroup>

View file

@ -56,21 +56,29 @@ const ParameterForm = ({
return (
<>
<Popover.Header className={darkMode && 'dark-theme'} style={{ fontSize: '12px' }}>
{isEdit ? 'UPDATE PARAMETER' : 'ADD PARAMETER'}
<Popover.Header className={`${darkMode && 'dark-theme'}`}>
<p className="tj-text-xsm " style={{ fontWeight: '600' }}>
{isEdit ? 'UPDATE PARAMETER' : 'ADD PARAMETER'}
</p>
</Popover.Header>
<Popover.Body className={darkMode && 'dark-theme dark-theme'} key={'1'} bsPrefix="popover-body">
<Popover.Body
className={darkMode && 'dark-theme dark-theme'}
key={'1'}
bsPrefix="popover-body"
style={{ padding: '16px 16px 32px 16px', overflowY: 'auto' }}
>
<Form
className="container p-0 tj-app-input"
onSubmit={handleSubmit}
style={{ paddingRight: '25px !important' }}
>
<Form.Group as={Row} className="mb-2 pr-1">
<Form.Group as={Col} style={{ display: 'flex' }} className="mb-2 pr-1">
<Form.Label column htmlFor="paramName">
Name
</Form.Label>
<Col sm="12">
<Col>
<Form.Control
style={{ height: '32px', width: '177px' }}
type="text"
aria-describedby="paramName"
onChange={(event) => setName(event.target.value)}
@ -79,9 +87,9 @@ const ParameterForm = ({
{name && error && <div className="invalid-feedback d-block">{error}</div>}
</Col>
</Form.Group>
<Form.Group as={Row}>
<Form.Label column htmlFor="defaultValue">
Default Value
<Form.Group as={Col} style={{ display: 'flex' }}>
<Form.Label column htmlFor="defaultValue" style={{ marginRight: '0px' }}>
Value
<span className="ms-1">
<OverlayTrigger
placement="bottom"
@ -98,15 +106,15 @@ const ParameterForm = ({
</OverlayTrigger>
</span>
</Form.Label>
<Col sm="12">
<Col style={{ padding: '0px', width: '177px' }}>
<div className="d-flex">
<div className="w-100">
<div className="" style={{ height: '32px', width: '177px' }}>
<CodeHinter
onChange={(value) => setDefaultValue(value)}
theme={darkMode ? 'monokai' : 'default'}
currentState={emptyObj.current}
usePortalEditor={false}
height={36}
style={{ height: '32px', width: '177px', marginBotto: '16px' }}
initialValue={defaultValue}
enablePreview={false}
/>

View file

@ -10,11 +10,11 @@ const ParameterList = ({
handleParameterRemove,
currentState,
darkMode,
containerRef,
}) => {
const [showMore, setShowMore] = useState(false);
const [selectedParameter, setSelectedParameter] = useState();
const [formattedParameters, setFormattedParameters] = useState([]);
const containerRef = useRef(null);
const containerWidth = containerRef.current?.offsetWidth;
useEffect(() => {
@ -22,10 +22,10 @@ const ParameterList = ({
const formattedParams = containerWidth
? parameters.map((param, index) => {
const boxWidth = Math.min((param?.name || '').length * 6 + 63 + 8, 178);
totalWidth += boxWidth;
totalWidth = Math.min(totalWidth + boxWidth, containerWidth);
return {
...param,
isVisible: totalWidth <= containerWidth - 57 - 125 - 85,
isVisible: totalWidth < containerWidth - 178,
index,
};
})
@ -54,8 +54,8 @@ const ParameterList = ({
}, [showMore]);
return (
<div className="card-header" ref={containerRef}>
Parameters
<div className="card-header">
<p style={{ marginRight: '4px', margin: '0px' }}>Parameters</p>
{formattedParameters
.filter((param) => param.isVisible)
.map((parameter) => {
@ -104,7 +104,7 @@ const ParameterList = ({
<Popover.Body
key={'1'}
bsPrefix="popover-body"
className={`ps-1 pe-1 me-2 py-2 query-manager`}
className={`ps-1 pe-1 py-2 query-manager`}
style={{ maxWidth: '500px' }}
>
{formattedParameters
@ -129,8 +129,9 @@ const ParameterList = ({
<span>
{formattedParameters.some((param) => !param.isVisible) && (
<PillButton
name={`${formattedParameters.reduce((count, param) => count + (!param.isVisible ? 1 : 0), 0)} More`}
name={`+${formattedParameters.reduce((count, param) => count + (!param.isVisible ? 1 : 0), 0)}`}
onClick={() => setShowMore(true)}
className="more-parameters-button"
/>
)}
</span>

View file

@ -269,7 +269,6 @@ export const QueryManagerBody = ({
}`}
>
{selectedQuery?.data_source_id && selectedDataSource !== null ? renderChangeDataSource() : null}
{selectedDataSource === null || !selectedQuery ? renderDataSourcesList() : renderQueryElement()}
{selectedDataSource !== null ? renderQueryOptions() : null}
</div>

View file

@ -20,14 +20,18 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
import { shallow } from 'zustand/shallow';
import { Tooltip } from 'react-tooltip';
import { Button } from 'react-bootstrap';
import { cloneDeep } from 'lodash';
export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef }, ref) => {
import ParameterList from './ParameterList';
export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef, setOptions }, ref) => {
const { renameQuery } = useDataQueriesActions();
const selectedQuery = useSelectedQuery();
const selectedDataSource = useSelectedDataSource();
const [showCreateQuery, setShowCreateQuery] = useShowCreateQuery();
const queryName = selectedQuery?.name ?? '';
const { queries } = useCurrentState((state) => ({ queries: state.queries }), shallow);
const currentState = useCurrentState((state) => ({ queries: state.queries }), shallow);
const { queries } = currentState;
const { isVersionReleased } = useAppVersionStore(
(state) => ({
isVersionReleased: state.isVersionReleased,
@ -36,6 +40,8 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef },
shallow
);
const { updateDataQuery } = useDataQueriesActions();
useEffect(() => {
if (selectedQuery?.name) {
setShowCreateQuery(false);
@ -80,8 +86,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef },
kind: selectedDataSource.kind,
name: queryName,
};
const hasParamSupport = selectedQuery?.options?.hasParamSupport;
previewQuery(editorRef, query, false, undefined, hasParamSupport)
previewQuery(editorRef, query, false, undefined)
.then(() => {
ref.current.scrollIntoView();
})
@ -100,11 +105,7 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef },
})}
>
<button
onClick={() =>
runQuery(editorRef, selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {
shouldSetPreviewData: true,
})
}
onClick={() => runQuery(editorRef, selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)}
className={`border-0 default-secondary-button float-right1 ${buttonLoadingState(isLoading)}`}
data-cy="query-run-button"
disabled={isInDraft}
@ -129,14 +130,54 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef },
const renderButtons = () => {
if (selectedQuery === null || showCreateQuery) return;
const { isLoading } = queries[selectedQuery?.name] ?? false;
return (
<>
<PreviewButton onClick={previewButtonOnClick} buttonLoadingState={buttonLoadingState} />
<PreviewButton
onClick={previewButtonOnClick}
buttonLoadingState={buttonLoadingState}
isRunButtonLoading={isLoading}
/>
{renderRunButton()}
</>
);
};
const optionsChanged = (newOptions) => {
setOptions(newOptions);
updateDataQuery(cloneDeep(newOptions));
};
const handleAddParameter = (newParameter) => {
const prevOptions = { ...options };
//check if paramname already used
if (!prevOptions?.parameters?.some((param) => param.name === newParameter.name)) {
const newOptions = {
...prevOptions,
parameters: [...(prevOptions?.parameters ?? []), newParameter],
};
optionsChanged(newOptions);
}
};
const handleParameterChange = (index, updatedParameter) => {
const prevOptions = { ...options };
//check if paramname already used
if (!prevOptions?.parameters?.some((param, idx) => param.name === updatedParameter.name && index !== idx)) {
const updatedParameters = [...prevOptions.parameters];
updatedParameters[index] = updatedParameter;
optionsChanged({ ...prevOptions, parameters: updatedParameters });
}
};
const handleParameterRemove = (index) => {
const prevOptions = { ...options };
const updatedParameters = prevOptions.parameters.filter((param, i) => index !== i);
optionsChanged({ ...prevOptions, parameters: updatedParameters });
};
const paramListContainerRef = useRef(null);
return (
<div className="row header">
<div className="col font-weight-500">
@ -148,20 +189,37 @@ export const QueryManagerHeader = forwardRef(({ darkMode, options, editorRef },
isDiabled={isVersionReleased}
/>
)}
<div
className="query-parameters-list col w-100 d-flex justify-content-center font-weight-500"
ref={paramListContainerRef}
>
{selectedQuery && (
<ParameterList
parameters={options.parameters}
handleAddParameter={handleAddParameter}
handleParameterChange={handleParameterChange}
handleParameterRemove={handleParameterRemove}
currentState={currentState}
darkMode={darkMode}
containerRef={paramListContainerRef}
/>
)}
</div>
</div>
<div className="query-header-buttons me-3">{renderButtons()}</div>
</div>
);
});
const PreviewButton = ({ buttonLoadingState, onClick }) => {
const PreviewButton = ({ buttonLoadingState, onClick, isRunButtonLoading }) => {
const previewLoading = usePreviewLoading();
const { t } = useTranslation();
return (
<button
onClick={onClick}
className={`default-tertiary-button float-right1 ${buttonLoadingState(previewLoading)}`}
className={`default-tertiary-button float-right1 ${buttonLoadingState(previewLoading && !isRunButtonLoading)}`}
data-cy={'query-preview-button'}
>
<span className="query-preview-svg d-flex align-items-center query-icon-wrapper">

View file

@ -3,7 +3,6 @@ import { CodeHinter } from '@/Editor/CodeBuilder/CodeHinter';
import { defaults } from 'lodash';
import { Card } from 'react-bootstrap';
import { useCurrentState } from '@/_stores/currentStateStore';
import ParameterList from './ParameterList';
const Runjs = (props) => {
const currentState = useCurrentState();
@ -18,50 +17,12 @@ const Runjs = (props) => {
});
}, [currentState?.components, options?.parameters]);
const handleAddParameter = (newParameter) => {
const prevOptions = { ...options };
//check if paramname already used
if (!prevOptions?.parameters?.some((param) => param.name === newParameter.name)) {
props.optionsChanged({
...prevOptions,
parameters: [...prevOptions.parameters, newParameter],
});
}
};
useEffect(() => {
setOptions(props.options);
}, [props.options]);
const handleParameterChange = (index, updatedParameter) => {
const prevOptions = { ...options };
//check if paramname already used
if (!prevOptions?.parameters?.some((param, idx) => param.name === updatedParameter.name && index !== idx)) {
const updatedParameters = [...prevOptions.parameters];
updatedParameters[index] = updatedParameter;
props.optionsChanged({ ...prevOptions, parameters: updatedParameters });
}
};
const handleParameterRemove = (index) => {
const prevOptions = { ...options };
const updatedParameters = prevOptions.parameters.filter((param, i) => index !== i);
props.optionsChanged({ ...prevOptions, parameters: updatedParameters });
};
return (
<Card className="runjs-editor mb-3">
{(options.hasParamSupport || props.mode === 'create') && (
<ParameterList
parameters={options.parameters}
handleAddParameter={handleAddParameter}
handleParameterChange={handleParameterChange}
handleParameterRemove={handleParameterRemove}
currentState={props.currentState}
darkMode={props.darkMode}
/>
)}
<CodeHinter
initialValue={props.options.code}
mode="javascript"

View file

@ -2,7 +2,7 @@ export function changeOption(_ref, option, value) {
_ref.setState(
{
options: {
..._ref.state.options,
..._ref.props.options,
[option]: value,
},
},

View file

@ -6,6 +6,8 @@ import { runQuery } from '@/_helpers/appUtils';
import { defaultSources } from './constants';
import { useDataSources, useGlobalDataSources, useLoadingDataSources } from '@/_stores/dataSourcesStore';
import { useQueryToBeRun, useSelectedQuery, useQueryPanelActions } from '@/_stores/queryPanelStore';
import { CodeHinterContext } from '@/Editor/CodeBuilder/CodeHinterContext';
import { resolveReferences } from '@/_helpers/utils';
const QueryManager = ({ mode, appId, darkMode, apps, allComponents, appDefinition, editorRef }) => {
const loadingDataSources = useLoadingDataSources();
@ -14,7 +16,6 @@ const QueryManager = ({ mode, appId, darkMode, apps, allComponents, appDefinitio
const queryToBeRun = useQueryToBeRun();
const selectedQuery = useSelectedQuery();
const { setSelectedDataSource, setQueryToBeRun } = useQueryPanelActions();
const [options, setOptions] = useState({});
useEffect(() => {
@ -53,16 +54,34 @@ const QueryManager = ({ mode, appId, darkMode, apps, allComponents, appDefinitio
'd-none': loadingDataSources,
})}
>
<QueryManagerHeader darkMode={darkMode} options={options} editorRef={editorRef} appId={appId} />
<QueryManagerBody
<QueryManagerHeader
darkMode={darkMode}
options={options}
allComponents={allComponents}
apps={apps}
editorRef={editorRef}
appId={appId}
appDefinition={appDefinition}
setOptions={setOptions}
/>
<CodeHinterContext.Provider
value={{
parameters: selectedQuery?.options?.parameters?.reduce(
(parameters, parameter) => ({
...parameters,
[parameter.name]: resolveReferences(parameter.defaultValue, {}, undefined),
}),
{}
),
}}
>
<QueryManagerBody
darkMode={darkMode}
options={options}
allComponents={allComponents}
apps={apps}
appId={appId}
appDefinition={appDefinition}
setOptions={setOptions}
/>
</CodeHinterContext.Provider>
</div>
);
};

View file

@ -79,7 +79,6 @@ export const schemaUnavailableOptions = {
},
runjs: {
code: '',
hasParamSupport: true,
parameters: [],
},
runpy: {},

View file

@ -11,7 +11,7 @@ import useShowPopover from '@/_hooks/useShowPopover';
import DataSourceIcon from '../QueryManager/Components/DataSourceIcon';
import { staticDataSources } from '../QueryManager/constants';
import { Tooltip } from 'react-tooltip';
import { PillButton } from '../QueryManager/QueryEditors/Runjs/ParameterDetails';
import { PillButton } from '../QueryManager/Components/ParameterDetails';
const FilterandSortPopup = ({ darkMode, selectedDataSources, onFilterDatasourcesChange, clearSelectedDataSources }) => {
const [showMenu, setShowMenu] = useShowPopover(false, '#query-sort-filter-popover', '#query-sort-filter-popover-btn');

View file

@ -5,12 +5,12 @@ import {
orgEnvironmentConstantService,
dataqueryService,
appService,
appEnvironmentService,
} from '@/_services';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { Container } from './Container';
import { Confirm } from './Viewer/Confirm';
import { ViewerNavigation } from './Viewer/ViewerNavigation';
import {
onComponentOptionChanged,
onComponentOptionsChanged,
@ -38,7 +38,13 @@ import { shallow } from 'zustand/shallow';
import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore';
import { getPreviewQueryParams, redirectToErrorPage } from '@/_helpers/routes';
import { ERROR_TYPES } from '@/_helpers/constants';
import { camelizeKeys } from 'humps';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import TooljetLogoIcon from '@/_ui/Icon/solidIcons/TooljetLogoIcon';
import TooljetLogoText from '@/_ui/Icon/solidIcons/TooljetLogoText';
import ViewerSidebarNavigation from './Viewer/ViewerSidebarNavigation';
import MobileHeader from './Viewer/MobileHeader';
import DesktopHeader from './Viewer/DesktopHeader';
import './Viewer/viewer.scss';
class ViewerComponent extends React.Component {
constructor(props) {
@ -75,13 +81,13 @@ class ViewerComponent extends React.Component {
setStateForApp = (data, byAppSlug = false) => {
const appDefData = buildAppDefinition(data);
if (byAppSlug) {
appDefData.globalSettings = data.globalSettings;
appDefData.homePageId = data.homePageId;
appDefData.showViewerNavigation = data.showViewerNavigation;
}
useAppVersionStore.getState().actions.updateEditingVersion(data.editing_version);
useAppVersionStore.getState().actions.updateReleasedVersionId(data.currentVersionId);
this.setState({
app: data,
isLoading: false,
@ -92,7 +98,7 @@ class ViewerComponent extends React.Component {
};
setStateForContainer = async (data, appVersionId) => {
const appDefData = buildAppDefinition(data);
const appDefData = this.state.appDefinition;
const currentUser = this.state.currentUser;
let userVars = {};
@ -199,7 +205,6 @@ class ViewerComponent extends React.Component {
computeComponentState(components).then(async () => {
this.setState({ initialComputationOfStateDone: true, defaultComponentStateComputed: true });
console.log('Default component state computed and set');
this.runQueries(dataQueries);
const currentPageEvents = this.state.events.filter(
@ -233,16 +238,11 @@ class ViewerComponent extends React.Component {
variablesResult = constants;
}
console.log('--org constant 2.0', { variablesResult });
if (variablesResult && Array.isArray(variablesResult)) {
variablesResult.map((constant) => {
const constantValue = constant.values.find((value) => value.environmentName === 'production')['value'];
orgConstants[constant.name] = constantValue;
});
// console.log('--org constant 2.0', { orgConstants });
return {
constants: orgConstants,
};
@ -271,6 +271,11 @@ class ViewerComponent extends React.Component {
return variables;
};
fetchAppVersions = async (appId) => {
const appVersions = await appEnvironmentService.getVersionsByEnvironment(appId);
useAppVersionStore.getState().actions.setAppVersions(appVersions.appVersions);
};
loadApplicationBySlug = (slug, authentication_failed = false) => {
appService
.fetchAppBySlug(slug)
@ -303,8 +308,8 @@ class ViewerComponent extends React.Component {
});
};
loadApplicationByVersion = (appId, versionId) => {
appService
loadApplicationByVersion = async (appId, versionId) => {
await appService
.fetchAppByVersion(appId, versionId)
.then((data) => {
this.setStateForApp(data);
@ -317,6 +322,13 @@ class ViewerComponent extends React.Component {
});
};
setAppDefinitionFromVersion = (data) => {
this.setState({
isLoading: true,
});
this.loadApplicationByVersion(this.props.id, data.editing_version.id);
};
updateQueryConfirmationList = (queryConfirmationList) =>
useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList);
@ -348,7 +360,7 @@ class ViewerComponent extends React.Component {
userVars,
versionId,
});
this.fetchAppVersions(appId);
versionId ? this.loadApplicationByVersion(appId, versionId) : this.loadApplicationBySlug(slug);
} else if (currentSession?.authentication_failed) {
this.loadApplicationBySlug(slug, true);
@ -386,6 +398,11 @@ class ViewerComponent extends React.Component {
this.setState({ isLoading: true });
this.loadApplicationBySlug(this.props.params.slug);
}
if (prevProps.currentLayout !== this.props.currentLayout) {
if (this.props.id && useAppVersionStore.getState()?.editingVersion?.id) {
this.loadApplicationByVersion(this.props.id, useAppVersionStore.getState().editingVersion.id);
}
}
if (this.state.initialComputationOfStateDone) this.handlePageSwitchingBasedOnURLparam();
if (this.state.homepage !== prevState.homepage && !this.state.isLoading) {
@ -544,7 +561,6 @@ class ViewerComponent extends React.Component {
componentWillUnmount() {
this.subscription && this.subscription.unsubscribe();
}
render() {
const {
appDefinition,
@ -555,12 +571,15 @@ class ViewerComponent extends React.Component {
dataQueries,
canvasWidth,
} = this.state;
const currentCanvasWidth = canvasWidth;
const queryConfirmationList = this.props?.queryConfirmationList ?? [];
const canvasMaxWidth = this.computeCanvasMaxWidth();
const pages =
Object.entries(_.cloneDeep(appDefinition)?.pages)
.map(([id, page]) => ({ id, ...page }))
.sort((a, b) => a.index - b.index) || [];
const isMobilePreviewMode = this.props.versionId && this.props.currentLayout === 'mobile';
if (this.state.app?.isLoading) {
return (
<div className="tooljet-logo-loader">
@ -574,116 +593,173 @@ class ViewerComponent extends React.Component {
</div>
</div>
);
} else {
if (this.state.app?.is_maintenance_on) {
return (
<div className="maintenance_container">
<div className="card">
<div className="card-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h3>{this.props.t('viewer', 'Sorry!. This app is under maintenance')}</h3>
</div>
} else if (this.state.app?.is_maintenance_on) {
return (
<div className="maintenance_container">
<div className="card">
<div className="card-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h3>{this.props.t('viewer', 'Sorry!. This app is under maintenance')}</h3>
</div>
</div>
);
} else {
return (
<div className="viewer wrapper">
<Confirm
show={queryConfirmationList.length > 0}
message={'Do you want to run this query?'}
onConfirm={(queryConfirmationData) =>
onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationData, true, 'view')
}
onCancel={() => onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationList[0], false, 'view')}
queryConfirmationData={queryConfirmationList[0]}
key={queryConfirmationList[0]?.queryName}
/>
<DndProvider backend={HTML5Backend}>
<ViewerNavigation.Header
</div>
);
} else {
return (
<div className={`viewer wrapper ${this.props.currentLayout === 'mobile' ? 'mobile-layout' : ''}`}>
<Confirm
show={queryConfirmationList.length > 0}
message={'Do you want to run this query?'}
onConfirm={(queryConfirmationData) =>
onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationData, true, 'view')
}
onCancel={() => onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationList[0], false, 'view')}
queryConfirmationData={queryConfirmationList[0]}
key={queryConfirmationList[0]?.queryName}
/>
<DndProvider backend={HTML5Backend}>
{this.props.currentLayout !== 'mobile' && (
<DesktopHeader
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
appName={this.state.app?.name ?? null}
changeDarkMode={this.changeDarkMode}
darkMode={this.props.darkMode}
pages={Object.entries(this.state.appDefinition?.pages) ?? []}
pages={pages}
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
switchPage={this.switchPage}
setAppDefinitionFromVersion={this.setAppDefinitionFromVersion}
showViewerNavigation={appDefinition?.showViewerNavigation}
/>
<div className="sub-section">
<div className="main">
<div
className="canvas-container align-items-center"
style={{
background: this.computeCanvasBackgroundColor() || (!this.props.darkMode ? '#EBEBEF' : '#2E3035'),
}}
>
<div className="areas d-flex flex-rows">
{appDefinition?.showViewerNavigation && (
<ViewerNavigation
isMobileDevice={this.props.currentLayout === 'mobile'}
canvasBackgroundColor={this.computeCanvasBackgroundColor()}
pages={Object.entries(this.state.appDefinition?.pages) ?? []}
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
switchPage={this.switchPage}
darkMode={this.props.darkMode}
/>
)}
<div className="flex-grow-1 d-flex justify-content-center">
<div
className="canvas-area"
style={{
width: currentCanvasWidth,
maxWidth: canvasMaxWidth,
backgroundColor: this.computeCanvasBackgroundColor(),
margin: 0,
padding: 0,
}}
>
{defaultComponentStateComputed && (
<>
{isLoading ? (
<div className="mx-auto mt-5 w-50 p-5">
<center>
<div className="spinner-border text-azure" role="status"></div>
</center>
</div>
) : (
<Container
appDefinition={appDefinition}
appDefinitionChanged={() => false} // function not relevant in viewer
snapToGrid={true}
appLoading={isLoading}
darkMode={this.props.darkMode}
onEvent={this.handleEvent}
mode="view"
deviceWindowWidth={deviceWindowWidth}
selectedComponent={this.state.selectedComponent}
onComponentClick={(id, component) => {
this.setState({
selectedComponent: { id, component },
});
onComponentClick(this, id, component, 'view');
}}
onComponentOptionChanged={(component, optionName, value) => {
return onComponentOptionChanged(component, optionName, value);
}}
onComponentOptionsChanged={onComponentOptionsChanged}
canvasWidth={this.getCanvasWidth()}
dataQueries={dataQueries}
currentPageId={this.state.currentPageId}
/>
)}
</>
)}
</div>
)}
{/* Render following mobile header only when its in preview mode and not in launched app */}
{this.props.currentLayout === 'mobile' && !isMobilePreviewMode && (
<MobileHeader
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
appName={this.state.app?.name ?? null}
changeDarkMode={this.changeDarkMode}
darkMode={this.props.darkMode}
pages={pages}
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
switchPage={this.switchPage}
setAppDefinitionFromVersion={this.setAppDefinitionFromVersion}
showViewerNavigation={appDefinition?.showViewerNavigation}
/>
)}
<div className="sub-section">
<div className="main">
<div
className="canvas-container align-items-center"
style={{
background: this.computeCanvasBackgroundColor() || (!this.props.darkMode ? '#EBEBEF' : '#2E3035'),
}}
>
<div className="areas d-flex flex-rows">
{appDefinition?.showViewerNavigation && (
<ViewerSidebarNavigation
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
isMobileDevice={this.props.currentLayout === 'mobile'}
canvasBackgroundColor={this.computeCanvasBackgroundColor()}
pages={pages}
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
switchPage={this.switchPage}
darkMode={this.props.darkMode}
/>
)}
<div
className="flex-grow-1 d-flex justify-content-center"
style={{
backgroundColor: isMobilePreviewMode ? '#ACB2B9' : 'unset',
marginLeft:
appDefinition?.showViewerNavigation && this.props.currentLayout !== 'mobile'
? '200px'
: 'auto',
}}
>
<div
className="canvas-area"
style={{
width: isMobilePreviewMode ? '450px' : currentCanvasWidth,
maxWidth: isMobilePreviewMode ? '450px' : canvasMaxWidth,
backgroundColor: this.computeCanvasBackgroundColor(),
margin: 0,
padding: 0,
}}
>
{this.props.currentLayout === 'mobile' && isMobilePreviewMode && (
<MobileHeader
showHeader={!appDefinition.globalSettings?.hideHeader && isAppLoaded}
appName={this.state.app?.name ?? null}
changeDarkMode={this.changeDarkMode}
darkMode={this.props.darkMode}
pages={pages}
currentPageId={this.state?.currentPageId ?? this.state.appDefinition?.homePageId}
switchPage={this.switchPage}
setAppDefinitionFromVersion={this.setAppDefinitionFromVersion}
showViewerNavigation={appDefinition?.showViewerNavigation}
/>
)}
{defaultComponentStateComputed && (
<>
{isLoading ? (
<div className="mx-auto mt-5 w-50 p-5">
<center>
<div className="spinner-border text-azure" role="status"></div>
</center>
</div>
) : (
<Container
appDefinition={appDefinition}
appDefinitionChanged={() => false} // function not relevant in viewer
snapToGrid={true}
appLoading={isLoading}
darkMode={this.props.darkMode}
onEvent={this.handleEvent}
mode="view"
deviceWindowWidth={isMobilePreviewMode ? '450px' : deviceWindowWidth}
selectedComponent={this.state.selectedComponent}
onComponentClick={(id, component) => {
this.setState({
selectedComponent: { id, component },
});
onComponentClick(this, id, component, 'view');
}}
onComponentOptionChanged={(component, optionName, value) => {
return onComponentOptionChanged(component, optionName, value);
}}
onComponentOptionsChanged={onComponentOptionsChanged}
canvasWidth={this.getCanvasWidth()}
dataQueries={dataQueries}
currentPageId={this.state.currentPageId}
/>
)}
</>
)}
</div>
<div
className="powered-with-tj"
onClick={() => {
const url = `https://tooljet.com/?utm_source=powered_by_banner&utm_medium=${
useAppDataStore.getState()?.metadata?.instance_id
}&utm_campaign=self_hosted`;
window.open(url, '_blank');
}}
>
Built with
<span className={'powered-with-tj-icon'}>
<TooljetLogoIcon />
</span>
<TooljetLogoText fill={this.props.darkMode ? '#ECEDEE' : '#11181C'} />
</div>
{/* Following div is a hack to prevent showing mobile drawer navigation coming from left*/}
{isMobilePreviewMode && <div className="hide-drawer-transition" style={{ right: 0 }}></div>}
{isMobilePreviewMode && <div className="hide-drawer-transition" style={{ left: 0 }}></div>}
</div>
</div>
</div>
</div>
</DndProvider>
</div>
);
}
</div>
</DndProvider>
</div>
);
}
}
}
@ -696,7 +772,6 @@ const withStore = (Component) => (props) => {
}),
shallow
);
const { updateState } = useAppDataActions();
return (
<Component

View file

@ -0,0 +1,86 @@
import React from 'react';
import _, { isEmpty } from 'lodash';
// eslint-disable-next-line import/no-unresolved
import LogoIcon from '@assets/images/rocket.svg';
import { Link } from 'react-router-dom';
import { DarkModeToggle } from '@/_components/DarkModeToggle';
import Header from './Header';
import { shallow } from 'zustand/shallow';
import { redirectToDashboard } from '@/_helpers/routes';
import classNames from 'classnames';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import PreviewSettings from './PreviewSettings';
const DesktopHeader = ({ showHeader, appName, changeDarkMode, darkMode, setAppDefinitionFromVersion }) => {
const { isVersionReleased, editingVersion } = useAppVersionStore(
(state) => ({
isVersionReleased: state.isVersionReleased,
editingVersion: state?.editingVersion,
}),
shallow
);
const _renderAppNameAndLogo = () => (
<div
className={classNames('d-flex', 'align-items-center')}
style={{ visibility: showHeader || isVersionReleased ? 'visible' : 'hidden' }}
>
<h1 className="navbar-brand d-none-navbar-horizontal pe-0">
<Link
data-cy="viewer-page-logo"
onClick={() => {
redirectToDashboard();
}}
>
<LogoIcon />
</Link>
</h1>
<div className="navbar-seperator" style={{ margin: '0px 1.375rem' }}></div>
{appName && (
<div className="d-flex align-items-center app-title">
<span>{appName}</span>
</div>
)}
</div>
);
if (!showHeader) {
return (
<>
{!isVersionReleased && !isEmpty(editingVersion) && (
<PreviewSettings
isMobileLayout={false}
showHeader={showHeader}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
darkMode={darkMode}
/>
)}
<span className="released-version-no-header-dark-mode-icon">
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
</span>
</>
);
}
return (
<Header
styles={{
height: '46px',
}}
>
{_renderAppNameAndLogo()}
{!isVersionReleased && !isEmpty(editingVersion) && (
<PreviewSettings
isMobileLayout={false}
showHeader={showHeader}
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
darkMode={darkMode}
/>
)}
<div className="d-flex align-items-center">
<DarkModeToggle switchDarkMode={changeDarkMode} darkMode={darkMode} />
</div>
</Header>
);
};
export default DesktopHeader;

View file

@ -1,14 +1,16 @@
import React from 'react';
import classNames from 'classnames';
const Header = ({ children, className }) => {
const Header = ({ children, className, styles = {}, showNavbarClass = true }) => {
return (
<div
style={{
minHeight: 45,
minHeight: '47px',
...styles,
}}
className={`header ${className}`}
>
<header className="navbar navbar-expand-md d-print-none">
<header className={classNames({ 'navbar navbar-expand-md': showNavbarClass })}>
<div className="container-xl header-container position-relative">{children}</div>
</header>
</div>

Some files were not shown because too many files have changed in this diff Show more