mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-05 22:38:48 +00:00
Merge pull request #8124 from ToolJet/merge/appDef-to-develop
Merge main back to develop (v2.24.0)
This commit is contained in:
commit
019ac15a22
117 changed files with 8568 additions and 3595 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
2.23.0
|
||||
2.24.0
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ module.exports = defineConfig({
|
|||
trashAssetsBeforeRuns: true,
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
setupNodeEvents (on, config) {
|
||||
on("task", {
|
||||
readPdf(pathToPdf) {
|
||||
readPdf (pathToPdf) {
|
||||
return new Promise((resolve) => {
|
||||
const pdfPath = path.resolve(pathToPdf);
|
||||
let dataBuffer = fs.readFileSync(pdfPath);
|
||||
|
|
@ -33,7 +33,7 @@ module.exports = defineConfig({
|
|||
});
|
||||
|
||||
on("task", {
|
||||
readXlsx(filePath) {
|
||||
readXlsx (filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let dataBuffer = fs.readFileSync(filePath);
|
||||
|
|
@ -48,7 +48,7 @@ module.exports = defineConfig({
|
|||
});
|
||||
|
||||
on("task", {
|
||||
deleteFolder(folderName) {
|
||||
deleteFolder (folderName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fs.existsSync(folderName)) {
|
||||
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
|
||||
|
|
@ -66,7 +66,7 @@ module.exports = defineConfig({
|
|||
});
|
||||
|
||||
on("task", {
|
||||
updateId({ dbconfig, sql }) {
|
||||
updateId ({ dbconfig, sql }) {
|
||||
const client = new pg.Pool(dbconfig);
|
||||
return client.query(sql);
|
||||
},
|
||||
|
|
@ -80,7 +80,7 @@ module.exports = defineConfig({
|
|||
specPattern: "cypress/e2e/**/*.cy.js",
|
||||
downloadsFolder: "cypress/downloads",
|
||||
numTestsKeptInMemory: 0,
|
||||
redirectionLimit: 7,
|
||||
redirectionLimit: 10,
|
||||
experimentalRunAllSpecs: true,
|
||||
trashAssetsBeforeRuns: true,
|
||||
experimentalMemoryManagement: true,
|
||||
|
|
|
|||
|
|
@ -163,3 +163,21 @@ Cypress.Commands.add("apiCreateWorkspace", (workspaceName, workspaceSlug) => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("logoutApi", () => {
|
||||
cy.getCookie("tj_auth_token").then((cookie) => {
|
||||
cy.request(
|
||||
{
|
||||
method: "GET",
|
||||
url: "http://localhost:3000/api/logout",
|
||||
headers: {
|
||||
"Tj-Workspace-Id": Cypress.env("workspaceId"),
|
||||
Cookie: `tj_auth_token=${cookie.value}`,
|
||||
},
|
||||
},
|
||||
{ log: false }
|
||||
).then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -108,18 +108,23 @@ Cypress.Commands.add(
|
|||
.find("pre.CodeMirror-line")
|
||||
.invoke("text")
|
||||
.then((text) => {
|
||||
cy.wrap(subject).type(createBackspaceText(text), { delay: 0 }),
|
||||
cy
|
||||
.wrap(subject)
|
||||
.last()
|
||||
.click()
|
||||
.type(createBackspaceText(text), { delay: 0 }),
|
||||
{
|
||||
delay: 0,
|
||||
};
|
||||
});
|
||||
if (!Array.isArray(value)) {
|
||||
cy.wrap(subject).type(value, {
|
||||
cy.wrap(subject).last().type(value, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 0,
|
||||
});
|
||||
} else {
|
||||
cy.wrap(subject)
|
||||
.last()
|
||||
.type(value[1], {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 0,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export const workspaceConstantsText = {
|
|||
"To resolve a Workspace constant use {{constants.access_token}}",
|
||||
emptyStateHeader: "No Workspace constants yet",
|
||||
emptyStateText:
|
||||
"Use Workspace constants seamlessly in both the app builder and global data source connections across ToolJet.",
|
||||
"Use workspace constants seamlessly in both the app builder and data source connections across ToolJet.",
|
||||
addNewConstantButton: "Create new constant",
|
||||
addConstatntText: "Add new constant in production ",
|
||||
constantCreatedToast: "Constant has been created",
|
||||
|
|
|
|||
39
cypress-tests/cypress/e2e/ce/appSlug.cy.js
Normal file
39
cypress-tests/cypress/e2e/ce/appSlug.cy.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import { fake } from "Fixtures/fake";
|
||||
import { logout, navigateToAppEditor, verifyTooltip, releaseApp } from "Support/utils/common";
|
||||
import { commonText } from "Texts/common";
|
||||
import { addNewUserMW } from "Support/utils/userPermissions";
|
||||
import { userSignUp } from "Support/utils/onboarding";
|
||||
|
||||
describe("App share functionality", () => {
|
||||
const data = {};
|
||||
data.appName = `${fake.companyName} App`;
|
||||
data.firstName = fake.firstName;
|
||||
data.lastName = fake.lastName.replaceAll("[^A-Za-z]", "");
|
||||
data.email = fake.email.toLowerCase();
|
||||
const slug = data.appName.toLowerCase().replace(/\s+/g, "-");
|
||||
const firstUserEmail = data.email
|
||||
const envVar = Cypress.env("environment");
|
||||
// beforeEach(() => {
|
||||
// cy.appUILogin();
|
||||
// });
|
||||
before(() => {
|
||||
cy.apiLogin();
|
||||
cy.apiCreateApp(data.appName);
|
||||
// cy.visit('/')
|
||||
// logout();
|
||||
})
|
||||
|
||||
it("", () => {
|
||||
cy.openApp(data.appName);
|
||||
|
||||
cy.get('[data-cy="left-sidebar-settings-button"]').click();
|
||||
cy.get('[data-cy="app-slug-label"]').verifyVisibleElement("have.text", "Unique app slug");
|
||||
cy.get('[data-cy="app-slug-input-field"]').verifyVisibleElement("have.value", Cypress.env("appId"));
|
||||
cy.get('[data-cy="app-slug-info-label"]').verifyVisibleElement("have.text", "URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens");
|
||||
cy.get('[data-cy="app-link-label"]').verifyVisibleElement("have.text", "App link");
|
||||
cy.get('[data-cy="app-link-field"]').verifyVisibleElement("have.text", `http://localhost:8082/my-workspace/apps/${Cypress.env("appId")}`)
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
|
|
@ -37,12 +37,18 @@ describe("App Version Functionality", () => {
|
|||
let currentVersion = "";
|
||||
let newVersion = [];
|
||||
let versionFrom = "";
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
before(() => {
|
||||
cy.apiLogin();
|
||||
cy.apiCreateApp(data.appName);
|
||||
cy.logoutApi();
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.apiLogin();
|
||||
cy.visit('/my-workspace')
|
||||
})
|
||||
|
||||
it("Verify the elements of the version module", () => {
|
||||
cy.createApp(data.appName);
|
||||
navigateToAppEditor(data.appName);
|
||||
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
|
||||
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
|
||||
"have.value",
|
||||
|
|
@ -103,6 +109,6 @@ describe("App Version Functionality", () => {
|
|||
createNewVersion((newVersion = ["v6"]), (versionFrom = "v3"));
|
||||
|
||||
verifyVersionAfterPreview((currentVersion = "v6"));
|
||||
cy.go("back");
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,27 +77,37 @@ describe("Editor- Inspector", () => {
|
|||
cy.get(multipageSelector.sidebarPageButton).click();
|
||||
addNewPage("test_page");
|
||||
|
||||
cy.dragAndDropWidget("Button", 100, 200);
|
||||
cy.dragAndDropWidget("Button", 500, 500);
|
||||
selectEvent("On click", "Switch page");
|
||||
cy.get('[data-cy="switch-page-label-and-input"] > .select-search')
|
||||
.click()
|
||||
.type("home{enter}");
|
||||
cy.get('[data-cy="button-add-query-param"]').click();
|
||||
cy.wait(1000);
|
||||
cy.get('[data-cy="button-add-query-param"]').click();
|
||||
|
||||
addSupportCSAData("query-param-key", "key");
|
||||
addSupportCSAData("query-param-value", "value");
|
||||
cy.get('[data-cy="switch-page-label-and-input"] > .select-search')
|
||||
.click()
|
||||
.type("home{enter}");
|
||||
|
||||
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
|
||||
cy.dragAndDropWidget("Button", 100, 300);
|
||||
cy.dragAndDropWidget("Button", 500, 300);
|
||||
selectEvent("On click", "Set variable");
|
||||
addSupportCSAData("key", "globalVar");
|
||||
addSupportCSAData("variable", "globalVar");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
|
||||
|
||||
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
|
||||
cy.dragAndDropWidget("Button", 100, 400);
|
||||
cy.dragAndDropWidget("Button", 500, 400);
|
||||
selectEvent("On click", "Set page variable");
|
||||
addSupportCSAData("key", "pageVar");
|
||||
addSupportCSAData("variable", "pageVar");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
|
||||
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
|
|
@ -147,7 +157,7 @@ describe("Editor- Inspector", () => {
|
|||
});
|
||||
});
|
||||
|
||||
cy.dragAndDropWidget("Button", 100, 300);
|
||||
cy.dragAndDropWidget("Button", 500, 300);
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
openNode("components");
|
||||
cy.get(`[data-cy="inspector-node-button1"] > .mx-1`).realHover();
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import {
|
|||
describe("Editor- Test Button widget", () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin();
|
||||
cy.apiCreateApp();
|
||||
cy.apiCreateApp(`${fake.companyName}-App`);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 500);
|
||||
});
|
||||
|
|
@ -76,6 +76,8 @@ describe("Editor- Test Button widget", () => {
|
|||
openEditorSidebar(data.widgetName);
|
||||
openAccordion(commonWidgetText.accordionEvents);
|
||||
addDefaultEventHandler(data.alertMessage);
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget(data.widgetName)).click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, data.alertMessage);
|
||||
|
||||
|
|
@ -341,6 +343,7 @@ describe("Editor- Test Button widget", () => {
|
|||
});
|
||||
|
||||
it("Should verify csa", () => {
|
||||
cy.get('[data-tooltip-content="Hide query panel"]').click();
|
||||
// cy.dragAndDropWidget(buttonText.defaultWidgetText);
|
||||
selectEvent("On click", "Show alert");
|
||||
|
||||
|
|
@ -359,7 +362,8 @@ describe("Editor- Test Button widget", () => {
|
|||
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 150);
|
||||
selectEvent("On click", "Control Component");
|
||||
selectCSA("button1", "Disable");
|
||||
cy.get('[data-cy="Value-toggle-button"]').click();
|
||||
cy.get('[data-cy="Value-fx-button"]').realClick();
|
||||
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror(`{{true`);
|
||||
|
||||
cy.get('[data-cy="real-canvas"]').click("topRight", { force: true });
|
||||
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 200);
|
||||
|
|
@ -370,7 +374,9 @@ describe("Editor- Test Button widget", () => {
|
|||
cy.dragAndDropWidget(buttonText.defaultWidgetText, 500, 250);
|
||||
selectEvent("On click", "Control Component");
|
||||
selectCSA("button1", "Loading");
|
||||
cy.get('[data-cy="Value-toggle-button"]').click();
|
||||
cy.wait(500);
|
||||
cy.get('[data-cy="Value-fx-button"]').realClick();
|
||||
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror(`{{true`);
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget("textinput1")).type("testBtn");
|
||||
cy.wait(500);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ describe("Editor- CSA", () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateApp(appName1);
|
||||
cy.openApp();
|
||||
cy.get('[data-tooltip-content="Hide query panel"]').click();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -38,6 +39,8 @@ describe("Editor- CSA", () => {
|
|||
selectEvent("On click", "Control Component");
|
||||
selectCSA("tabs1", "Set current tab");
|
||||
addSupportCSAData("Id", "2");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
cy.get(".nav-link").eq(0).verifyVisibleElement("not.have.class", "active");
|
||||
|
|
@ -69,7 +72,14 @@ describe("Editor- CSA", () => {
|
|||
cy.get('[data-cy="draggable-widget-numberinput1"]')
|
||||
.click()
|
||||
.type(`{selectAll}{backspace}30{enter}`);
|
||||
cy.wait(200);
|
||||
cy.forceClickOnCanvas();
|
||||
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
|
||||
cy.wait(200);
|
||||
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
|
||||
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
"Form submitted successfully"
|
||||
|
|
@ -79,6 +89,8 @@ describe("Editor- CSA", () => {
|
|||
cy.get('[data-cy="draggable-widget-numberinput1"]')
|
||||
.click()
|
||||
.type(`{selectAll}{backspace}20{enter}`);
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
|
||||
cy.get('[data-cy="draggable-widget-numberinput1"]').should(
|
||||
"have.value",
|
||||
|
|
@ -98,7 +110,8 @@ describe("Editor- CSA", () => {
|
|||
selectEvent("On click", "Control Component");
|
||||
selectCSA("dropdown1", "Select option");
|
||||
addSupportCSAData("Select", "{{3");
|
||||
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
cy.get(
|
||||
'[data-cy="draggable-widget-dropdown1"] .css-1qrxvr1-singleValue'
|
||||
|
|
@ -130,6 +143,8 @@ describe("Editor- CSA", () => {
|
|||
cy.get(commonWidgetSelector.draggableWidget("textarea1"))
|
||||
.should("be.visible")
|
||||
.and("have.text", "New Text");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget("button2")).click();
|
||||
cy.get(commonWidgetSelector.draggableWidget("textarea1"))
|
||||
|
|
@ -182,10 +197,13 @@ describe("Editor- CSA", () => {
|
|||
cy.dragAndDropWidget("Button", 500, 300);
|
||||
selectEvent("On click", "Control Component");
|
||||
selectCSA("icon1", "Set Visibility");
|
||||
cy.get('[data-cy="Value-toggle-button"]').click();
|
||||
cy.get('[data-cy="Value-toggle-button"]')
|
||||
.should("be.visible")
|
||||
.and("not.be.checked");
|
||||
cy.get('[data-cy="Value-fx-button"]').click();
|
||||
cy.get('[data-cy="Value-input-field"]').clearAndTypeOnCodeMirror("{{false");
|
||||
// cy.get('[data-cy="Value-toggle-button"]')
|
||||
// .should("be.visible")
|
||||
// .and("not.be.checked");
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
cy.verifyToastMessage(
|
||||
|
|
@ -200,7 +218,7 @@ describe("Editor- CSA", () => {
|
|||
cy.get('[data-cy="draggable-widget-icon1"]').should("not.be.visible");
|
||||
});
|
||||
|
||||
it("Should verify Kanban CSA", () => {
|
||||
it.only("Should verify Kanban CSA", () => {
|
||||
cy.viewport(1400, 1900);
|
||||
|
||||
cy.dragAndDropWidget("Kanban", 50, 400);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
describe("List view widget", () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin();
|
||||
cy.apiCreateApp();
|
||||
cy.apiCreateApp(`${fake.companyName}-App`);
|
||||
cy.openApp();
|
||||
cy.viewport(1200, 1200);
|
||||
cy.dragAndDropWidget("List View", 50, 500);
|
||||
|
|
@ -164,6 +164,8 @@ describe("List view widget", () => {
|
|||
)
|
||||
);
|
||||
cy.get(commonWidgetSelector.buttonCloseEditorSideBar).click();
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(`[data-cy=${data.widgetName.toLowerCase()}-row-1]`).click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, data.marks[1]);
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,10 @@ describe("Table", () => {
|
|||
"have.text",
|
||||
"Button Position"
|
||||
); // dropdown_type
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
openEditorSidebar(data.widgetName);
|
||||
cy.get('[data-cy="pages-name-fakename1"]').click();
|
||||
|
||||
cy.get('[data-cy="rightActions-cell-2"]')
|
||||
.eq(0)
|
||||
|
|
@ -291,6 +295,9 @@ describe("Table", () => {
|
|||
);
|
||||
|
||||
cy.get('[data-cy="add-event-handler"]').eq(1).click();
|
||||
cy.waitForAutoSave();
|
||||
openEditorSidebar(data.widgetName);
|
||||
cy.get('[data-cy="pages-name-fakename1"]').click();
|
||||
cy.get('[data-cy="leftActions-cell-0"]').eq(0).find("button").click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Hello world!");
|
||||
openEditorSidebar(data.widgetName);
|
||||
|
|
@ -1098,7 +1105,7 @@ describe("Table", () => {
|
|||
verifyNodeData(tableText.defaultWidgetName, "Object", "22 entries ");
|
||||
cy.wait(1000);
|
||||
openNode(tableText.defaultWidgetName, 0, 1);
|
||||
openNode(tableText.defaultWidgetName, 0, 1);
|
||||
// openNode(tableText.defaultWidgetName, 0, 1);
|
||||
verifyNodeData("newRows", "Array", "1 item ");
|
||||
openNode("newRows");
|
||||
verifyNodeData("0", "Object", "3 entries ");
|
||||
|
|
|
|||
|
|
@ -50,15 +50,20 @@ describe("App Import Functionality", () => {
|
|||
);
|
||||
}
|
||||
});
|
||||
cy.get(importSelectors.importOptionInput).selectFile(toolJetImage, {
|
||||
cy.get(importSelectors.importOptionInput).eq(0).selectFile(toolJetImage, {
|
||||
force: true,
|
||||
});
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
importText.couldNotImportAppToastMessage
|
||||
);
|
||||
|
||||
cy.get(importSelectors.importOptionInput).selectFile(appFile, {
|
||||
cy.reload();
|
||||
cy.get(importSelectors.dropDownMenu).should("be.visible").click();
|
||||
cy.get(importSelectors.importOptionLabel).verifyVisibleElement(
|
||||
"have.text",
|
||||
importText.importOption
|
||||
);
|
||||
cy.get(importSelectors.importOptionInput).eq(0).selectFile(appFile, {
|
||||
force: true,
|
||||
});
|
||||
cy.get('[data-cy="import-app-title"]').should("be.visible");
|
||||
|
|
@ -72,6 +77,7 @@ describe("App Import Functionality", () => {
|
|||
"contain.value",
|
||||
appData.name.toLowerCase()
|
||||
);
|
||||
cy.skipEditorPopover();
|
||||
cy.modifyCanvasSize(900, 600);
|
||||
cy.dragAndDropWidget(buttonText.defaultWidgetText);
|
||||
cy.get(appVersionSelectors.appVersionLabel).should("be.visible");
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ describe("App share functionality", () => {
|
|||
cy.get(commonSelectors.editorPageLogo).click();
|
||||
|
||||
logout();
|
||||
cy.wait(2500);
|
||||
cy.visit(`/applications/${slug}`);
|
||||
cy.wait(2500);
|
||||
|
||||
cy.get(commonSelectors.loginButton).should("be.visible");
|
||||
|
||||
|
|
@ -76,21 +78,34 @@ describe("App share functionality", () => {
|
|||
cy.get(commonSelectors.editorPageLogo).click();
|
||||
|
||||
logout();
|
||||
cy.wait(2500);
|
||||
cy.visit(`/applications/${slug}`);
|
||||
cy.wait(500);
|
||||
cy.wait(2500);
|
||||
cy.get('[data-cy="draggable-widget-table1"]').should("be.visible");
|
||||
});
|
||||
|
||||
it("Verify app private and public app visibility for the same workspace user", () => {
|
||||
addNewUserMW(data.firstName, data.email);
|
||||
navigateToAppEditor(data.appName);
|
||||
cy.wait(2000);
|
||||
cy.get(commonWidgetSelector.shareAppButton).click();
|
||||
cy.get("body").then(($el) => {
|
||||
if (!$el.text().includes("Embedded app link", { timeout: 2000 })) {
|
||||
cy.get(commonWidgetSelector.makePublicAppToggle).check();
|
||||
}
|
||||
});
|
||||
cy.get(commonWidgetSelector.modalCloseButton).click();
|
||||
cy.get(commonSelectors.editorPageLogo).click();
|
||||
|
||||
addNewUserMW(data.firstName, data.email);
|
||||
logout();
|
||||
|
||||
cy.visit(`/applications/${slug}`);
|
||||
cy.get('[data-cy="draggable-widget-table1"]').should("be.visible");
|
||||
|
||||
cy.appUILogin();
|
||||
navigateToAppEditor(data.appName);
|
||||
cy.skipEditorPopover()
|
||||
cy.wait(2000);
|
||||
cy.skipEditorPopover();
|
||||
cy.get(commonWidgetSelector.shareAppButton).click();
|
||||
cy.get(commonWidgetSelector.makePublicAppToggle).uncheck();
|
||||
cy.get(commonWidgetSelector.modalCloseButton).click();
|
||||
|
|
@ -119,6 +134,10 @@ describe("App share functionality", () => {
|
|||
cy.clearAndType(commonSelectors.passwordInputField, "password");
|
||||
cy.get(commonSelectors.signInButton).click();
|
||||
cy.wait(1000);
|
||||
cy.get(`[data-cy="workspace-sign-in-sub-header"]`).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Sign in to your workspace - My workspace"
|
||||
);
|
||||
|
||||
cy.visit("/");
|
||||
cy.wait(2000);
|
||||
|
|
@ -126,6 +145,7 @@ describe("App share functionality", () => {
|
|||
cy.appUILogin();
|
||||
|
||||
navigateToAppEditor(data.appName);
|
||||
cy.wait(2000);
|
||||
cy.skipEditorPopover();
|
||||
cy.get(commonWidgetSelector.shareAppButton).click();
|
||||
cy.get(commonWidgetSelector.makePublicAppToggle).check();
|
||||
|
|
|
|||
|
|
@ -21,21 +21,21 @@ describe("User permissions", () => {
|
|||
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=").as("homePage");
|
||||
cy.apiLogin();
|
||||
cy.apiCreateApp(data.appName);
|
||||
cy.visit('/')
|
||||
cy.visit('/my-workspace')
|
||||
permissions.reset();
|
||||
cy.get(commonSelectors.homePageLogo).click();
|
||||
cy.wait("@homePage");
|
||||
permissions.addNewUserMW(data.firstName, data.email);
|
||||
common.logout();
|
||||
cy.logoutApi();
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.appUILogin();
|
||||
cy.visitTheWorkspace("My workspace");
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
});
|
||||
|
||||
it("Should verify the create new app permission", () => {
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.logoutApi();
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.get("body").then(($title) => {
|
||||
if ($title.text().includes(dashboardText.emptyPageDescription)) {
|
||||
cy.get(commonSelectors.dashboardAppCreateButton).should('be.disabled');
|
||||
|
|
@ -43,7 +43,7 @@ describe("User permissions", () => {
|
|||
cy.contains(dashboardText.createAppButton).should("not.exist");
|
||||
}
|
||||
});
|
||||
common.logout();
|
||||
cy.logoutApi();
|
||||
});
|
||||
|
||||
it("Should verify the View and Edit permission", () => {
|
||||
|
|
@ -59,7 +59,9 @@ describe("User permissions", () => {
|
|||
});
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
cy.contains(data.appName).should("exist");
|
||||
cy.get(commonSelectors.appCard(data.appName)).should(
|
||||
"contain.text",
|
||||
|
|
@ -74,8 +76,11 @@ describe("User permissions", () => {
|
|||
"tj-disabled-btn"
|
||||
);
|
||||
});
|
||||
common.logout();
|
||||
|
||||
permissions.adminLogin();
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
common.navigateToManageGroups();
|
||||
cy.contains("tr", data.appName)
|
||||
.parent()
|
||||
.within(() => {
|
||||
|
|
@ -87,7 +92,9 @@ describe("User permissions", () => {
|
|||
);
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
cy.get(commonSelectors.appCard(data.appName)).should(
|
||||
"contain.text",
|
||||
data.appName
|
||||
|
|
@ -114,9 +121,10 @@ describe("User permissions", () => {
|
|||
|
||||
it("Should verify the Create and Delete app permission", () => {
|
||||
data.appName = `${fake.companyName}-App`;
|
||||
cy.createApp(data.appName);
|
||||
cy.get(commonSelectors.editorPageLogo).click();
|
||||
cy.wait(1000);
|
||||
cy.apiCreateApp(data.appName);
|
||||
cy.visit('/my-workspace')
|
||||
cy.wait(500);
|
||||
|
||||
common.navigateToManageGroups();
|
||||
cy.get(groupsSelector.appSearchBox).click();
|
||||
cy.get(groupsSelector.searchBoxOptions).contains(data.appName).click();
|
||||
|
|
@ -133,18 +141,24 @@ describe("User permissions", () => {
|
|||
cy.get(groupsSelector.permissionsLink).click();
|
||||
cy.get(groupsSelector.appsDeleteCheck).check();
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.logoutApi();
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit('/my-workspace');
|
||||
cy.get(commonSelectors.appCreateButton).should("exist");
|
||||
common.viewAppCardOptions(data.appName);
|
||||
cy.contains("Delete app").should("exist");
|
||||
|
||||
permissions.adminLogin();
|
||||
common.logout();
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
common.navigateToManageGroups();
|
||||
cy.get(groupsSelector.permissionsLink).click();
|
||||
cy.get(groupsSelector.appsDeleteCheck).uncheck();
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.logoutApi();
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(1000)
|
||||
common.viewAppCardOptions(data.appName);
|
||||
cy.contains("Delete app").should("not.exist");
|
||||
|
||||
|
|
@ -157,12 +171,17 @@ describe("User permissions", () => {
|
|||
cy.get(commonSelectors.appCardOptions(commonText.deleteAppOption)).click();
|
||||
cy.get(commonSelectors.buttonSelector("Yes")).click();
|
||||
|
||||
permissions.adminLogin();
|
||||
common.logout
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
common.navigateToManageGroups();
|
||||
cy.get(groupsSelector.permissionsLink).click();
|
||||
cy.get(groupsSelector.appsCreateCheck).uncheck();
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.logoutApi();
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(1000)
|
||||
cy.contains("Create new application").should("not.exist");
|
||||
});
|
||||
|
||||
|
|
@ -171,8 +190,10 @@ describe("User permissions", () => {
|
|||
cy.get(groupsSelector.permissionsLink).click();
|
||||
cy.get(groupsSelector.foldersCreateCheck).check();
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.logoutApi();
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
|
||||
cy.get(commonSelectors.createNewFolderButton).click();
|
||||
cy.clearAndType(commonSelectors.folderNameInput, data.folderName);
|
||||
|
|
@ -188,15 +209,22 @@ describe("User permissions", () => {
|
|||
});
|
||||
cy.get(commonSelectors.deleteFolderOption(data.folderName)).click();
|
||||
cy.get(commonSelectors.buttonSelector("Yes")).click();
|
||||
common.logout();
|
||||
|
||||
permissions.adminLogin();
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
common.navigateToManageGroups();
|
||||
cy.get(groupsSelector.permissionsLink).click();
|
||||
cy.get(groupsSelector.foldersCreateCheck).uncheck();
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
|
||||
permissions.adminLogin();
|
||||
cy.apiLogin();
|
||||
cy.visit("/my-workspace");
|
||||
common.navigateToManageGroups();
|
||||
cy.contains("td", data.appName)
|
||||
.parent()
|
||||
.within(() => {
|
||||
|
|
@ -204,7 +232,9 @@ describe("User permissions", () => {
|
|||
});
|
||||
|
||||
common.logout();
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
cy.contains(data.appName).should("not.exist");
|
||||
|
||||
common.logout();
|
||||
|
|
@ -223,7 +253,9 @@ describe("User permissions", () => {
|
|||
).verifyVisibleElement("have.text", "Go to workspace constants");
|
||||
common.logout();
|
||||
|
||||
cy.login(data.email, usersText.password);
|
||||
cy.apiLogin(data.email, usersText.password);
|
||||
cy.visit("/my-workspace");
|
||||
cy.wait(500)
|
||||
common.navigateToWorkspaceVariable();
|
||||
cy.get('[data-cy="alert-info-text"]>>.text-muted').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ describe("Workspace constants", () => {
|
|||
.click();
|
||||
|
||||
cy.get(commonSelectors.breadcrumbTitle).should(($el) => {
|
||||
expect($el.contents().first().text().trim()).to.eq("Workspace settings");
|
||||
expect($el.contents().first().text().trim()).to.eq(
|
||||
"Workspace settings"
|
||||
);
|
||||
});
|
||||
cy.get(commonSelectors.breadcrumbPageTitle).verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -67,7 +69,9 @@ describe("Workspace constants", () => {
|
|||
);
|
||||
|
||||
cy.get("body").then(($body) => {
|
||||
if ($body.find(workspaceConstantsSelectors.emptyStateImage).length > 0) {
|
||||
if (
|
||||
$body.find(workspaceConstantsSelectors.emptyStateImage).length > 0
|
||||
) {
|
||||
cy.get(workspaceConstantsSelectors.emptyStateImage).should(
|
||||
"be.visible"
|
||||
);
|
||||
|
|
@ -77,7 +81,9 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
workspaceConstantsText.emptyStateHeader
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.emptyStateText).verifyVisibleElement(
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.emptyStateText
|
||||
).verifyVisibleElement(
|
||||
"have.text",
|
||||
workspaceConstantsText.emptyStateText
|
||||
);
|
||||
|
|
@ -94,7 +100,10 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
workspaceConstantsText.addConstatntText
|
||||
);
|
||||
cy.get(commonSelectors.nameLabel).verifyVisibleElement("have.text", "Name");
|
||||
cy.get(commonSelectors.nameLabel).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Name"
|
||||
);
|
||||
cy.get(commonSelectors.nameInputField)
|
||||
.invoke("attr", "placeholder")
|
||||
.should("eq", "Enter Constant Name");
|
||||
|
|
@ -111,11 +120,12 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
"Cancel"
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Add constant"
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.addConstantButton
|
||||
).verifyVisibleElement("have.text", "Add constant");
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should(
|
||||
"be.disabled"
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should("be.disabled");
|
||||
|
||||
contantsNameValidation(" ", commonText.constantsNameError);
|
||||
contantsNameValidation("9", commonText.constantsNameError);
|
||||
|
|
@ -134,13 +144,17 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
commonText.constantsValueError
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should("be.disabled");
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should(
|
||||
"be.disabled"
|
||||
);
|
||||
cy.get(commonSelectors.cancelButton).click();
|
||||
cy.get(workspaceConstantsSelectors.addNewConstantButton).click();
|
||||
|
||||
cy.clearAndType(commonSelectors.nameInputField, data.constName);
|
||||
cy.clearAndType(commonSelectors.valueInputField, data.constName);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should("be.enabled");
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should(
|
||||
"be.enabled"
|
||||
);
|
||||
cy.get(commonSelectors.cancelButton).click();
|
||||
cy.get(workspaceConstantsSelectors.constantName(data.constName)).should(
|
||||
"not.exist"
|
||||
|
|
@ -189,14 +203,22 @@ describe("Workspace constants", () => {
|
|||
).verifyVisibleElement("have.text", "Delete");
|
||||
cy.get(commonSelectors.pagination).should("be.visible");
|
||||
|
||||
cy.get(workspaceConstantsSelectors.constEditButton(data.constName)).click();
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.constEditButton(data.constName)
|
||||
).click();
|
||||
|
||||
cy.get(workspaceConstantsSelectors.contantFormTitle).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Update constant in production "
|
||||
);
|
||||
cy.get(commonSelectors.nameLabel).verifyVisibleElement("have.text", "Name");
|
||||
cy.get(commonSelectors.nameInputField).should("have.value", data.constName);
|
||||
cy.get(commonSelectors.nameLabel).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Name"
|
||||
);
|
||||
cy.get(commonSelectors.nameInputField).should(
|
||||
"have.value",
|
||||
data.constName
|
||||
);
|
||||
cy.get(commonSelectors.nameInputField)
|
||||
.should("be.visible")
|
||||
.and("be.disabled");
|
||||
|
|
@ -211,20 +233,25 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
"Cancel"
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Update"
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.addConstantButton
|
||||
).verifyVisibleElement("have.text", "Update");
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should(
|
||||
"be.disabled"
|
||||
);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should("be.disabled");
|
||||
|
||||
cy.clearAndType(commonSelectors.valueInputField, data.newConstvalue);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should("be.enabled");
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).should(
|
||||
"be.enabled"
|
||||
);
|
||||
cy.get(commonSelectors.cancelButton).click();
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.constantValue(data.constName)
|
||||
).verifyVisibleElement("have.text", data.constName);
|
||||
|
||||
cy.get(workspaceConstantsSelectors.constEditButton(data.constName)).click();
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.constEditButton(data.constName)
|
||||
).click();
|
||||
cy.clearAndType(commonSelectors.valueInputField, data.newConstvalue);
|
||||
cy.get(workspaceConstantsSelectors.addConstantButton).click();
|
||||
cy.verifyToastMessage(
|
||||
|
|
@ -246,7 +273,10 @@ describe("Workspace constants", () => {
|
|||
"have.text",
|
||||
"Cancel"
|
||||
);
|
||||
cy.get(commonSelectors.yesButton).verifyVisibleElement("have.text", "Yes");
|
||||
cy.get(commonSelectors.yesButton).verifyVisibleElement(
|
||||
"have.text",
|
||||
"Yes"
|
||||
);
|
||||
cy.get(commonSelectors.cancelButton).click();
|
||||
cy.get(
|
||||
workspaceConstantsSelectors.constantValue(data.constName)
|
||||
|
|
@ -312,47 +342,31 @@ describe("Workspace constants", () => {
|
|||
`[data-cy="inspector-node-${data.constantsName}"] > .mx-2`
|
||||
).verifyVisibleElement("have.text", `"dJ_8Q~BcaMPd"`);
|
||||
|
||||
cy.get('[data-cy="button-release"]').click();
|
||||
cy.get('[data-cy="yes-button"]').click();
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released");
|
||||
|
||||
if (envVar === "Community") {
|
||||
cy.get('[data-cy="button-release"]').click();
|
||||
cy.get('[data-cy="yes-button"]').click();
|
||||
cy.verifyToastMessage(
|
||||
commonSelectors.toastMessage,
|
||||
"Version v1 released"
|
||||
);
|
||||
cy.get(commonWidgetSelector.shareAppButton).click();
|
||||
cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${data.slug}`);
|
||||
cy.wait(1500);
|
||||
cy.get(commonWidgetSelector.modalCloseButton).click();
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.wait(500)
|
||||
cy.openInCurrentTab(commonWidgetSelector.previewButton);
|
||||
cy.wait(4000);
|
||||
|
||||
cy.get(commonWidgetSelector.shareAppButton).click();
|
||||
cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${data.slug}`);
|
||||
cy.get(commonWidgetSelector.modalCloseButton).click();
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.openInCurrentTab(commonWidgetSelector.previewButton);
|
||||
cy.wait(4000);
|
||||
cy.get(
|
||||
commonWidgetSelector.draggableWidget(data.constantsName)
|
||||
).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd");
|
||||
|
||||
cy.get(
|
||||
commonWidgetSelector.draggableWidget(data.constantsName)
|
||||
).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd");
|
||||
cy.get('[data-cy="viewer-page-logo"]').click();
|
||||
cy.wait("@homePage");
|
||||
cy.visit(`/applications/${data.slug}`);
|
||||
cy.wait(4000);
|
||||
|
||||
cy.get('[data-cy="viewer-page-logo"]').click();
|
||||
cy.wait("@homePage");
|
||||
cy.visit(`/applications/${data.slug}`);
|
||||
cy.wait(4000);
|
||||
|
||||
cy.get(
|
||||
commonWidgetSelector.draggableWidget(data.constantsName)
|
||||
).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd");
|
||||
} else {
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.openInCurrentTab(commonWidgetSelector.previewButton);
|
||||
cy.wait(4000);
|
||||
|
||||
cy.get(
|
||||
commonWidgetSelector.draggableWidget(data.constantsName)
|
||||
).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd");
|
||||
|
||||
cy.get('[data-cy="viewer-page-logo"]').click();
|
||||
cy.wait("@homePage");
|
||||
}
|
||||
cy.get(
|
||||
commonWidgetSelector.draggableWidget(data.constantsName)
|
||||
).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,12 @@ export const verifyControlComponentAction = (widgetName, value) => {
|
|||
cy.get(commonWidgetSelector.componentTextInput)
|
||||
.find('[data-cy*="-input-field"]')
|
||||
.clearAndTypeOnCodeMirror(value);
|
||||
cy.forceClickOnCanvas();
|
||||
cy.waitForAutoSave();
|
||||
cy.get(commonWidgetSelector.draggableWidget(widgetName)).click();
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget('textinput1')).should("have.value", value);
|
||||
cy.get(commonWidgetSelector.draggableWidget("textinput1")).should(
|
||||
"have.value",
|
||||
value
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,5 +151,7 @@ export const verifyVersionAfterPreview = (currentVersion) => {
|
|||
.click();
|
||||
cy.url().should("include", "/home");
|
||||
verifyComponent("button1");
|
||||
cy.go("back");
|
||||
cy.waitForAppLoad()
|
||||
cy.contains(currentVersion);
|
||||
};
|
||||
|
|
|
|||
1905
docs/package-lock.json
generated
1905
docs/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -18,12 +18,15 @@
|
|||
"@docusaurus/plugin-google-gtag": "^2.4.3",
|
||||
"@docusaurus/plugin-sitemap": "^2.4.3",
|
||||
"@docusaurus/preset-classic": "^2.4.3",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"plugin-image-zoom": "github:flexanalytics/plugin-image-zoom",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"got": "^12.1.0",
|
||||
"trim": "^0.0.3"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.23.0
|
||||
2.24.0
|
||||
|
|
|
|||
|
|
@ -1,11 +1,41 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Editor } from '../Editor/Editor';
|
||||
import { RealtimeEditor } from '@/Editor/RealtimeEditor';
|
||||
import config from 'config';
|
||||
import { appService } from '@/_services';
|
||||
import { useAppDataActions } from '@/_stores/appDataStore';
|
||||
|
||||
const AppLoaderComponent = (props) => {
|
||||
return config.ENABLE_MULTIPLAYER_EDITING ? <RealtimeEditor {...props} /> : <Editor {...props} />;
|
||||
};
|
||||
const AppLoaderComponent = React.memo((props) => {
|
||||
const [shouldLoadApp, setShouldLoadApp] = React.useState(false);
|
||||
const { updateState } = useAppDataActions();
|
||||
|
||||
useEffect(() => {
|
||||
props?.id && props?.slug && loadAppDetails(props?.id);
|
||||
|
||||
return () => {
|
||||
setShouldLoadApp(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadAppDetails = (appId) => {
|
||||
appService.fetchApp(appId, 'edit').then((data) => {
|
||||
setShouldLoadApp(true);
|
||||
updateState({
|
||||
app: data,
|
||||
appId: data.id,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!shouldLoadApp) return <></>;
|
||||
|
||||
return config.ENABLE_MULTIPLAYER_EDITING ? (
|
||||
<RealtimeEditor {...props} shouldLoadApp={shouldLoadApp} />
|
||||
) : (
|
||||
<Editor {...props} />
|
||||
);
|
||||
});
|
||||
|
||||
export const AppLoader = withTranslation()(AppLoaderComponent);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const CreateVersion = ({
|
|||
}) => {
|
||||
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
|
||||
const [versionName, setVersionName] = useState('');
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { editingVersion } = useAppVersionStore(
|
||||
(state) => ({
|
||||
|
|
@ -25,6 +26,14 @@ export const CreateVersion = ({
|
|||
shallow
|
||||
);
|
||||
|
||||
const options = appVersions.map((version) => {
|
||||
return { label: version.name, value: version };
|
||||
});
|
||||
|
||||
const [selectedVersion, setSelectedVersion] = useState(
|
||||
() => options.find((option) => option?.value?.id === editingVersion?.id)?.value
|
||||
);
|
||||
|
||||
const createVersion = () => {
|
||||
if (versionName.trim().length > 25) {
|
||||
toast.error('Version name should not be longer than 25 characters');
|
||||
|
|
@ -36,18 +45,26 @@ export const CreateVersion = ({
|
|||
}
|
||||
|
||||
setIsCreatingVersion(true);
|
||||
|
||||
appVersionService
|
||||
.create(appId, versionName, editingVersion.id)
|
||||
.then(() => {
|
||||
.create(appId, versionName, selectedVersion.id)
|
||||
.then((data) => {
|
||||
toast.success('Version Created');
|
||||
appVersionService.getAll(appId).then((data) => {
|
||||
setVersionName('');
|
||||
setIsCreatingVersion(false);
|
||||
setAppVersions(data.versions);
|
||||
const latestVersion = data.versions.at(0);
|
||||
setAppDefinitionFromVersion(latestVersion);
|
||||
setShowCreateAppVersion(false);
|
||||
});
|
||||
|
||||
appVersionService
|
||||
.getAppVersionData(appId, data.id)
|
||||
.then((data) => {
|
||||
setAppDefinitionFromVersion(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error?.error);
|
||||
|
|
@ -55,10 +72,6 @@ export const CreateVersion = ({
|
|||
});
|
||||
};
|
||||
|
||||
const options = appVersions.map((version) => {
|
||||
return { label: version.name, value: version };
|
||||
});
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
show={showCreateAppVersion}
|
||||
|
|
@ -101,9 +114,9 @@ export const CreateVersion = ({
|
|||
<div className="ts-control" data-cy="create-version-from-input-field">
|
||||
<Select
|
||||
options={options}
|
||||
defaultValue={options.find((option) => option?.value?.id === editingVersion?.id)}
|
||||
value={selectedVersion}
|
||||
onChange={(version) => {
|
||||
setAppDefinitionFromVersion(version, false);
|
||||
setSelectedVersion(version);
|
||||
}}
|
||||
useMenuPortal={false}
|
||||
width="100%"
|
||||
|
|
|
|||
|
|
@ -114,11 +114,14 @@ export const CustomSelect = ({ ...props }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<CreateVersion
|
||||
{...props}
|
||||
showCreateAppVersion={showCreateAppVersion}
|
||||
setShowCreateAppVersion={setShowCreateAppVersion}
|
||||
/>
|
||||
{showCreateAppVersion && (
|
||||
<CreateVersion
|
||||
{...props}
|
||||
showCreateAppVersion={showCreateAppVersion}
|
||||
setShowCreateAppVersion={setShowCreateAppVersion}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { appVersionService, appEnvironmentService } from '@/_services';
|
||||
import { appVersionService } from '@/_services';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
|
||||
export const AppVersionsManager = function ({
|
||||
appId,
|
||||
releasedVersionId,
|
||||
setAppDefinitionFromVersion,
|
||||
onVersionDelete,
|
||||
}) {
|
||||
const [appVersions, setAppVersions] = useState([]);
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState('');
|
||||
const appVersionLoadingStatus = Object.freeze({
|
||||
loading: 'loading',
|
||||
loaded: 'loaded',
|
||||
error: 'error',
|
||||
});
|
||||
|
||||
export const AppVersionsManager = function ({ appId, setAppDefinitionFromVersion, onVersionDelete }) {
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
|
||||
const [deleteVersion, setDeleteVersion] = useState({
|
||||
versionId: '',
|
||||
versionName: '',
|
||||
showModal: false,
|
||||
});
|
||||
|
||||
const { editingVersion } = useAppVersionStore(
|
||||
const { releasedVersionId, editingVersion, appVersions, setAppVersions } = useAppVersionStore(
|
||||
(state) => ({
|
||||
editingVersion: state.editingVersion,
|
||||
appVersions: state.appVersions,
|
||||
setAppVersions: state.actions?.setAppVersions,
|
||||
releasedVersionId: state.releasedVersionId,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
setGetAppVersionStatus('loading');
|
||||
appEnvironmentService
|
||||
.getVersionsByEnvironment(appId)
|
||||
.then((data) => {
|
||||
setAppVersions(data.appVersions);
|
||||
setGetAppVersionStatus('success');
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error);
|
||||
setGetAppVersionStatus('failure');
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
if (appVersions && appVersions.length > 0) {
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loaded);
|
||||
}
|
||||
|
||||
return () => {
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loading);
|
||||
};
|
||||
}, [appVersions]);
|
||||
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
const selectVersion = (id) => {
|
||||
appVersionService
|
||||
.getOne(appId, id)
|
||||
.getAppVersionData(appId, id)
|
||||
.then((data) => {
|
||||
setAppDefinitionFromVersion(data, true);
|
||||
const isCurrentVersionReleased = data.currentVersionId ? true : false;
|
||||
setAppDefinitionFromVersion(data, isCurrentVersionReleased);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(error);
|
||||
|
|
@ -67,18 +67,22 @@ export const AppVersionsManager = function ({
|
|||
appVersionService
|
||||
.del(appId, versionId)
|
||||
.then(() => {
|
||||
onVersionDelete();
|
||||
toast.dismiss(deleteingToastId);
|
||||
toast.success(`Version - ${versionName} Deleted`);
|
||||
resetDeleteModal();
|
||||
appVersionService.getAll(appId).then((data) => {
|
||||
setAppVersions(data.versions);
|
||||
});
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.loading);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.dismiss(deleteingToastId);
|
||||
toast.error(error?.error ?? 'Oops, something went wrong');
|
||||
setGetAppVersionStatus(appVersionLoadingStatus.error);
|
||||
resetDeleteModal();
|
||||
})
|
||||
.finally(() => {
|
||||
appVersionService.getAll(appId, true).then((data) => {
|
||||
setAppVersions(data.versions);
|
||||
onVersionDelete();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import _ from 'lodash';
|
|||
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 = {
|
||||
|
|
@ -164,6 +165,8 @@ export const Box = memo(
|
|||
};
|
||||
}
|
||||
|
||||
const { events } = useAppInfo();
|
||||
|
||||
const componentMeta = useMemo(() => {
|
||||
return componentTypes.find((comp) => component.component === comp.component);
|
||||
}, [component]);
|
||||
|
|
@ -265,7 +268,10 @@ export const Box = memo(
|
|||
if (mode === 'edit' && eventName === 'onClick') {
|
||||
onComponentClick(id, component);
|
||||
}
|
||||
onEvent(eventName, { ...options, customVariables: { ...customResolvables }, component });
|
||||
|
||||
const componentEvents = events.filter((event) => event.sourceId === id);
|
||||
|
||||
onEvent(eventName, componentEvents, { ...options, customVariables: { ...customResolvables } });
|
||||
};
|
||||
const validate = (value) =>
|
||||
validateWidget({
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export function CodeHinter({
|
|||
};
|
||||
const currentState = useCurrentState();
|
||||
const [realState, setRealState] = useState(currentState);
|
||||
const [currentValue, setCurrentValue] = useState(initialValue);
|
||||
const [currentValue, setCurrentValue] = useState('');
|
||||
|
||||
const [prevCurrentValue, setPrevCurrentValue] = useState(null);
|
||||
const [resolvedValue, setResolvedValue] = useState(null);
|
||||
|
|
@ -120,6 +120,17 @@ export function CodeHinter({
|
|||
const { variablesExposedForPreview } = useContext(EditorContext);
|
||||
const prevCountRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(initialValue);
|
||||
|
||||
return () => {
|
||||
setPrevCurrentValue(null);
|
||||
setResolvedValue(null);
|
||||
setResolvingError(null);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (_currentState) {
|
||||
setRealState(_currentState);
|
||||
|
|
@ -127,7 +138,7 @@ export function CodeHinter({
|
|||
setRealState(currentState);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState.components, _currentState]);
|
||||
}, [JSON.stringify({ currentState, _currentState })]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
|
|
@ -149,7 +160,7 @@ export function CodeHinter({
|
|||
}, [wrapperRef, isFocused, isPreviewFocused, currentValue, prevCountRef, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (JSON.stringify(currentValue) !== JSON.stringify(prevCurrentValue)) {
|
||||
if (enablePreview && isFocused && JSON.stringify(currentValue) !== JSON.stringify(prevCurrentValue)) {
|
||||
const customResolvables = getCustomResolvables();
|
||||
const [preview, error] = resolveReferences(currentValue, realState, null, customResolvables, true, true);
|
||||
setPrevCurrentValue(currentValue);
|
||||
|
|
@ -162,13 +173,8 @@ export function CodeHinter({
|
|||
setResolvedValue(preview);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
setPrevCurrentValue(null);
|
||||
setResolvedValue(null);
|
||||
setResolvingError(null);
|
||||
};
|
||||
}, [JSON.stringify({ currentValue, realState })]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify({ currentValue, realState, isFocused })]);
|
||||
|
||||
function valueChanged(editor, onChange, ignoreBraces) {
|
||||
if (editor.getValue()?.trim() !== currentValue) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
|
|||
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
|
||||
import React from 'react';
|
||||
|
||||
const ClientServerSwitch = ({ value, onChange, cyLabel, meta, paramName }) => {
|
||||
const ClientServerSwitch = ({ value, onChange, meta }) => {
|
||||
const options = meta?.options;
|
||||
const defaultValue = value ? 'serverSide' : 'clientSide';
|
||||
const handleChange = (_value) => {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const Calendar = function ({
|
|||
action,
|
||||
};
|
||||
|
||||
fireEvent('onCalendarSlotSelect', { selectedSlots });
|
||||
fireEvent('onCalendarSlotSelect', { component, selectedSlots });
|
||||
};
|
||||
|
||||
function popoverClosed() {
|
||||
|
|
@ -153,7 +153,7 @@ export const Calendar = function ({
|
|||
min={startTime}
|
||||
max={endTime}
|
||||
onSelectEvent={(calendarEvent, e) => {
|
||||
fireEvent('onCalendarEventSelect', { calendarEvent });
|
||||
fireEvent('onCalendarEventSelect', { component, calendarEvent });
|
||||
if (properties.showPopOverOnEventClick)
|
||||
setEventPopoverOptions({
|
||||
...eventPopoverOptions,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { resolveWidgetFieldValue } from '@/_helpers/utils';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import * as XLSX from 'xlsx/xlsx.mjs';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
|
||||
export const FilePicker = ({
|
||||
id,
|
||||
|
|
@ -54,6 +55,10 @@ export const FilePicker = ({
|
|||
const parsedWidgetVisibility =
|
||||
typeof widgetVisibility !== 'boolean' ? resolveWidgetFieldValue(widgetVisibility, currentState) : widgetVisibility;
|
||||
|
||||
const { events: allAppEvents } = useAppInfo();
|
||||
|
||||
const filePickerEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id);
|
||||
|
||||
const bgThemeColor = darkMode ? '#232E3C' : '#fff';
|
||||
|
||||
const baseStyle = {
|
||||
|
|
@ -235,7 +240,7 @@ export const FilePicker = ({
|
|||
onComponentOptionChanged(component, 'file', [], id);
|
||||
}
|
||||
|
||||
if (acceptedFiles.length !== 0) {
|
||||
if (acceptedFiles.length !== 0 && onEvent) {
|
||||
const fileData = parsedEnableMultiple ? [...selectedFiles] : [];
|
||||
if (parseContent) {
|
||||
onComponentOptionChanged(component, 'isParsing', true, id);
|
||||
|
|
@ -250,7 +255,8 @@ export const FilePicker = ({
|
|||
});
|
||||
setSelectedFiles(fileData);
|
||||
onComponentOptionChanged(component, 'file', fileData, id);
|
||||
onEvent('onFileSelected', { component })
|
||||
|
||||
onEvent('onFileSelected', filePickerEvents, { component })
|
||||
.then(() => {
|
||||
setAccepted(true);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
|
@ -263,7 +269,7 @@ export const FilePicker = ({
|
|||
}, 600);
|
||||
});
|
||||
})
|
||||
.then(() => onEvent('onFileLoaded', { component }));
|
||||
.then(() => onEvent('onFileLoaded', filePickerEvents, { component }));
|
||||
}
|
||||
|
||||
if (fileRejections.length > 0) {
|
||||
|
|
@ -286,7 +292,7 @@ export const FilePicker = ({
|
|||
copy.splice(index, 1);
|
||||
return copy;
|
||||
});
|
||||
onEvent('onFileDeselected', { component });
|
||||
onEvent('onFileDeselected', filePickerEvents);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Box } from '@/Editor/Box';
|
|||
import { generateUIComponents } from './FormUtils';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
import { removeFunctionObjects } from '@/_helpers/appUtils';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
export const Form = function Form(props) {
|
||||
const {
|
||||
id,
|
||||
|
|
@ -28,6 +29,10 @@ export const Form = function Form(props) {
|
|||
dataCy,
|
||||
paramUpdated,
|
||||
} = props;
|
||||
|
||||
const { events: allAppEvents } = useAppInfo();
|
||||
|
||||
const formEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id);
|
||||
const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles;
|
||||
const { buttonToSubmit, loadingState, advanced, JSONSchema } = properties;
|
||||
const backgroundColor =
|
||||
|
|
@ -57,7 +62,7 @@ export const Form = function Form(props) {
|
|||
});
|
||||
setExposedVariable('submitForm', async function () {
|
||||
if (isValid) {
|
||||
onEvent('onSubmit', { component }).then(() => resetComponent());
|
||||
onEvent('onSubmit', formEvents).then(() => resetComponent());
|
||||
} else {
|
||||
fireEvent('onInvalid');
|
||||
}
|
||||
|
|
@ -167,7 +172,7 @@ export const Form = function Form(props) {
|
|||
};
|
||||
const fireSubmissionEvent = () => {
|
||||
if (isValid) {
|
||||
onEvent('onSubmit', { component }).then(() => resetComponent());
|
||||
onEvent('onSubmit', formEvents).then(() => resetComponent());
|
||||
} else {
|
||||
fireEvent('onInvalid');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,12 @@ export const Item = React.memo(
|
|||
isFirstItem = false,
|
||||
setShowModal = () => {},
|
||||
cardDataAsObj = {},
|
||||
setLastSelectedCard,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { id, component, containerProps, fireEvent, setExposedVariable, darkMode } = kanbanProps;
|
||||
const { id, component, containerProps, fireEvent, darkMode, setExposedVariable } = kanbanProps;
|
||||
useEffect(() => {
|
||||
if (!dragOverlay) {
|
||||
return;
|
||||
|
|
@ -61,6 +62,7 @@ export const Item = React.memo(
|
|||
)
|
||||
return;
|
||||
setExposedVariable('lastSelectedCard', cardDataAsObj[value]);
|
||||
setLastSelectedCard(cardDataAsObj[value]);
|
||||
setShowModal(true);
|
||||
fireEvent('onCardSelected');
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -37,11 +37,10 @@ const dropAnimation = {
|
|||
const TRASH_ID = 'void';
|
||||
|
||||
export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
||||
const { properties, fireEvent, setExposedVariable, setExposedVariables, exposedVariables, styles } = kanbanProps;
|
||||
const { lastSelectedCard = {} } = exposedVariables;
|
||||
const { properties, fireEvent, setExposedVariable, setExposedVariables, styles } = kanbanProps;
|
||||
const { columnData, cardData, cardWidth, cardHeight, showDeleteButton, enableAddCard } = properties;
|
||||
const { accentColor } = styles;
|
||||
|
||||
const [lastSelectedCard, setLastSelectedCard] = useState({});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const columnDataAsObj = useMemo(() => convertArrayToObj(columnData), [JSON.stringify(columnData)]);
|
||||
|
||||
|
|
@ -85,7 +84,6 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
|||
useEffect(() => {
|
||||
droppableItemsColumnId.current = containers.find((container) => items[container]?.length > 0);
|
||||
}, [items, containers]);
|
||||
|
||||
useEffect(() => {
|
||||
setExposedVariable('updateCardData', async function (cardId, value) {
|
||||
if (cardDataAsObj[cardId] === undefined) return toast.error('Card not found');
|
||||
|
|
@ -95,6 +93,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
|||
if (lastSelectedCard?.id === cardId) {
|
||||
setExposedVariables({
|
||||
lastSelectedCard: cardDataAsObj[cardId],
|
||||
|
||||
lastUpdatedCard: cardDataAsObj[cardId],
|
||||
lastCardUpdate: diffKeys.map((key) => {
|
||||
return {
|
||||
|
|
@ -104,9 +103,10 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
|||
updatedCardData: getData(cardDataAsObj),
|
||||
});
|
||||
fireEvent('onUpdate');
|
||||
} else {
|
||||
setExposedVariable('updatedCardData', getData(cardDataAsObj));
|
||||
fireEvent('onUpdate');
|
||||
}
|
||||
setExposedVariable('updatedCardData', getData(cardDataAsObj));
|
||||
fireEvent('onUpdate');
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lastSelectedCard, JSON.stringify(cardDataAsObj)]);
|
||||
|
|
@ -148,7 +148,6 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
|||
...items,
|
||||
[columnId]: [...items[columnId], cardDetails.id],
|
||||
}));
|
||||
|
||||
setExposedVariables({ lastAddedCard: { ...cardDetails }, updatedCardData: getData(cardDataAsObj) });
|
||||
fireEvent('onCardAdded');
|
||||
});
|
||||
|
|
@ -370,6 +369,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef }) {
|
|||
isFirstItem={index === 0 && droppableItemsColumnId.current === columnId}
|
||||
setShowModal={setShowModal}
|
||||
cardDataAsObj={cardDataAsObj}
|
||||
setLastSelectedCard={setLastSelectedCard}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
@ -426,6 +426,7 @@ function SortableItem({
|
|||
isFirstItem,
|
||||
setShowModal,
|
||||
cardDataAsObj,
|
||||
setLastSelectedCard,
|
||||
}) {
|
||||
const { setNodeRef, setActivatorNodeRef, listeners, isDragging, isSorting, transform, transition } = useSortable({
|
||||
id,
|
||||
|
|
@ -450,6 +451,7 @@ function SortableItem({
|
|||
isFirstItem={isFirstItem}
|
||||
setShowModal={setShowModal}
|
||||
cardDataAsObj={cardDataAsObj}
|
||||
setLastSelectedCard={setLastSelectedCard}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useCurrentState } from '@/_stores/currentStateStore';
|
|||
|
||||
export const BoardContext = React.createContext({});
|
||||
|
||||
// This one is deprecated and not deleted to support backward compatibility
|
||||
export const KanbanBoard = ({
|
||||
id,
|
||||
height,
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export const Listview = function Listview({
|
|||
key={index}
|
||||
data-cy={`${String(component.name).toLowerCase()}-row-${index}`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
onRecordClicked(index);
|
||||
onRowClicked(index);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export function AddNewRowComponent({
|
|||
columns,
|
||||
addNewRowsDetails,
|
||||
utilityForNestedNewRow,
|
||||
tableEvents,
|
||||
}) {
|
||||
const getNewRowObject = () => {
|
||||
return allColumns.reduce((accumulator, column) => {
|
||||
|
|
@ -160,11 +161,10 @@ export function AddNewRowComponent({
|
|||
<ButtonSolid
|
||||
variant="primary"
|
||||
className={`tj-text-xsm`}
|
||||
onClick={() => {
|
||||
onEvent('onNewRowsAdded', { component }).then(() => {
|
||||
mergeToAddNewRowsDetails({ newRowsDataUpdates: {}, newRowsChangeSet: {}, addingNewRows: false });
|
||||
setNewRowsState([]);
|
||||
});
|
||||
onClick={async () => {
|
||||
await onEvent('onNewRowsAdded', tableEvents, { component });
|
||||
mergeToAddNewRowsDetails({ newRowsDataUpdates: {}, newRowsChangeSet: {}, addingNewRows: false });
|
||||
setNewRowsState([]);
|
||||
}}
|
||||
size="sm"
|
||||
customStyles={{ padding: '10px 20px' }}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ export const GlobalFilter = ({
|
|||
onEvent,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
darkMode,
|
||||
tableEvents,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(globalFilter);
|
||||
const onChange = useAsyncDebounce((filterValue) => {
|
||||
setValue(filterValue);
|
||||
setGlobalFilter(filterValue || undefined);
|
||||
onComponentOptionChanged(component, 'searchText', filterValue).then(() => {
|
||||
onEvent('onSearch', { component, data: {} });
|
||||
onEvent('onSearch', tableEvents, { component, data: {} });
|
||||
});
|
||||
}, 500);
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,13 @@ import GenerateEachCellValue from './GenerateEachCellValue';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { AddNewRowComponent } from './AddNewRowComponent';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { OverlayTriggerComponent } from './OverlayTriggerComponent';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
// utilityForNestedNewRow function is used to construct nested object while adding or updating new row when '.' is present in column key for adding new row
|
||||
const utilityForNestedNewRow = (row) => {
|
||||
|
|
@ -92,7 +95,7 @@ export function Table({
|
|||
properties,
|
||||
variablesExposedForPreview,
|
||||
exposeToCodeHinter,
|
||||
events,
|
||||
// events,
|
||||
setProperty,
|
||||
mode,
|
||||
exposedVariables,
|
||||
|
|
@ -132,6 +135,11 @@ export function Table({
|
|||
|
||||
const updatedDataReference = useRef([]);
|
||||
const preSelectRow = useRef(false);
|
||||
const { events: allAppEvents } = useAppInfo();
|
||||
|
||||
const tableEvents = allAppEvents.filter((event) => event.target === 'component' && event.sourceId === id);
|
||||
const tableColumnEvents = allAppEvents.filter((event) => event.target === 'table_column' && event.sourceId === id);
|
||||
const tableActionEvents = allAppEvents.filter((event) => event.target === 'table_action' && event.sourceId === id);
|
||||
|
||||
const getItemStyle = ({ isDragging, isDropAnimating }, draggableStyle) => ({
|
||||
...draggableStyle,
|
||||
|
|
@ -179,13 +187,14 @@ export function Table({
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
const hoverEvent = component?.definition?.events?.find((event) => {
|
||||
const hoverEvent = tableEvents?.find(({ event }) => {
|
||||
return event?.eventId == 'onRowHovered';
|
||||
});
|
||||
if (hoverEvent?.eventId) {
|
||||
|
||||
if (hoverEvent?.event?.eventId) {
|
||||
setHoverAdded(true);
|
||||
}
|
||||
}, [JSON.stringify(component.definition.events)]);
|
||||
}, [JSON.stringify(tableEvents)]);
|
||||
|
||||
function showFilters() {
|
||||
mergeToFilterDetails({ filtersVisible: true });
|
||||
|
|
@ -281,7 +290,7 @@ export function Table({
|
|||
|
||||
function getExportFileBlob({ columns, fileType, fileName }) {
|
||||
let headers = columns.map((column) => {
|
||||
return { exportValue: String(column.exportValue), key: column.key ? String(column.key) : column.key };
|
||||
return { exportValue: String(column?.exportValue), key: column.key ? String(column.key) : column?.key };
|
||||
});
|
||||
let data = globalFilteredRows.map((row) => {
|
||||
return headers.reduce((accumulator, header) => {
|
||||
|
|
@ -399,12 +408,13 @@ export function Table({
|
|||
tableRef,
|
||||
t,
|
||||
darkMode,
|
||||
tableColumnEvents: tableColumnEvents,
|
||||
});
|
||||
|
||||
columnData = useMemo(
|
||||
() =>
|
||||
columnData.filter((column) => {
|
||||
if (resolveReferences(column.columnVisibility, currentState)) {
|
||||
if (resolveReferences(column?.columnVisibility, currentState)) {
|
||||
return column;
|
||||
}
|
||||
}),
|
||||
|
|
@ -437,8 +447,9 @@ export function Table({
|
|||
defaultColumn,
|
||||
fireEvent,
|
||||
setExposedVariables,
|
||||
tableActionEvents,
|
||||
}),
|
||||
[JSON.stringify(actions)]
|
||||
[JSON.stringify(actions), tableActionEvents]
|
||||
);
|
||||
|
||||
const textWrapActions = (id) => {
|
||||
|
|
@ -448,7 +459,7 @@ export function Table({
|
|||
return wrapOption?.textWrap;
|
||||
};
|
||||
|
||||
const optionsData = columnData.map((column) => column.columnOptions?.selectOptions);
|
||||
const optionsData = columnData.map((column) => column?.columnOptions?.selectOptions);
|
||||
const columns = useMemo(
|
||||
() => {
|
||||
return [...leftActionsCellData, ...columnData, ...rightActionsCellData];
|
||||
|
|
@ -467,6 +478,8 @@ export function Table({
|
|||
darkMode,
|
||||
allowSelection,
|
||||
highlightSelectedRow,
|
||||
JSON.stringify(tableActionEvents),
|
||||
JSON.stringify(tableColumnEvents),
|
||||
] // Hack: need to fix
|
||||
);
|
||||
|
||||
|
|
@ -777,11 +790,19 @@ export function Table({
|
|||
|
||||
useEffect(() => {
|
||||
const newColumnSizes = { ...columnSizes, ...state.columnResizing.columnWidths };
|
||||
if (!state.columnResizing.isResizingColumn && !_.isEmpty(newColumnSizes)) {
|
||||
|
||||
const isColumnSizeChanged = !_.isEmpty(diff(columnSizes, newColumnSizes));
|
||||
|
||||
if (isColumnSizeChanged && !state.columnResizing.isResizingColumn && !_.isEmpty(newColumnSizes)) {
|
||||
changeCanDrag(true);
|
||||
paramUpdated(id, 'columnSizes', {
|
||||
value: newColumnSizes,
|
||||
});
|
||||
paramUpdated(
|
||||
id,
|
||||
'columnSizes',
|
||||
{
|
||||
value: newColumnSizes,
|
||||
},
|
||||
{ componentDefinitionChanged: true }
|
||||
);
|
||||
} else {
|
||||
changeCanDrag(false);
|
||||
}
|
||||
|
|
@ -908,7 +929,7 @@ export function Table({
|
|||
</div>
|
||||
{allColumns.map(
|
||||
(column) =>
|
||||
typeof column.Header === 'string' && (
|
||||
typeof column?.Header === 'string' && (
|
||||
<div key={column.id}>
|
||||
<div>
|
||||
<label className="dropdown-item d-flex cursor-pointer">
|
||||
|
|
@ -1040,6 +1061,7 @@ export function Table({
|
|||
component={component}
|
||||
onEvent={onEvent}
|
||||
darkMode={darkMode}
|
||||
tableEvents={tableEvents}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1497,7 +1519,7 @@ export function Table({
|
|||
variant="primary"
|
||||
className={`tj-text-xsm`}
|
||||
onClick={() => {
|
||||
onEvent('onBulkUpdate', { component }).then(() => {
|
||||
onEvent('onBulkUpdate', tableEvents, { component }).then(() => {
|
||||
handleChangesSaved();
|
||||
});
|
||||
}}
|
||||
|
|
@ -1676,6 +1698,7 @@ export function Table({
|
|||
columns={columnsForAddNewRow}
|
||||
addNewRowsDetails={tableDetails.addNewRowsDetails}
|
||||
utilityForNestedNewRow={utilityForNestedNewRow}
|
||||
tableEvents={tableEvents}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import React from 'react';
|
||||
|
||||
const generateActionsData = ({ actions: actionItems, columnSizes, defaultColumn, fireEvent, setExposedVariables }) => {
|
||||
const generateActionsData = ({
|
||||
actions: actionItems,
|
||||
columnSizes,
|
||||
defaultColumn,
|
||||
fireEvent,
|
||||
setExposedVariables,
|
||||
tableActionEvents,
|
||||
}) => {
|
||||
const leftActions = (actions = actionItems) => actions.filter((action) => action.position === 'left');
|
||||
const rightActions = (actions = actionItems) =>
|
||||
actions.filter((action) => [undefined, 'right'].includes(action.position));
|
||||
|
|
@ -32,6 +39,7 @@ const generateActionsData = ({ actions: actionItems, columnSizes, defaultColumn,
|
|||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
tableActionEvents,
|
||||
});
|
||||
});
|
||||
}}
|
||||
|
|
@ -72,6 +80,7 @@ const generateActionsData = ({ actions: actionItems, columnSizes, defaultColumn,
|
|||
data: cell.row.original,
|
||||
rowId: cell.row.id,
|
||||
action,
|
||||
tableActionEvents,
|
||||
});
|
||||
});
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Toggle } from '../Toggle';
|
|||
import { Datepicker } from '../Datepicker';
|
||||
import { Link } from '../Link';
|
||||
import moment from 'moment';
|
||||
|
||||
export default function generateColumnsData({
|
||||
columnProperties,
|
||||
columnSizes,
|
||||
|
|
@ -25,10 +26,13 @@ export default function generateColumnsData({
|
|||
tableRef,
|
||||
t,
|
||||
darkMode,
|
||||
tableColumnEvents,
|
||||
}) {
|
||||
return columnProperties.map((column) => {
|
||||
const columnSize = columnSizes[column.id] || columnSizes[column.name];
|
||||
const columnType = column.columnType;
|
||||
if (!column) return;
|
||||
|
||||
const columnSize = columnSizes[column?.id] || columnSizes[column?.name];
|
||||
const columnType = column?.columnType;
|
||||
let sortType = 'alphanumeric';
|
||||
|
||||
const columnOptions = {};
|
||||
|
|
@ -429,6 +433,7 @@ export default function generateColumnsData({
|
|||
column: column,
|
||||
rowId: cell.row.id,
|
||||
row: cell.row.original,
|
||||
tableColumnEvents,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export const ConfigHandle = function ConfigHandle({
|
|||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setSelectedComponent(id, component, e.shiftKey);
|
||||
}}
|
||||
role="button"
|
||||
|
|
@ -56,7 +55,7 @@ export const ConfigHandle = function ConfigHandle({
|
|||
role="button"
|
||||
height="12"
|
||||
draggable="false"
|
||||
onClick={() => removeComponent({ id })}
|
||||
onClick={() => removeComponent(id)}
|
||||
data-cy={`${component.name.toLowerCase()}-delete-button`}
|
||||
className="delete-icon"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ import { addComponents, addNewWidgetToTheEditor } from '@/_helpers/appUtils';
|
|||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useAppInfo } from '@/_stores/appDataStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
const NO_OF_GRIDS = 43;
|
||||
|
||||
|
|
@ -44,15 +47,21 @@ export const Container = ({
|
|||
sideBarDebugger,
|
||||
currentPageId,
|
||||
}) => {
|
||||
const gridWidth = canvasWidth / NO_OF_GRIDS;
|
||||
const styles = {
|
||||
width: currentLayout === 'mobile' ? deviceWindowWidth : '100%',
|
||||
maxWidth: `${canvasWidth}px`,
|
||||
backgroundSize: `${gridWidth}px 10px`,
|
||||
};
|
||||
// Dont update first time to skip
|
||||
// redundant save on app definition load
|
||||
const firstUpdate = useRef(true);
|
||||
|
||||
const { showComments, currentLayout, selectedComponents } = useEditorStore(
|
||||
(state) => ({
|
||||
showComments: state?.showComments,
|
||||
currentLayout: state?.currentLayout,
|
||||
selectedComponents: state?.selectedComponents,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const { appId } = useAppInfo();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const components = appDefinition.pages[currentPageId]?.components ?? {};
|
||||
const currentState = useCurrentState();
|
||||
const { appVersionsId, enableReleasedVersionPopupState, isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
|
|
@ -62,55 +71,66 @@ export const Container = ({
|
|||
}),
|
||||
shallow
|
||||
);
|
||||
const { showComments, currentLayout, selectedComponents } = useEditorStore(
|
||||
(state) => ({
|
||||
showComments: state?.showComments,
|
||||
currentLayout: state?.currentLayout,
|
||||
selectedComponents: state?.selectedComponents,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
const { appId } = useAppDataStore(
|
||||
(state) => ({
|
||||
appId: state?.appId,
|
||||
}),
|
||||
shallow
|
||||
|
||||
const gridWidth = canvasWidth / NO_OF_GRIDS;
|
||||
const styles = {
|
||||
width: currentLayout === 'mobile' ? deviceWindowWidth : '100%',
|
||||
maxWidth: `${canvasWidth}px`,
|
||||
backgroundSize: `${gridWidth}px 10px`,
|
||||
};
|
||||
|
||||
const components = useMemo(
|
||||
() => appDefinition.pages[currentPageId]?.components ?? {},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[JSON.stringify(appDefinition), currentPageId]
|
||||
);
|
||||
|
||||
const [boxes, setBoxes] = useState(components);
|
||||
const [boxes, setBoxes] = useState([]);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [commentsPreviewList, setCommentsPreviewList] = useState([]);
|
||||
const [newThread, addNewThread] = useState({});
|
||||
const [isContainerFocused, setContainerFocus] = useState(false);
|
||||
const [canvasHeight, setCanvasHeight] = useState(null);
|
||||
|
||||
const paramUpdatesOptsRef = useRef({});
|
||||
const canvasRef = useRef(null);
|
||||
const focusedParentIdRef = useRef(undefined);
|
||||
useHotkeys('meta+z, control+z', () => handleUndo());
|
||||
useHotkeys('meta+shift+z, control+shift+z', () => handleRedo());
|
||||
useHotkeys(
|
||||
'meta+v, control+v',
|
||||
() => {
|
||||
async () => {
|
||||
if (isContainerFocused && !isVersionReleased) {
|
||||
navigator.clipboard.readText().then((cliptext) => {
|
||||
// Check if the clipboard API is available
|
||||
if (navigator.clipboard && typeof navigator.clipboard.readText === 'function') {
|
||||
try {
|
||||
const cliptext = await navigator.clipboard.readText();
|
||||
addComponents(
|
||||
currentPageId,
|
||||
appDefinition,
|
||||
appDefinitionChanged,
|
||||
focusedParentIdRef.current,
|
||||
JSON.parse(cliptext)
|
||||
JSON.parse(cliptext),
|
||||
true
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Clipboard API is not available in this browser.');
|
||||
}
|
||||
}
|
||||
enableReleasedVersionPopupState();
|
||||
},
|
||||
[isContainerFocused, appDefinition, focusedParentIdRef]
|
||||
[isContainerFocused, appDefinition, focusedParentIdRef.current]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setBoxes(components);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(components)]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e) => {
|
||||
if (canvasRef.current.contains(e.target) || document.getElementById('modal-container')?.contains(e.target)) {
|
||||
|
|
@ -132,11 +152,6 @@ export const Container = ({
|
|||
return () => document.removeEventListener('click', handleClick);
|
||||
}, [isContainerFocused, canvasRef]);
|
||||
|
||||
useEffect(() => {
|
||||
setBoxes(components);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(components)]);
|
||||
|
||||
//listening to no of component change to handle addition/deletion of widgets
|
||||
const noOfBoxs = Object.values(boxes || []).length;
|
||||
useEffect(() => {
|
||||
|
|
@ -157,9 +172,6 @@ export const Container = ({
|
|||
[boxes]
|
||||
);
|
||||
|
||||
// Dont update first time to skip
|
||||
// redundant save on app definition load
|
||||
const firstUpdate = useRef(true);
|
||||
useEffect(() => {
|
||||
if (firstUpdate.current) {
|
||||
firstUpdate.current = false;
|
||||
|
|
@ -177,7 +189,26 @@ export const Container = ({
|
|||
},
|
||||
};
|
||||
|
||||
appDefinitionChanged(newDefinition);
|
||||
//need to check if a new component is added or deleted
|
||||
|
||||
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
|
||||
const newComponents = boxes;
|
||||
|
||||
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
|
||||
|
||||
const opts = _.isEmpty(paramUpdatesOptsRef.current) ? { containerChanges: true } : paramUpdatesOptsRef.current;
|
||||
|
||||
paramUpdatesOptsRef.current = {};
|
||||
|
||||
if (componendAdded) {
|
||||
opts.componentAdded = true;
|
||||
}
|
||||
|
||||
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
|
||||
if (shouldUpdate) {
|
||||
appDefinitionChanged(newDefinition, opts);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [boxes]);
|
||||
|
||||
|
|
@ -385,18 +416,18 @@ export const Container = ({
|
|||
);
|
||||
|
||||
const paramUpdated = useCallback(
|
||||
(id, param, value) => {
|
||||
if (Object.keys(value).length > 0) {
|
||||
(id, param, value, opts = {}) => {
|
||||
if (Object.keys(value)?.length > 0) {
|
||||
setBoxes((boxes) =>
|
||||
update(boxes, {
|
||||
[id]: {
|
||||
$merge: {
|
||||
component: {
|
||||
...boxes[id].component,
|
||||
...boxes[id]?.component,
|
||||
definition: {
|
||||
...boxes[id].component.definition,
|
||||
...boxes[id]?.component?.definition,
|
||||
properties: {
|
||||
...boxes[id].component.definition.properties,
|
||||
...boxes?.[id]?.component?.definition?.properties,
|
||||
[param]: value,
|
||||
},
|
||||
},
|
||||
|
|
@ -405,9 +436,12 @@ export const Container = ({
|
|||
},
|
||||
})
|
||||
);
|
||||
if (!_.isEmpty(opts)) {
|
||||
paramUpdatesOptsRef.current = opts;
|
||||
}
|
||||
}
|
||||
},
|
||||
[setBoxes]
|
||||
[boxes, setBoxes]
|
||||
);
|
||||
|
||||
const handleAddThread = async (e) => {
|
||||
|
|
@ -504,7 +538,7 @@ export const Container = ({
|
|||
const componentWithChildren = {};
|
||||
Object.keys(components).forEach((key) => {
|
||||
const component = components[key];
|
||||
const { parent } = component;
|
||||
const parent = component?.component?.parent;
|
||||
if (parent) {
|
||||
componentWithChildren[parent] = {
|
||||
...componentWithChildren[parent],
|
||||
|
|
@ -620,7 +654,8 @@ export const Container = ({
|
|||
const canShowInCurrentLayout =
|
||||
box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
|
||||
const addDefaultChildren = box.withDefaultChildren;
|
||||
if (!box.parent && resolveReferences(canShowInCurrentLayout, currentState)) {
|
||||
|
||||
if (!box.component.parent && resolveReferences(canShowInCurrentLayout, currentState)) {
|
||||
return (
|
||||
<DraggableBox
|
||||
className={showComments && 'pointer-events-none'}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ export const DraggableBox = React.memo(
|
|||
style={getStyles(isDragging, isSelectedComponent)}
|
||||
>
|
||||
<Rnd
|
||||
maxWidth={canvasWidth}
|
||||
style={{ ...style }}
|
||||
resizeGrid={[gridWidth, 10]}
|
||||
dragGrid={[gridWidth, 10]}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import { Confirm } from '../Viewer/Confirm';
|
||||
|
|
@ -14,18 +14,16 @@ import ExportAppModal from '../../HomePage/ExportAppModal';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
|
||||
|
||||
export const GlobalSettings = ({
|
||||
globalSettings,
|
||||
globalSettingsChanged,
|
||||
darkMode,
|
||||
toggleAppMaintenance,
|
||||
is_maintenance_on,
|
||||
app,
|
||||
isMaintenanceOn,
|
||||
backgroundFxQuery,
|
||||
realState,
|
||||
handleSlugChange,
|
||||
slug: oldSlug,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { hideHeader, canvasMaxWidth, canvasMaxWidthType, canvasBackgroundColor } = globalSettings;
|
||||
|
|
@ -37,6 +35,7 @@ export const GlobalSettings = ({
|
|||
const [slug, setSlug] = useState({ value: null, error: '' });
|
||||
const [slugProgress, setSlugProgress] = useState(false);
|
||||
const [isSlugUpdated, setSlugUpdatedState] = useState(false);
|
||||
const { updateState } = useAppDataActions();
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
|
|
@ -44,6 +43,8 @@ export const GlobalSettings = ({
|
|||
shallow
|
||||
);
|
||||
|
||||
const { app, slug: oldSlug } = useAppInfo();
|
||||
|
||||
const coverStyles = {
|
||||
position: 'fixed',
|
||||
top: '0px',
|
||||
|
|
@ -58,6 +59,7 @@ export const GlobalSettings = ({
|
|||
special chars or spaces in their app slugs
|
||||
*/
|
||||
const existedSlugErrors = validateName(oldSlug, 'App slug', true, false, false, false);
|
||||
|
||||
setSlug({ value: oldSlug, error: existedSlugErrors.errorMsg });
|
||||
}, [oldSlug]);
|
||||
|
||||
|
|
@ -79,9 +81,11 @@ export const GlobalSettings = ({
|
|||
error: '',
|
||||
});
|
||||
setSlugProgress(false);
|
||||
handleSlugChange(value);
|
||||
setSlugUpdatedState(true);
|
||||
replaceEditorURL(value, realState?.page?.handle);
|
||||
updateState({
|
||||
slug: value,
|
||||
});
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
setSlug({
|
||||
|
|
@ -117,12 +121,13 @@ export const GlobalSettings = ({
|
|||
outline: showPicker && '1px solid var(--indigo9)',
|
||||
boxShadow: showPicker && '0px 0px 0px 1px #C6D4F9',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Confirm
|
||||
show={showConfirmation}
|
||||
message={
|
||||
is_maintenance_on
|
||||
isMaintenanceOn
|
||||
? 'Users will now be able to launch the released version of this app, do you wish to continue?'
|
||||
: 'Users will not be able to launch the app until maintenance mode is turned off, do you wish to continue?'
|
||||
}
|
||||
|
|
@ -219,7 +224,7 @@ export const GlobalSettings = ({
|
|||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={hideHeader}
|
||||
onChange={(e) => globalSettingsChanged('hideHeader', e.target.checked)}
|
||||
onChange={(e) => globalSettingsChanged({ hideHeader: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -232,7 +237,7 @@ export const GlobalSettings = ({
|
|||
data-cy={`toggle-maintenance-mode`}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={is_maintenance_on}
|
||||
checked={isMaintenanceOn}
|
||||
onChange={() => setConfirmationShow(true)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -251,7 +256,7 @@ export const GlobalSettings = ({
|
|||
placeholder={'0'}
|
||||
onChange={(e) => {
|
||||
const width = e.target.value;
|
||||
if (!Number.isNaN(width) && width >= 0) globalSettingsChanged('canvasMaxWidth', width);
|
||||
if (!Number.isNaN(width) && width >= 0) globalSettingsChanged({ canvasMaxWidth: width });
|
||||
}}
|
||||
value={canvasMaxWidth}
|
||||
/>
|
||||
|
|
@ -261,12 +266,16 @@ export const GlobalSettings = ({
|
|||
aria-label="Select canvas width type"
|
||||
onChange={(event) => {
|
||||
const newCanvasMaxWidthType = event.currentTarget.value;
|
||||
globalSettingsChanged('canvasMaxWidthType', newCanvasMaxWidthType);
|
||||
const options = {
|
||||
canvasMaxWidthType: newCanvasMaxWidthType,
|
||||
};
|
||||
|
||||
if (newCanvasMaxWidthType === '%') {
|
||||
globalSettingsChanged('canvasMaxWidth', 100);
|
||||
options.canvasMaxWidth = 100;
|
||||
} else if (newCanvasMaxWidthType === 'px') {
|
||||
globalSettingsChanged('canvasMaxWidth', 1292);
|
||||
options.canvasMaxWidth = 1292;
|
||||
}
|
||||
globalSettingsChanged(options);
|
||||
}}
|
||||
>
|
||||
<option value="%" selected={canvasMaxWidthType === '%'}>
|
||||
|
|
@ -279,26 +288,7 @@ export const GlobalSettings = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="d-flex mb-3">
|
||||
<span className="w-full m-auto" data-cy={`label-max-canvas-height`}>
|
||||
{t('leftSidebar.Settings.maxHeightOfCanvas', 'Max height of canvas')}
|
||||
</span>
|
||||
<div className="global-popover-div-wrap global-popover-div-wrap-width">
|
||||
<div className="input-with-icon">
|
||||
<input
|
||||
data-cy="maximum-canvas-height-input-field"
|
||||
type="text"
|
||||
className={`form-control form-control-sm maximum-canvas-height-input-field`}
|
||||
placeholder={'0'}
|
||||
onChange={(e) => {
|
||||
const height = e.target.value;
|
||||
if (!Number.isNaN(height) && height <= 2400) globalSettingsChanged('canvasMaxHeight', height);
|
||||
}}
|
||||
value={canvasMaxHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="d-flex justify-content-between mb-3">
|
||||
<span className="pt-2" data-cy={`label-bg-canvas`}>
|
||||
{t('leftSidebar.Settings.backgroundColorOfCanvas', 'Canvas bavkground')}
|
||||
|
|
@ -313,8 +303,12 @@ export const GlobalSettings = ({
|
|||
onFocus={() => setShowPicker(true)}
|
||||
color={canvasBackgroundColor}
|
||||
onChangeComplete={(color) => {
|
||||
globalSettingsChanged('canvasBackgroundColor', [color.hex, color.rgb]);
|
||||
globalSettingsChanged('backgroundFxQuery', color.hex);
|
||||
const options = {
|
||||
canvasBackgroundColor: [color.hex, color.rgb],
|
||||
backgroundFxQuery: color.hex,
|
||||
};
|
||||
|
||||
globalSettingsChanged(options);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -357,8 +351,11 @@ export const GlobalSettings = ({
|
|||
className="canvas-hinter-wrap"
|
||||
lineNumbers={false}
|
||||
onChange={(color) => {
|
||||
globalSettingsChanged('canvasBackgroundColor', resolveReferences(color, realState));
|
||||
globalSettingsChanged('backgroundFxQuery', color);
|
||||
const options = {
|
||||
canvasBackgroundColor: resolveReferences(color, realState),
|
||||
backgroundFxQuery: color,
|
||||
};
|
||||
globalSettingsChanged(options);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -14,30 +14,29 @@ import { useUpdatePresence } from '@y-presence/react';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useAppInfo, useCurrentUser } from '@/_stores/appDataStore';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { redirectToDashboard } from '@/_helpers/routes';
|
||||
|
||||
export default function EditorHeader({
|
||||
M,
|
||||
app,
|
||||
appVersionPreviewLink,
|
||||
slug,
|
||||
appId,
|
||||
canUndo,
|
||||
canRedo,
|
||||
handleUndo,
|
||||
handleRedo,
|
||||
isSaving,
|
||||
saveError,
|
||||
onNameChanged,
|
||||
setAppDefinitionFromVersion,
|
||||
handleSlugChange,
|
||||
onVersionRelease,
|
||||
saveEditingVersion,
|
||||
onVersionDelete,
|
||||
currentUser,
|
||||
slug,
|
||||
darkMode,
|
||||
}) {
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
const { isSaving, appId, appName, app, isPublic, appVersionPreviewLink } = useAppInfo();
|
||||
|
||||
const { isVersionReleased, editingVersion } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
|
|
@ -48,6 +47,7 @@ export default function EditorHeader({
|
|||
const currentState = useCurrentState();
|
||||
|
||||
const updatePresence = useUpdatePresence();
|
||||
|
||||
useEffect(() => {
|
||||
const initialPresence = {
|
||||
firstName: currentUser?.first_name ?? '',
|
||||
|
|
@ -62,7 +62,9 @@ export default function EditorHeader({
|
|||
updatePresence(initialPresence);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentUser]);
|
||||
const handleLogoClick = () => {
|
||||
|
||||
const handleLogoClick = (e) => {
|
||||
e.preventDefault();
|
||||
// Force a reload for clearing interval triggers
|
||||
redirectToDashboard();
|
||||
};
|
||||
|
|
@ -96,7 +98,7 @@ export default function EditorHeader({
|
|||
}}
|
||||
>
|
||||
<div className="global-settings-app-wrapper p-0 m-0 ">
|
||||
<EditAppName appId={app.id} appName={app.name} onNameChanged={onNameChanged} />
|
||||
<EditAppName appId={appId} appName={appName} onNameChanged={onNameChanged} />
|
||||
</div>
|
||||
<HeaderActions canUndo={canUndo} canRedo={canRedo} handleUndo={handleUndo} handleRedo={handleRedo} />
|
||||
<div className="d-flex align-items-center">
|
||||
|
|
@ -133,9 +135,9 @@ export default function EditorHeader({
|
|||
{editingVersion && (
|
||||
<AppVersionsManager
|
||||
appId={appId}
|
||||
releasedVersionId={app.current_version_id}
|
||||
setAppDefinitionFromVersion={setAppDefinitionFromVersion}
|
||||
onVersionDelete={onVersionDelete}
|
||||
isPublic={isPublic ?? false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -147,15 +149,16 @@ export default function EditorHeader({
|
|||
>
|
||||
<div className="navbar-nav flex-row order-md-last release-buttons ">
|
||||
<div className="nav-item">
|
||||
{app.id && (
|
||||
{appId && (
|
||||
<ManageAppUsers
|
||||
app={app}
|
||||
appId={appId}
|
||||
slug={slug}
|
||||
darkMode={darkMode}
|
||||
handleSlugChange={handleSlugChange}
|
||||
isVersionReleased={isVersionReleased}
|
||||
pageHandle={currentState?.page?.handle}
|
||||
M={M}
|
||||
isPublic={isPublic ?? false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -172,14 +175,12 @@ export default function EditorHeader({
|
|||
</Link>
|
||||
</div>
|
||||
<div className="nav-item dropdown">
|
||||
{app.id && (
|
||||
<ReleaseVersionButton
|
||||
appId={app.id}
|
||||
appName={app.name}
|
||||
onVersionRelease={onVersionRelease}
|
||||
saveEditingVersion={saveEditingVersion}
|
||||
/>
|
||||
)}
|
||||
<ReleaseVersionButton
|
||||
appId={appId}
|
||||
appName={appName}
|
||||
onVersionRelease={onVersionRelease}
|
||||
saveEditingVersion={saveEditingVersion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function GotoApp({ getAllApps, event, handlerChanged, eventIndex, darkMod
|
|||
<div key={index} className="row input-group mt-1">
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={event.queryParams[index][0]}
|
||||
initialValue={event?.queryParams?.[index]?.[0]}
|
||||
onChange={(value) => queryParamChangeHandler(index, 0, value)}
|
||||
mode="javascript"
|
||||
height={30}
|
||||
|
|
@ -77,7 +77,7 @@ export function GotoApp({ getAllApps, event, handlerChanged, eventIndex, darkMod
|
|||
</div>
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={event.queryParams[index][1]}
|
||||
initialValue={event?.queryParams?.[index]?.[1]}
|
||||
onChange={(value) => queryParamChangeHandler(index, 1, value)}
|
||||
mode="javascript"
|
||||
height={30}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function SwitchPage({ getPages, event, handlerChanged, eventIndex, darkMo
|
|||
<div key={index} className="row input-group mt-1">
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={event.queryParams[index][0]}
|
||||
initialValue={event?.queryParams?.[index]?.[0]}
|
||||
onChange={(value) => queryParamChangeHandler(index, 0, value)}
|
||||
mode="javascript"
|
||||
className="form-control codehinter-query-editor-input"
|
||||
|
|
@ -79,7 +79,7 @@ export function SwitchPage({ getPages, event, handlerChanged, eventIndex, darkMo
|
|||
</div>
|
||||
<div className="col">
|
||||
<CodeHinter
|
||||
initialValue={event.queryParams[index][1]}
|
||||
initialValue={event?.queryParams?.[index]?.[1]}
|
||||
onChange={(value) => queryParamChangeHandler(index, 1, value)}
|
||||
mode="javascript"
|
||||
className="form-control codehinter-query-editor-input"
|
||||
|
|
|
|||
|
|
@ -56,17 +56,16 @@ class Chart extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.state;
|
||||
const data = this.state.component.component.definition.properties.data;
|
||||
const { dataQueries, component, paramUpdated, componentMeta, components, currentState } = this.props;
|
||||
const data = this.props.component.component.definition.properties.data; // since component is not unmounting on every render in current scenario
|
||||
|
||||
const jsonDescription = this.state.component.component.definition.properties.jsonDescription;
|
||||
const jsonDescription = this.props.component.component.definition.properties.jsonDescription;
|
||||
|
||||
const plotFromJson = resolveReferences(
|
||||
this.state.component.component.definition.properties.plotFromJson?.value,
|
||||
this.props.component.component.definition.properties.plotFromJson?.value,
|
||||
currentState
|
||||
);
|
||||
|
||||
const chartType = this.state.component.component.definition.properties.type.value;
|
||||
const chartType = this.props.component.component.definition.properties.type.value;
|
||||
|
||||
let items = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -98,8 +98,9 @@ export const baseComponentProperties = (
|
|||
isOpen: true,
|
||||
children: (
|
||||
<EventManager
|
||||
component={component}
|
||||
componentMeta={componentMeta}
|
||||
sourceId={component?.id}
|
||||
eventSourceType="component"
|
||||
eventMetaDefinition={componentMeta}
|
||||
currentState={currentState}
|
||||
dataQueries={dataQueries}
|
||||
components={allComponents}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const Form = ({
|
|||
const { id } = component;
|
||||
const newOptions = [{ name: 'None', value: 'none' }];
|
||||
Object.entries(allComponents).forEach(([componentId, component]) => {
|
||||
if (component.parent === id && component?.component?.component === 'Button') {
|
||||
if (component.component.parent === id && component?.component?.component === 'Button') {
|
||||
newOptions.push({ name: component.component.name, value: componentId });
|
||||
}
|
||||
});
|
||||
|
|
@ -94,8 +94,9 @@ export const baseComponentProperties = (
|
|||
isOpen: true,
|
||||
children: (
|
||||
<EventManager
|
||||
component={component}
|
||||
componentMeta={componentMeta}
|
||||
sourceId={component?.id}
|
||||
eventSourceType="component"
|
||||
eventMetaDefinition={componentMeta}
|
||||
currentState={currentState}
|
||||
dataQueries={dataQueries}
|
||||
components={allComponents}
|
||||
|
|
|
|||
|
|
@ -139,8 +139,9 @@ export function Icon({ componentMeta, darkMode, ...restProps }) {
|
|||
isOpen: false,
|
||||
children: (
|
||||
<EventManager
|
||||
component={component}
|
||||
componentMeta={componentMeta}
|
||||
sourceId={component?.id}
|
||||
eventSourceType="component"
|
||||
eventMetaDefinition={componentMeta}
|
||||
currentState={currentState}
|
||||
dataQueries={dataQueries}
|
||||
components={allComponents}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import List from '@/ToolJetUI/List/List';
|
|||
import { capitalize, has } from 'lodash';
|
||||
import NoListItem from './NoListItem';
|
||||
import { ProgramaticallyHandleProperties } from './ProgramaticallyHandleProperties';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
class TableComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -73,13 +74,13 @@ class TableComponent extends React.Component {
|
|||
onActionButtonPropertyChanged = (index, property, value) => {
|
||||
const actions = this.props.component.component.definition.properties.actions;
|
||||
actions.value[index][property] = value;
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', actions.value, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', actions.value, 'properties', true);
|
||||
};
|
||||
|
||||
actionButtonEventsChanged = (events, index) => {
|
||||
let actions = this.props.component.component.definition.properties.actions.value;
|
||||
actions[index]['events'] = events;
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', actions, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', actions, 'properties', true);
|
||||
};
|
||||
|
||||
actionButtonEventUpdated = (event, value, extraData) => {
|
||||
|
|
@ -91,7 +92,7 @@ class TableComponent extends React.Component {
|
|||
actionId: value,
|
||||
};
|
||||
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties', true);
|
||||
};
|
||||
|
||||
actionButtonEventOptionUpdated = (event, option, value, extraData) => {
|
||||
|
|
@ -106,7 +107,7 @@ class TableComponent extends React.Component {
|
|||
[option]: value,
|
||||
};
|
||||
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValues, 'properties', true);
|
||||
};
|
||||
|
||||
columnEventChanged = (columnForWhichEventsAreChanged, events) => {
|
||||
|
|
@ -454,22 +455,18 @@ class TableComponent extends React.Component {
|
|||
/>
|
||||
</div>
|
||||
<EventManager
|
||||
component={{
|
||||
component: {
|
||||
definition: {
|
||||
events: column.events ?? [],
|
||||
},
|
||||
},
|
||||
}}
|
||||
sourceId={this.props?.component?.id}
|
||||
eventSourceType="table_column"
|
||||
hideEmptyEventsAlert={true}
|
||||
componentMeta={{ events: { onChange: { displayName: 'On change' } } }}
|
||||
currentState={this.props.currentState}
|
||||
eventMetaDefinition={{ events: { onChange: { displayName: 'On change' } } }}
|
||||
currentState={this.state.currentState}
|
||||
dataQueries={this.props.dataQueries}
|
||||
components={this.props.components}
|
||||
eventsChanged={(events) => this.columnEventChanged(column, events)}
|
||||
apps={this.props.apps}
|
||||
popOverCallback={(showing) => {
|
||||
this.setColumnPopoverRootCloseBlocker('event-manager', showing);
|
||||
this.setState({ actionPopOverRootClose: !showing });
|
||||
this.setState({ showPopOver: showing });
|
||||
}}
|
||||
pages={this.props.pages}
|
||||
/>
|
||||
|
|
@ -751,6 +748,18 @@ class TableComponent extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
deleteEvents = (ref, eventTarget) => {
|
||||
const events = useAppDataStore.getState().events.filter((event) => event.target === eventTarget);
|
||||
|
||||
const toDelete = events?.filter((e) => e.event?.ref === ref.ref);
|
||||
|
||||
return new Promise.all(
|
||||
toDelete?.forEach((e) => {
|
||||
return useAppDataStore.getState().actions.deleteAppVersionEventHandler(e.id);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
actionPopOver = (action, index) => {
|
||||
const dummyComponentForActionButton = {
|
||||
component: {
|
||||
|
|
@ -760,6 +769,8 @@ class TableComponent extends React.Component {
|
|||
},
|
||||
};
|
||||
|
||||
const actionRef = { ref: `${action?.name}` };
|
||||
|
||||
return (
|
||||
<Popover id="popover-basic" className={`${this.props.darkMode && 'dark-theme'}`}>
|
||||
<Popover.Body>
|
||||
|
|
@ -827,8 +838,12 @@ class TableComponent extends React.Component {
|
|||
paramType="properties"
|
||||
/>
|
||||
<EventManager
|
||||
//!have to check
|
||||
component={dummyComponentForActionButton}
|
||||
componentMeta={{ events: { onClick: { displayName: 'On click' } } }}
|
||||
sourceId={this.props?.component?.id}
|
||||
eventSourceType="table_action"
|
||||
customEventRefs={actionRef}
|
||||
eventMetaDefinition={{ events: { onClick: { displayName: 'On click' } } }}
|
||||
currentState={this.state.currentState}
|
||||
dataQueries={this.props.dataQueries}
|
||||
components={this.props.components}
|
||||
|
|
@ -840,7 +855,10 @@ class TableComponent extends React.Component {
|
|||
}}
|
||||
pages={this.props.pages}
|
||||
/>
|
||||
<button className="btn btn-sm btn-outline-danger mt-2 col" onClick={() => this.removeAction(index)}>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger mt-2 col"
|
||||
onClick={() => this.removeAction(index, actionRef)}
|
||||
>
|
||||
{this.props.t('widget.Table.remove', 'Remove')}
|
||||
</button>
|
||||
</Popover.Body>
|
||||
|
|
@ -891,20 +909,21 @@ class TableComponent extends React.Component {
|
|||
const columns = this.props.component.component.definition.properties.columns;
|
||||
const newValue = columns.value;
|
||||
newValue.push({ name: this.generateNewColumnName(columns.value), id: uuidv4() });
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties');
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true);
|
||||
};
|
||||
|
||||
addNewAction = () => {
|
||||
const actions = this.props.component.component.definition.properties.actions;
|
||||
const newValue = actions ? actions.value : [];
|
||||
newValue.push({ name: computeActionName(actions), buttonText: 'Button', events: [] });
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValue, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValue, 'properties', true);
|
||||
};
|
||||
|
||||
removeAction = (index) => {
|
||||
removeAction = (index, ref) => {
|
||||
const newValue = this.props.component.component.definition.properties.actions.value;
|
||||
newValue.splice(index, 1);
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValue, 'properties');
|
||||
this.props.paramUpdated({ name: 'actions' }, 'value', newValue, 'properties', true);
|
||||
this.deleteEvents(ref, 'table_action');
|
||||
};
|
||||
|
||||
onColumnItemChange = (index, item, value) => {
|
||||
|
|
@ -914,7 +933,8 @@ class TableComponent extends React.Component {
|
|||
column[item] = value;
|
||||
const newColumns = columns.value;
|
||||
newColumns[index] = column;
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties');
|
||||
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newColumns, 'properties', true);
|
||||
};
|
||||
|
||||
getItemStyle = (isDragging, draggableStyle) => ({
|
||||
|
|
@ -922,11 +942,11 @@ class TableComponent extends React.Component {
|
|||
...draggableStyle,
|
||||
});
|
||||
|
||||
removeColumn = (index) => {
|
||||
removeColumn = (index, ref) => {
|
||||
const columns = this.props.component.component.definition.properties.columns;
|
||||
const newValue = columns.value;
|
||||
const removedColumns = newValue.splice(index, 1);
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties');
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', newValue, 'properties', true);
|
||||
|
||||
const existingcolumnDeletionHistory =
|
||||
this.props.component.component.definition.properties.columnDeletionHistory?.value ?? [];
|
||||
|
|
@ -934,14 +954,16 @@ class TableComponent extends React.Component {
|
|||
...existingcolumnDeletionHistory,
|
||||
...removedColumns.map((column) => column.key || column.name),
|
||||
];
|
||||
this.props.paramUpdated({ name: 'columnDeletionHistory' }, 'value', newcolumnDeletionHistory, 'properties');
|
||||
this.props.paramUpdated({ name: 'columnDeletionHistory' }, 'value', newcolumnDeletionHistory, 'properties', true);
|
||||
|
||||
this.deleteEvents(ref, 'table_column');
|
||||
};
|
||||
|
||||
reorderColumns = (startIndex, endIndex) => {
|
||||
const result = this.props.component.component.definition.properties.columns.value;
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', result, 'properties');
|
||||
this.props.paramUpdated({ name: 'columns' }, 'value', result, 'properties', true);
|
||||
};
|
||||
|
||||
onDragEnd({ source, destination }) {
|
||||
|
|
@ -959,7 +981,6 @@ class TableComponent extends React.Component {
|
|||
|
||||
const columns = component.component.definition.properties.columns;
|
||||
const actions = component.component.definition.properties.actions || { value: [] };
|
||||
|
||||
if (!component.component.definition.properties.displaySearchBox)
|
||||
paramUpdated({ name: 'displaySearchBox' }, 'value', true, 'properties');
|
||||
const displaySearchBox = component.component.definition.properties.displaySearchBox.value;
|
||||
|
|
@ -1056,7 +1077,8 @@ class TableComponent extends React.Component {
|
|||
enableActionsMenu
|
||||
isEditable={item.isEditable === '{{true}}'}
|
||||
onMenuOptionClick={(listItem, menuOptionLabel) => {
|
||||
if (menuOptionLabel === 'Delete') this.removeColumn(index);
|
||||
if (menuOptionLabel === 'Delete')
|
||||
this.removeColumn(index, `${item.name}-${index}`);
|
||||
}}
|
||||
darkMode={darkMode}
|
||||
menuActions={[
|
||||
|
|
@ -1142,8 +1164,11 @@ class TableComponent extends React.Component {
|
|||
isOpen: true,
|
||||
children: (
|
||||
<EventManager
|
||||
//!have to check
|
||||
component={component}
|
||||
componentMeta={componentMeta}
|
||||
sourceId={this.props?.component?.id}
|
||||
eventSourceType="component"
|
||||
eventMetaDefinition={componentMeta}
|
||||
currentState={currentState}
|
||||
dataQueries={dataQueries}
|
||||
components={components}
|
||||
|
|
|
|||
|
|
@ -14,25 +14,25 @@ import defaultStyles from '@/_ui/Select/styles';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import RunjsParameters from './ActionConfigurationPanels/RunjsParamters';
|
||||
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
|
||||
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
|
||||
import { isQueryRunnable } from '@/_helpers/utils';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import ManageEventButton from './ManageEventButton';
|
||||
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
|
||||
import NoListItem from './Components/Table/NoListItem';
|
||||
import ManageEventButton from './ManageEventButton';
|
||||
|
||||
export const EventManager = ({
|
||||
component,
|
||||
componentMeta,
|
||||
currentState = {},
|
||||
sourceId,
|
||||
eventSourceType,
|
||||
eventMetaDefinition,
|
||||
components,
|
||||
eventsChanged,
|
||||
apps,
|
||||
excludeEvents,
|
||||
popOverCallback,
|
||||
popoverPlacement,
|
||||
pages,
|
||||
hideEmptyEventsAlert,
|
||||
callerQueryId,
|
||||
customEventRefs = undefined,
|
||||
}) => {
|
||||
const dataQueries = useDataQueriesStore(({ dataQueries = [] }) => {
|
||||
if (callerQueryId) {
|
||||
|
|
@ -41,13 +41,35 @@ export const EventManager = ({
|
|||
}
|
||||
return dataQueries;
|
||||
}, shallow);
|
||||
const [events, setEvents] = useState(() => component.component.definition.events || []);
|
||||
const { apps, appId, events: allAppEvents } = useAppInfo();
|
||||
|
||||
const { updateAppVersionEventHandlers, createAppVersionEventHandlers, deleteAppVersionEventHandler } =
|
||||
useAppDataActions();
|
||||
|
||||
const currentEvents = allAppEvents.filter((event) => {
|
||||
if (customEventRefs) {
|
||||
if (event.event.ref !== customEventRefs.ref) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return event.sourceId === sourceId && event.target === eventSourceType;
|
||||
});
|
||||
|
||||
const [events, setEvents] = useState([]);
|
||||
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setEvents(component.component.definition.events || []);
|
||||
}, [component?.component?.definition?.events]);
|
||||
if (_.isEqual(currentEvents, events)) return;
|
||||
|
||||
const sortedEvents = currentEvents.sort((a, b) => {
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
setEvents(sortedEvents || []);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(currentEvents)]);
|
||||
|
||||
let actionOptions = ActionTypes.map((action) => {
|
||||
return { name: action.name, value: action.id };
|
||||
|
|
@ -100,11 +122,11 @@ export const EventManager = ({
|
|||
excludeEvents = excludeEvents || [];
|
||||
|
||||
/* Filter events based on excludesEvents ( a list of event ids to exclude ) */
|
||||
let possibleEvents = Object.keys(componentMeta.events)
|
||||
let possibleEvents = Object.keys(eventMetaDefinition.events)
|
||||
.filter((eventId) => !excludeEvents.includes(eventId))
|
||||
.map((eventId) => {
|
||||
return {
|
||||
name: componentMeta.events[eventId].displayName,
|
||||
name: eventMetaDefinition?.events[eventId]?.displayName,
|
||||
value: eventId,
|
||||
};
|
||||
});
|
||||
|
|
@ -151,7 +173,7 @@ export const EventManager = ({
|
|||
const actions = targetComponentMeta.actions;
|
||||
|
||||
const options = actions.map((action) => ({
|
||||
name: action.displayName,
|
||||
name: action?.displayName,
|
||||
value: action.handle,
|
||||
}));
|
||||
|
||||
|
|
@ -172,7 +194,7 @@ export const EventManager = ({
|
|||
|
||||
function getComponentActionDefaultParams(componentId, actionHandle) {
|
||||
const action = getAction(componentId, actionHandle);
|
||||
const defaultParams = (action.params ?? []).map((param) => ({
|
||||
const defaultParams = (action?.params ?? []).map((param) => ({
|
||||
handle: param.handle,
|
||||
value: param.defaultValue,
|
||||
}));
|
||||
|
|
@ -182,7 +204,7 @@ export const EventManager = ({
|
|||
function getAllApps() {
|
||||
let appsOptionsList = [];
|
||||
apps
|
||||
.filter((item) => item.slug !== undefined)
|
||||
.filter((item) => item.slug !== undefined && item.id !== appId)
|
||||
.forEach((item) => {
|
||||
appsOptionsList.push({
|
||||
name: item.name,
|
||||
|
|
@ -208,51 +230,105 @@ export const EventManager = ({
|
|||
}));
|
||||
}
|
||||
|
||||
function handlerChanged(index, param, value) {
|
||||
let newEvents = [...events];
|
||||
|
||||
function handleQueryChange(index, updates) {
|
||||
let newEvents = _.cloneDeep(events);
|
||||
let updatedEvent = newEvents[index];
|
||||
updatedEvent[param] = value;
|
||||
|
||||
updatedEvent.event = {
|
||||
...updatedEvent.event,
|
||||
...updates,
|
||||
};
|
||||
|
||||
newEvents[index] = updatedEvent;
|
||||
|
||||
setEvents(newEvents);
|
||||
eventsChanged(newEvents);
|
||||
updateAppVersionEventHandlers(
|
||||
[
|
||||
{
|
||||
event_id: updatedEvent.id,
|
||||
diff: updatedEvent,
|
||||
},
|
||||
],
|
||||
'update'
|
||||
);
|
||||
}
|
||||
|
||||
function handlerChanged(index, param, value) {
|
||||
let newEvents = _.cloneDeep(events);
|
||||
|
||||
let updatedEvent = newEvents[index];
|
||||
updatedEvent.event[param] = value;
|
||||
|
||||
if (param === 'componentSpecificActionHandle') {
|
||||
const getDefault = getComponentActionDefaultParams(updatedEvent.event?.componentId, value);
|
||||
updatedEvent.event['componentSpecificActionParams'] = getDefault;
|
||||
}
|
||||
|
||||
newEvents[index] = updatedEvent;
|
||||
|
||||
updateAppVersionEventHandlers(
|
||||
[
|
||||
{
|
||||
event_id: updatedEvent.id,
|
||||
diff: updatedEvent,
|
||||
},
|
||||
],
|
||||
'update'
|
||||
);
|
||||
}
|
||||
|
||||
function removeHandler(index) {
|
||||
let newEvents = component.component.definition.events;
|
||||
newEvents.splice(index, 1);
|
||||
setEvents(newEvents);
|
||||
eventsChanged(newEvents);
|
||||
const eventsHandler = _.cloneDeep(events);
|
||||
|
||||
const eventId = eventsHandler[index].id;
|
||||
|
||||
deleteAppVersionEventHandler(eventId);
|
||||
}
|
||||
|
||||
function addHandler() {
|
||||
let newEvents = component.component.definition.events;
|
||||
newEvents.push({
|
||||
eventId: Object.keys(componentMeta.events)[0],
|
||||
actionId: 'show-alert',
|
||||
message: 'Hello world!',
|
||||
alertType: 'info',
|
||||
let newEvents = events;
|
||||
const eventIndex = newEvents.length;
|
||||
|
||||
createAppVersionEventHandlers({
|
||||
event: {
|
||||
eventId: Object.keys(eventMetaDefinition?.events)[0],
|
||||
actionId: 'show-alert',
|
||||
message: 'Hello world!',
|
||||
alertType: 'info',
|
||||
...customEventRefs,
|
||||
},
|
||||
eventType: eventSourceType,
|
||||
attachedTo: sourceId,
|
||||
index: eventIndex,
|
||||
});
|
||||
setEvents(newEvents);
|
||||
eventsChanged(newEvents);
|
||||
}
|
||||
|
||||
//following two are functions responsible for on change and value for the control specific actions
|
||||
const onChangeHandlerForComponentSpecificActionHandle = (value, index, param, event) => {
|
||||
const newParam = { ...param, value: value };
|
||||
const params = event?.componentSpecificActionParams ?? [];
|
||||
const newParams = params.map((paramOfParamList) =>
|
||||
paramOfParamList.handle === param.handle ? newParam : paramOfParamList
|
||||
);
|
||||
|
||||
const newParams =
|
||||
params.length > 0
|
||||
? params.map((paramOfParamList) => {
|
||||
return paramOfParamList.handle === param.handle ? newParam : paramOfParamList;
|
||||
})
|
||||
: [newParam];
|
||||
|
||||
return handlerChanged(index, 'componentSpecificActionParams', newParams);
|
||||
};
|
||||
const valueForComponentSpecificActionHandle = (event, param) => {
|
||||
return (
|
||||
event?.componentSpecificActionParams?.find((paramItem) => paramItem.handle === param.handle)?.value ??
|
||||
param.defaultValue
|
||||
);
|
||||
const componentSpecificActionParamsExits = Array.isArray(event?.componentSpecificActionParams);
|
||||
const defaultValue = param.defaultValue ?? '';
|
||||
|
||||
if (componentSpecificActionParamsExits) {
|
||||
const paramValue =
|
||||
event?.componentSpecificActionParams?.find((paramItem) => paramItem.handle === param.handle)?.value ??
|
||||
defaultValue;
|
||||
|
||||
return paramValue;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
function eventPopover(event, index) {
|
||||
|
|
@ -260,10 +336,14 @@ export const EventManager = ({
|
|||
<Popover
|
||||
id="popover-basic"
|
||||
style={{ width: '350px', maxWidth: '350px' }}
|
||||
className={`${darkMode && ' dark-theme'} shadow event-manager-popover`}
|
||||
className={`${darkMode && 'dark-theme'} shadow`}
|
||||
data-cy="popover-card"
|
||||
>
|
||||
<Popover.Body>
|
||||
<Popover.Body
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-3 p-2">
|
||||
<span data-cy="event-label">{t('editor.inspector.eventManager.event', 'Event')}</span>
|
||||
|
|
@ -443,10 +523,11 @@ export const EventManager = ({
|
|||
options={dataQueries
|
||||
.filter((qry) => isQueryRunnable(qry))
|
||||
.map((qry) => ({ name: qry.name, value: qry.id }))}
|
||||
value={event.queryId}
|
||||
value={event?.queryId}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
const query = dataQueries.find((dataquery) => dataquery.id === value);
|
||||
|
||||
const parameters = (query?.options?.parameters ?? []).reduce(
|
||||
(paramObj, param) => ({
|
||||
...paramObj,
|
||||
|
|
@ -454,9 +535,12 @@ export const EventManager = ({
|
|||
}),
|
||||
{}
|
||||
);
|
||||
handlerChanged(index, 'queryId', query.id);
|
||||
handlerChanged(index, 'queryName', query.name);
|
||||
handlerChanged(index, 'parameters', parameters);
|
||||
|
||||
handleQueryChange(index, {
|
||||
queryId: query.id,
|
||||
queryName: query.name,
|
||||
parameters: parameters,
|
||||
});
|
||||
}}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={styles}
|
||||
|
|
@ -688,7 +772,6 @@ export const EventManager = ({
|
|||
value={event?.componentId}
|
||||
search={true}
|
||||
onChange={(value) => {
|
||||
handlerChanged(index, 'componentSpecificActionHandle', '');
|
||||
handlerChanged(index, 'componentId', value);
|
||||
}}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
|
|
@ -710,11 +793,6 @@ export const EventManager = ({
|
|||
search={true}
|
||||
onChange={(value) => {
|
||||
handlerChanged(index, 'componentSpecificActionHandle', value);
|
||||
handlerChanged(
|
||||
index,
|
||||
'componentSpecificActionParams',
|
||||
getComponentActionDefaultParams(event?.componentId, value)
|
||||
);
|
||||
}}
|
||||
placeholder={t('globals.select', 'Select') + '...'}
|
||||
styles={styles}
|
||||
|
|
@ -725,10 +803,10 @@ export const EventManager = ({
|
|||
</div>
|
||||
{event?.componentId &&
|
||||
event?.componentSpecificActionHandle &&
|
||||
(getAction(event?.componentId, event?.componentSpecificActionHandle).params ?? []).map((param) => (
|
||||
(getAction(event?.componentId, event?.componentSpecificActionHandle)?.params ?? []).map((param) => (
|
||||
<div className="row mt-2" key={param.handle}>
|
||||
<div className="col-3 p-1" data-cy={`action-options-${param.displayName}-field-label`}>
|
||||
{param.displayName}
|
||||
<div className="col-3 p-1" data-cy={`action-options-${param?.displayName}-field-label`}>
|
||||
{param?.displayName}
|
||||
</div>
|
||||
{param.type === 'select' ? (
|
||||
<div className="col-9" data-cy="action-options-action-selection-field">
|
||||
|
|
@ -763,7 +841,7 @@ export const EventManager = ({
|
|||
enablePreview={true}
|
||||
type={param?.type}
|
||||
fieldMeta={{ options: param?.options }}
|
||||
cyLabel={param.displayName}
|
||||
cyLabel={param?.displayName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -789,11 +867,24 @@ export const EventManager = ({
|
|||
}
|
||||
|
||||
const reorderEvents = (startIndex, endIndex) => {
|
||||
const result = [...component.component.definition.events];
|
||||
const result = _.cloneDeep(events);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
setEvents(result);
|
||||
eventsChanged(result, true);
|
||||
|
||||
const reorderedEvents = result.map((event, index) => {
|
||||
return {
|
||||
...event,
|
||||
index: index,
|
||||
};
|
||||
});
|
||||
|
||||
updateAppVersionEventHandlers(
|
||||
reorderedEvents.map((event) => ({
|
||||
event_id: event.id,
|
||||
diff: event,
|
||||
})),
|
||||
'reorder'
|
||||
);
|
||||
};
|
||||
|
||||
const onDragEnd = ({ source, destination }) => {
|
||||
|
|
@ -817,8 +908,8 @@ export const EventManager = ({
|
|||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
<div {...droppableProps} ref={innerRef}>
|
||||
{events.map((event, index) => {
|
||||
const actionMeta = ActionTypes.find((action) => action.id === event.actionId);
|
||||
const rowClassName = `card-body p-0 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`;
|
||||
const actionMeta = ActionTypes.find((action) => action.id === event.event.actionId);
|
||||
// const rowClassName = `card-body p-0 ${focusedEventIndex === index ? ' bg-azure-lt' : ''}`;
|
||||
return (
|
||||
<Draggable key={index} draggableId={`${event.eventId}-${index}`} index={index}>
|
||||
{renderDraggable((provided, snapshot) => {
|
||||
|
|
@ -831,14 +922,13 @@ export const EventManager = ({
|
|||
trigger="click"
|
||||
placement={popoverPlacement || 'left'}
|
||||
rootClose={true}
|
||||
overlay={eventPopover(event, index)}
|
||||
overlay={eventPopover(event.event, index)}
|
||||
onHide={() => setFocusedEventIndex(null)}
|
||||
onToggle={(showing) => {
|
||||
if (showing) {
|
||||
setFocusedEventIndex(index);
|
||||
} else {
|
||||
setFocusedEventIndex(null);
|
||||
eventsChanged(events);
|
||||
}
|
||||
if (typeof popOverCallback === 'function') popOverCallback(showing);
|
||||
}}
|
||||
|
|
@ -850,7 +940,7 @@ export const EventManager = ({
|
|||
{...provided.dragHandleProps}
|
||||
>
|
||||
<ManageEventButton
|
||||
eventDisplayName={componentMeta.events[event.eventId]['displayName']}
|
||||
eventDisplayName={eventMetaDefinition?.events[event.event.eventId]?.displayName}
|
||||
actionName={actionMeta.name}
|
||||
removeHandler={removeHandler}
|
||||
index={index}
|
||||
|
|
@ -888,12 +978,33 @@ export const EventManager = ({
|
|||
);
|
||||
}
|
||||
|
||||
const componentName = eventMetaDefinition?.name ? eventMetaDefinition.name : 'query';
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<>
|
||||
{renderAddHandlerBtn()}
|
||||
{!hideEmptyEventsAlert ? (
|
||||
<div className="text-left">
|
||||
<small className="color-disabled" data-cy="no-event-handler-message">
|
||||
{t(
|
||||
'editor.inspector.eventManager.emptyMessage',
|
||||
"This {{componentName}} doesn't have any event handlers",
|
||||
{
|
||||
componentName: componentName.toLowerCase(),
|
||||
}
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
{renderHandlers(events)}
|
||||
{renderAddHandlerBtn()}
|
||||
</div>
|
||||
{renderHandlers(events)}
|
||||
{renderAddHandlerBtn()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export const Inspector = ({
|
|||
selectedComponentId,
|
||||
componentDefinitionChanged,
|
||||
allComponents,
|
||||
apps,
|
||||
darkMode,
|
||||
switchSidebarTab,
|
||||
removeComponent,
|
||||
|
|
@ -66,18 +65,17 @@ export const Inspector = ({
|
|||
const dataQueries = useDataQueries();
|
||||
const component = {
|
||||
id: selectedComponentId,
|
||||
component: allComponents[selectedComponentId].component,
|
||||
component: JSON.parse(JSON.stringify(allComponents[selectedComponentId].component)),
|
||||
layouts: allComponents[selectedComponentId].layouts,
|
||||
parent: allComponents[selectedComponentId].parent,
|
||||
};
|
||||
const currentState = useCurrentState();
|
||||
const [showWidgetDeleteConfirmation, setWidgetDeleteConfirmation] = useState(false);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [tabHeight, setTabHeight] = React.useState(0);
|
||||
|
||||
const componentNameRef = useRef(null);
|
||||
const [newComponentName, setNewComponentName] = useState(component.component.name);
|
||||
const [inputRef, setInputFocus] = useFocus();
|
||||
const [selectedTab, setSelectedTab] = useState('properties');
|
||||
// const [selectedTab, setSelectedTab] = useState('properties');
|
||||
const [showHeaderActionsMenu, setShowHeaderActionsMenu] = useState(false);
|
||||
const { isVersionReleased } = useAppVersionStore(
|
||||
(state) => ({
|
||||
|
|
@ -101,13 +99,6 @@ export const Inspector = ({
|
|||
componentNameRef.current = newComponentName;
|
||||
}, [newComponentName]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
handleComponentNameChange(componentNameRef.current);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const validateComponentName = (name) => {
|
||||
const isValid = !Object.values(allComponents)
|
||||
.map((component) => component.component.name)
|
||||
|
|
@ -131,9 +122,9 @@ export const Inspector = ({
|
|||
return setInputFocus();
|
||||
}
|
||||
if (validateQueryName(newName)) {
|
||||
let newComponent = { ...component };
|
||||
let newComponent = JSON.parse(JSON.stringify(component));
|
||||
newComponent.component.name = newName;
|
||||
componentDefinitionChanged(newComponent);
|
||||
componentDefinitionChanged(newComponent, { componentNameUpdated: true });
|
||||
} else {
|
||||
toast.error(
|
||||
t(
|
||||
|
|
@ -152,9 +143,9 @@ export const Inspector = ({
|
|||
return null;
|
||||
};
|
||||
|
||||
function paramUpdated(param, attr, value, paramType) {
|
||||
console.log({ param, attr, value, paramType });
|
||||
let newDefinition = _.cloneDeep(component.component.definition);
|
||||
function paramUpdated(param, attr, value, paramType, isParamFromTableColumn = false) {
|
||||
let newComponent = JSON.parse(JSON.stringify(component));
|
||||
let newDefinition = _.cloneDeep(newComponent.component.definition);
|
||||
let allParams = newDefinition[paramType] || {};
|
||||
const paramObject = allParams[param.name];
|
||||
if (!paramObject) {
|
||||
|
|
@ -163,13 +154,19 @@ export const Inspector = ({
|
|||
if (attr) {
|
||||
allParams[param.name][attr] = value;
|
||||
const defaultValue = getDefaultValue(value);
|
||||
// This is needed to have enable pagination as backward compatible
|
||||
// This is needed to have enable pagination in Table as backward compatible
|
||||
// Whenever enable pagination is false, we turn client and server side pagination as false
|
||||
if (param.name === 'enablePagination' && !resolveReferences(value, currentState)) {
|
||||
if (
|
||||
component.component.component === 'Table' &&
|
||||
param.name === 'enablePagination' &&
|
||||
!resolveReferences(value, currentState)
|
||||
) {
|
||||
if (allParams?.['clientSidePagination']?.[attr]) {
|
||||
allParams['clientSidePagination'][attr] = value;
|
||||
}
|
||||
allParams['serverSidePagination'][attr] = value;
|
||||
if (allParams['serverSidePagination']?.[attr]) {
|
||||
allParams['serverSidePagination'][attr] = value;
|
||||
}
|
||||
}
|
||||
// This case is required to handle for older apps when serverSidePagination is connected to Fx
|
||||
if (param.name === 'serverSidePagination' && !allParams?.['enablePagination']?.[attr]) {
|
||||
|
|
@ -194,12 +191,11 @@ export const Inspector = ({
|
|||
allParams[param.name] = value;
|
||||
}
|
||||
newDefinition[paramType] = allParams;
|
||||
let newComponent = _.merge(component, {
|
||||
component: {
|
||||
definition: newDefinition,
|
||||
},
|
||||
newComponent.component.definition = newDefinition;
|
||||
componentDefinitionChanged(newComponent, {
|
||||
componentPropertyUpdated: true,
|
||||
isParamFromTableColumn: isParamFromTableColumn,
|
||||
});
|
||||
componentDefinitionChanged(newComponent);
|
||||
}
|
||||
|
||||
function layoutPropertyChanged(param, attr, value, paramType) {
|
||||
|
|
@ -207,9 +203,7 @@ export const Inspector = ({
|
|||
|
||||
// User wants to show the widget on mobile devices
|
||||
if (param.name === 'showOnMobile' && value === true) {
|
||||
let newComponent = {
|
||||
...component,
|
||||
};
|
||||
let newComponent = JSON.parse(JSON.stringify(component));
|
||||
|
||||
const { width, height } = newComponent.layouts['desktop'];
|
||||
|
||||
|
|
@ -223,7 +217,7 @@ export const Inspector = ({
|
|||
},
|
||||
};
|
||||
|
||||
componentDefinitionChanged(newComponent);
|
||||
componentDefinitionChanged(newComponent, { layoutPropertyChanged: true });
|
||||
|
||||
// Child components should also have a mobile layout
|
||||
const childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent === component.id);
|
||||
|
|
@ -246,54 +240,11 @@ export const Inspector = ({
|
|||
},
|
||||
};
|
||||
|
||||
componentDefinitionChanged(newChild);
|
||||
componentDefinitionChanged(newChild, { withChildLayout: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function eventUpdated(event, actionId) {
|
||||
let newDefinition = { ...component.component.definition };
|
||||
newDefinition.events[event.name] = { actionId };
|
||||
|
||||
let newComponent = {
|
||||
...component,
|
||||
};
|
||||
|
||||
componentDefinitionChanged(newComponent);
|
||||
}
|
||||
|
||||
function eventsChanged(newEvents, isReordered = false) {
|
||||
let newDefinition;
|
||||
if (isReordered) {
|
||||
newDefinition = { ...component.component };
|
||||
newDefinition.definition.events = newEvents;
|
||||
} else {
|
||||
newDefinition = { ...component.component.definition };
|
||||
newDefinition.events = newEvents;
|
||||
}
|
||||
|
||||
let newComponent = {
|
||||
...component,
|
||||
};
|
||||
|
||||
componentDefinitionChanged(newComponent);
|
||||
}
|
||||
|
||||
function eventOptionUpdated(event, option, value) {
|
||||
console.log('eventOptionUpdated', event, option, value);
|
||||
|
||||
let newDefinition = { ...component.component.definition };
|
||||
let eventDefinition = newDefinition.events[event.name] || { options: {} };
|
||||
|
||||
newDefinition.events[event.name] = { ...eventDefinition, options: { ...eventDefinition.options, [option]: value } };
|
||||
|
||||
let newComponent = {
|
||||
...component,
|
||||
};
|
||||
|
||||
componentDefinitionChanged(newComponent);
|
||||
}
|
||||
|
||||
const handleInspectorHeaderActions = (value) => {
|
||||
if (value === 'rename') {
|
||||
setTimeout(() => setInputFocus(), 0);
|
||||
|
|
@ -339,13 +290,13 @@ export const Inspector = ({
|
|||
paramUpdated={paramUpdated}
|
||||
dataQueries={dataQueries}
|
||||
componentMeta={componentMeta}
|
||||
eventUpdated={eventUpdated}
|
||||
eventOptionUpdated={eventOptionUpdated}
|
||||
// eventUpdated={eventUpdated}
|
||||
// eventOptionUpdated={eventOptionUpdated}
|
||||
components={allComponents}
|
||||
currentState={currentState}
|
||||
darkMode={darkMode}
|
||||
eventsChanged={eventsChanged}
|
||||
apps={apps}
|
||||
// eventsChanged={eventsChanged}
|
||||
// apps={apps} !check
|
||||
pages={pages}
|
||||
allComponents={allComponents}
|
||||
/>
|
||||
|
|
@ -382,31 +333,15 @@ export const Inspector = ({
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify({ showHeaderActionsMenu })]);
|
||||
|
||||
const handleDeleteConfirm = React.useCallback(() => {
|
||||
switchSidebarTab(2);
|
||||
removeComponent(component);
|
||||
setWidgetDeleteConfirmation(false);
|
||||
}, [switchSidebarTab, removeComponent, component, setWidgetDeleteConfirmation]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyPress = (event) => {
|
||||
if (showWidgetDeleteConfirmation && event.key === 'Enter') {
|
||||
handleDeleteConfirm();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyPress);
|
||||
};
|
||||
}, [showWidgetDeleteConfirmation, handleDeleteConfirm]);
|
||||
|
||||
return (
|
||||
<div className="inspector">
|
||||
<ConfirmDialog
|
||||
show={showWidgetDeleteConfirmation}
|
||||
message={'Widget will be deleted, do you want to continue?'}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onConfirm={() => {
|
||||
switchSidebarTab(2);
|
||||
removeComponent(component.id);
|
||||
}}
|
||||
onCancel={() => setWidgetDeleteConfirmation(false)}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export const LeftSidebarInspector = ({
|
|||
delete jsontreeData.server;
|
||||
delete jsontreeData.actions;
|
||||
delete jsontreeData.succededQuery;
|
||||
delete jsontreeData.layout;
|
||||
|
||||
//*Sorted components and queries alphabetically
|
||||
const sortedComponents = Object.keys(jsontreeData['components'])
|
||||
|
|
@ -117,7 +118,7 @@ export const LeftSidebarInspector = ({
|
|||
const iconsList = useMemo(() => [...queryIcons, ...componentIcons], [queryIcons, componentIcons]);
|
||||
|
||||
const handleRemoveComponent = (component) => {
|
||||
removeComponent(component);
|
||||
removeComponent(component.id);
|
||||
};
|
||||
|
||||
const handleSelectComponentOnEditor = (component) => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useAppVersionStore } from '@/_stores/appVersionStore';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
|
||||
export const GlobalSettings = ({ darkMode, showHideViewerNavigationControls, showPageViwerPageNavitation }) => {
|
||||
export const GlobalSettings = ({ darkMode, showHideViewerNavigationControls, isViewerNavigationDisabled }) => {
|
||||
const { isVersionReleased, enableReleasedVersionPopupState } = useAppVersionStore(
|
||||
(state) => ({
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
|
|
@ -35,7 +35,7 @@ export const GlobalSettings = ({ darkMode, showHideViewerNavigationControls, sho
|
|||
</label>
|
||||
<hr style={{ margin: '0.75rem 0' }} />
|
||||
<div className="menu-options mb-0">
|
||||
<Toggle onChange={onChange} value={!showPageViwerPageNavitation} />
|
||||
<Toggle onChange={onChange} value={isViewerNavigationDisabled} />
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const PageHandler = ({
|
|||
currentPageId,
|
||||
updateHomePage,
|
||||
updatePageHandle,
|
||||
updateOnPageLoadEvents,
|
||||
|
||||
apps,
|
||||
pages,
|
||||
components,
|
||||
|
|
@ -201,7 +201,6 @@ export const PageHandler = ({
|
|||
!haveUserPinned && pinPagesPopover(false);
|
||||
}}
|
||||
darkMode={darkMode}
|
||||
updateOnPageLoadEvents={updateOnPageLoadEvents}
|
||||
apps={apps}
|
||||
pages={pages}
|
||||
components={components}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const SettingsModal = ({
|
|||
show,
|
||||
handleClose,
|
||||
darkMode,
|
||||
updateOnPageLoadEvents,
|
||||
|
||||
apps,
|
||||
pages,
|
||||
components,
|
||||
|
|
@ -55,6 +55,7 @@ export const SettingsModal = ({
|
|||
<Modal.Body onClick={() => pinPagesPopover(true)}>
|
||||
<b data-cy={'page-events-labe'}>Events</b>
|
||||
<EventManager
|
||||
//!page
|
||||
component={{
|
||||
component: {
|
||||
definition: {
|
||||
|
|
@ -62,11 +63,12 @@ export const SettingsModal = ({
|
|||
},
|
||||
},
|
||||
}}
|
||||
componentMeta={{ events: { onPageLoad: { displayName: 'On page load' } }, name: 'page' }}
|
||||
sourceId={page?.id}
|
||||
eventSourceType="page"
|
||||
eventMetaDefinition={{ events: { onPageLoad: { displayName: 'On page load' } }, name: 'page' }}
|
||||
components={components}
|
||||
apps={apps}
|
||||
pages={allpages}
|
||||
eventsChanged={(events) => updateOnPageLoadEvents(page.id, events)}
|
||||
popOverCallback={(showing) => showing}
|
||||
/>
|
||||
</Modal.Body>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const LeftSidebarPageSelector = ({
|
|||
homePageId,
|
||||
showHideViewerNavigationControls,
|
||||
updateOnSortingPages,
|
||||
updateOnPageLoadEvents,
|
||||
|
||||
apps,
|
||||
pinned,
|
||||
setPinned,
|
||||
|
|
@ -89,7 +89,7 @@ const LeftSidebarPageSelector = ({
|
|||
<GlobalSettings
|
||||
darkMode={darkMode}
|
||||
showHideViewerNavigationControls={showHideViewerNavigationControls}
|
||||
showPageViwerPageNavitation={appDefinition?.showViewerNavigation || false}
|
||||
isViewerNavigationDisabled={!appDefinition?.showViewerNavigation}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
@ -166,7 +166,6 @@ const LeftSidebarPageSelector = ({
|
|||
updatePageHandle={updatePageHandle}
|
||||
classNames="page-handler"
|
||||
onSort={updateOnSortingPages}
|
||||
updateOnPageLoadEvents={updateOnPageLoadEvents}
|
||||
currentState={currentState}
|
||||
apps={apps}
|
||||
allpages={pages}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import { useDataSources } from '@/_stores/dataSourcesStore';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import useDebugger from './SidebarDebugger/useDebugger';
|
||||
import { GlobalSettings } from '../Header/GlobalSettings';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
import { resolveReferences } from '@/_helpers/utils';
|
||||
import { useCurrentState } from '@/_stores/currentStateStore';
|
||||
|
||||
export const LeftSidebar = forwardRef((props, ref) => {
|
||||
const router = useRouter();
|
||||
|
|
@ -46,7 +46,6 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
updatePageHandle,
|
||||
showHideViewerNavigationControls,
|
||||
updateOnSortingPages,
|
||||
updateOnPageLoadEvents,
|
||||
apps,
|
||||
clonePage,
|
||||
setEditorMarginLeft,
|
||||
|
|
@ -54,10 +53,8 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
toggleAppMaintenance,
|
||||
app,
|
||||
disableEnablePage,
|
||||
slug,
|
||||
handleSlugChange,
|
||||
isMaintenanceOn,
|
||||
} = props;
|
||||
const { is_maintenance_on } = app;
|
||||
|
||||
const dataSources = useDataSources();
|
||||
const prevSelectedSidebarItem = localStorage.getItem('selectedSidebarItem');
|
||||
|
|
@ -80,8 +77,9 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
}),
|
||||
shallow
|
||||
);
|
||||
const [pinned, setPinned] = useState(!!localStorage.getItem('selectedSidebarItem'));
|
||||
const currentState = useCurrentState();
|
||||
const [pinned, setPinned] = useState(!!localStorage.getItem('selectedSidebarItem'));
|
||||
|
||||
const [realState, setRealState] = useState(currentState);
|
||||
|
||||
const { errorLogs, clearErrorLogs, unReadErrorCount, allLog } = useDebugger({
|
||||
|
|
@ -142,9 +140,10 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
sideBarBtnRefs.current[page] = ref;
|
||||
};
|
||||
useEffect(() => {
|
||||
setRealState(currentState);
|
||||
setRealState(currentState); //!ceck this
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentState.components]);
|
||||
|
||||
const backgroundFxQuery = appDefinition?.globalSettings?.backgroundFxQuery;
|
||||
|
||||
const SELECTED_ITEMS = {
|
||||
|
|
@ -164,11 +163,14 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
updateHomePage={updateHomePage}
|
||||
updatePageHandle={updatePageHandle}
|
||||
clonePage={clonePage}
|
||||
pages={Object.entries(appDefinition.pages).map(([id, page]) => ({ id, ...page })) || []}
|
||||
pages={
|
||||
Object.entries(_.cloneDeep(appDefinition).pages)
|
||||
.map(([id, page]) => ({ id, ...page }))
|
||||
.sort((a, b) => a.index - b.index) || []
|
||||
}
|
||||
homePageId={appDefinition.homePageId}
|
||||
showHideViewerNavigationControls={showHideViewerNavigationControls}
|
||||
updateOnSortingPages={updateOnSortingPages}
|
||||
updateOnPageLoadEvents={updateOnPageLoadEvents}
|
||||
apps={apps}
|
||||
setPinned={handlePin}
|
||||
pinned={pinned}
|
||||
|
|
@ -221,21 +223,19 @@ export const LeftSidebar = forwardRef((props, ref) => {
|
|||
globalSettings={appDefinition.globalSettings}
|
||||
darkMode={darkMode}
|
||||
toggleAppMaintenance={toggleAppMaintenance}
|
||||
is_maintenance_on={is_maintenance_on}
|
||||
isMaintenanceOn={isMaintenanceOn}
|
||||
app={app}
|
||||
realState={currentState}
|
||||
backgroundFxQuery={backgroundFxQuery}
|
||||
realState={realState}
|
||||
slug={slug}
|
||||
handleSlugChange={handleSlugChange}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
backgroundFxQuery &&
|
||||
globalSettingsChanged('canvasBackgroundColor', resolveReferences(backgroundFxQuery, realState));
|
||||
globalSettingsChanged({ canvasBackgroundColor: resolveReferences(backgroundFxQuery, currentState) });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(resolveReferences(backgroundFxQuery, realState))]);
|
||||
}, [JSON.stringify(resolveReferences(backgroundFxQuery, currentState))]);
|
||||
|
||||
return (
|
||||
<div className="left-sidebar" data-cy="left-sidebar-inspector">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import SolidIcon from '@/_ui/Icon/SolidIcons';
|
|||
import cx from 'classnames';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { TOOLTIP_MESSAGES } from '@/_helpers/constants';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
|
||||
class ManageAppUsersComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -21,7 +22,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
|
||||
this.state = {
|
||||
showModal: false,
|
||||
app: { ...props.app },
|
||||
appId: null,
|
||||
isLoading: true,
|
||||
isSlugVerificationInProgress: false,
|
||||
addingUser: false,
|
||||
|
|
@ -49,14 +50,14 @@ class ManageAppUsersComponent extends React.Component {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
const appId = this.props.app.id;
|
||||
this.fetchAppUsers();
|
||||
const appId = this.props.appId;
|
||||
this.fetchAppUsers(appId);
|
||||
this.setState({ appId });
|
||||
}
|
||||
|
||||
fetchAppUsers = () => {
|
||||
fetchAppUsers = (appId) => {
|
||||
appsService
|
||||
.getAppUsers(this.props.app.id)
|
||||
.getAppUsers(appId)
|
||||
.then((data) =>
|
||||
this.setState({
|
||||
users: data.users,
|
||||
|
|
@ -65,7 +66,8 @@ class ManageAppUsersComponent extends React.Component {
|
|||
)
|
||||
.catch((error) => {
|
||||
this.setState({ isLoading: false });
|
||||
toast.error(error);
|
||||
const errorMessage = error?.message || 'Something went wrong';
|
||||
toast.error(errorMessage);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -89,11 +91,11 @@ class ManageAppUsersComponent extends React.Component {
|
|||
const { organizationUserId, role } = this.state.newUser;
|
||||
|
||||
appService
|
||||
.createAppUser(this.state.app.id, organizationUserId, role)
|
||||
.createAppUser(this.state.appId, organizationUserId, role)
|
||||
.then(() => {
|
||||
this.setState({ addingUser: false, newUser: {} });
|
||||
toast.success('Added user successfully');
|
||||
this.fetchAppUsers();
|
||||
this.fetchAppUsers(this.state.appId);
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
this.setState({ addingUser: false });
|
||||
|
|
@ -102,21 +104,19 @@ class ManageAppUsersComponent extends React.Component {
|
|||
};
|
||||
|
||||
toggleAppVisibility = () => {
|
||||
const newState = !this.state.app.is_public;
|
||||
const newState = !this.props.isPublic;
|
||||
this.setState({
|
||||
ischangingVisibility: true,
|
||||
});
|
||||
|
||||
useAppDataStore.getState().actions.updateState({ isPublic: newState });
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
appsService
|
||||
.setVisibility(this.state.app.id, newState)
|
||||
.setVisibility(this.state.appId, newState)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
ischangingVisibility: false,
|
||||
app: {
|
||||
...this.state.app,
|
||||
is_public: newState,
|
||||
},
|
||||
});
|
||||
|
||||
if (newState) {
|
||||
|
|
@ -153,7 +153,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
isSlugVerificationInProgress: true,
|
||||
});
|
||||
appsService
|
||||
.setSlug(this.state.app.id, value)
|
||||
.setSlug(this.state.appId, value)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
newSlug: {
|
||||
|
|
@ -163,8 +163,9 @@ class ManageAppUsersComponent extends React.Component {
|
|||
isSlugVerificationInProgress: false,
|
||||
isSlugUpdated: true,
|
||||
});
|
||||
this.props.handleSlugChange(value);
|
||||
|
||||
replaceEditorURL(value, this.props.pageHandle);
|
||||
useAppDataStore.getState().actions.updateState({ slug: value });
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
this.setState({
|
||||
|
|
@ -189,8 +190,8 @@ class ManageAppUsersComponent extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { isLoading, app, isSlugVerificationInProgress, newSlug, isSlugUpdated } = this.state;
|
||||
const appId = app.id;
|
||||
const { isLoading, appId, isSlugVerificationInProgress, newSlug, isSlugUpdated } = this.state;
|
||||
|
||||
const appLink = `${getHostURL()}/applications/`;
|
||||
const shareableLink = appLink + (this.props.slug || appId);
|
||||
const slugButtonClass = !_.isEmpty(newSlug.error) ? 'is-invalid' : 'is-valid';
|
||||
|
|
@ -249,7 +250,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
className="form-check-input"
|
||||
type="checkbox"
|
||||
onClick={this.toggleAppVisibility}
|
||||
checked={this.state.app.is_public}
|
||||
checked={this?.props?.isPublic}
|
||||
disabled={this.state.ischangingVisibility}
|
||||
data-cy="make-public-app-toggle"
|
||||
/>
|
||||
|
|
@ -356,7 +357,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
<label className="label label-info">{`URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens`}</label>
|
||||
)}
|
||||
</div>
|
||||
{(this.state.app.is_public || window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
|
||||
{(this?.props?.isPublic || window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
|
||||
<div className="tj-app-input">
|
||||
<label className="field-name">Embedded app link</label>
|
||||
<span className={`tj-text-input justify-content-between ${this.props.darkMode ? 'dark' : ''}`}>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { EventManager } from '@/Editor/Inspector/EventManager';
|
|||
import { staticDataSources, customToggles, mockDataQueryAsComponent } from '../constants';
|
||||
import { DataSourceTypes } from '../../DataSourceManager/SourceComponents';
|
||||
import { useDataSources, useGlobalDataSources } from '@/_stores/dataSourcesStore';
|
||||
import { useDataQueriesActions, useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
|
||||
import { useSelectedQuery, useSelectedDataSource } from '@/_stores/queryPanelStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
|
@ -97,14 +97,6 @@ export const QueryManagerBody = ({
|
|||
validateNewOptions(newOptions);
|
||||
};
|
||||
|
||||
const eventsChanged = (events) => {
|
||||
optionchanged('events', events);
|
||||
//added this here since the subscriber added in QueryManager component does not detect this change
|
||||
useDataQueriesStore
|
||||
.getState()
|
||||
.actions.saveData({ ...selectedQuery, options: { ...selectedQuery.options, events: events } });
|
||||
};
|
||||
|
||||
const toggleOption = (option) => {
|
||||
const currentValue = selectedQuery?.options?.[option] ?? false;
|
||||
optionchanged(option, !currentValue);
|
||||
|
|
@ -185,9 +177,9 @@ export const QueryManagerBody = ({
|
|||
<div className={`form-label`}>{t('editor.queryManager.eventsHandler', 'Events')}</div>
|
||||
<div className="query-manager-events pb-4 flex-grow-1">
|
||||
<EventManager
|
||||
eventsChanged={eventsChanged}
|
||||
component={queryComponent.component}
|
||||
componentMeta={queryComponent.componentMeta}
|
||||
sourceId={selectedQuery?.id}
|
||||
eventSourceType="data_query" //check
|
||||
eventMetaDefinition={queryComponent.componentMeta}
|
||||
currentState={currentState}
|
||||
components={allComponents}
|
||||
callerQueryId={selectedQueryId}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,21 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { QueryManagerHeader } from './Components/QueryManagerHeader';
|
||||
import { QueryManagerBody } from './Components/QueryManagerBody';
|
||||
import { runQuery } from '@/_helpers/appUtils';
|
||||
import { defaultSources } from './constants';
|
||||
|
||||
import { useQueryCreationLoading, useQueryUpdationLoading } from '@/_stores/dataQueriesStore';
|
||||
import { useDataSources, useGlobalDataSources, useLoadingDataSources } from '@/_stores/dataSourcesStore';
|
||||
import { useQueryToBeRun, useSelectedQuery, useQueryPanelActions } from '@/_stores/queryPanelStore';
|
||||
|
||||
const QueryManager = ({ mode, dataQueriesChanged, appId, darkMode, apps, allComponents, appDefinition, editorRef }) => {
|
||||
const QueryManager = ({ mode, appId, darkMode, apps, allComponents, appDefinition, editorRef }) => {
|
||||
const loadingDataSources = useLoadingDataSources();
|
||||
const dataSources = useDataSources();
|
||||
const globalDataSources = useGlobalDataSources();
|
||||
const queryToBeRun = useQueryToBeRun();
|
||||
const isCreationInProcess = useQueryCreationLoading();
|
||||
const isUpdationInProcess = useQueryUpdationLoading();
|
||||
const selectedQuery = useSelectedQuery();
|
||||
const { setSelectedDataSource, setQueryToBeRun } = useQueryPanelActions();
|
||||
|
||||
const [options, setOptions] = useState({});
|
||||
const mounted = useRef(false);
|
||||
|
||||
/** TODO: Below effect primarily used only for websocket invocation post update. Can be removed onece websocket logic is revamped */
|
||||
useEffect(() => {
|
||||
if (mounted.current && !isCreationInProcess && !isUpdationInProcess) {
|
||||
return dataQueriesChanged();
|
||||
}
|
||||
mounted.current = true;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCreationInProcess, isUpdationInProcess, mounted.current]);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions(selectedQuery?.options || {});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ const QueryPanel = ({
|
|||
dataQueriesChanged,
|
||||
fetchDataQueries,
|
||||
darkMode,
|
||||
apps,
|
||||
allComponents,
|
||||
appId,
|
||||
appDefinition,
|
||||
|
|
@ -203,7 +202,6 @@ const QueryPanel = ({
|
|||
dataQueriesChanged={updateDataQueries}
|
||||
appId={appId}
|
||||
darkMode={darkMode}
|
||||
apps={apps}
|
||||
allComponents={allComponents}
|
||||
appDefinition={appDefinition}
|
||||
editorRef={editorRef}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Popover from '@/_ui/Popover';
|
||||
import Avatar from '@/_ui/Avatar';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { useOthers, useSelf } from '@y-presence/react';
|
||||
import { useAppDataActions, useAppInfo } from '@/_stores/appDataStore';
|
||||
|
||||
const MAX_DISPLAY_USERS = 2;
|
||||
const RealtimeAvatars = ({ darkMode }) => {
|
||||
|
|
@ -17,6 +18,17 @@ const RealtimeAvatars = ({ darkMode }) => {
|
|||
const getAvatarText = (presence) => presence.firstName?.charAt(0) + presence.lastName?.charAt(0);
|
||||
const getAvatarTitle = (presence) => `${presence.firstName} ${presence.lastName}`;
|
||||
|
||||
const { updateState } = useAppDataActions();
|
||||
const { areOthersOnSameVersionAndPage, currentVersionId } = useAppInfo();
|
||||
|
||||
useEffect(() => {
|
||||
const areActiveUsersOnSameVersionAndPage = othersOnSameVersionAndPage.length > 0;
|
||||
const shouldUpdateState = areActiveUsersOnSameVersionAndPage !== areOthersOnSameVersionAndPage;
|
||||
|
||||
if (shouldUpdateState) updateState({ areOthersOnSameVersionAndPage: areActiveUsersOnSameVersionAndPage });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify({ others, self, currentVersionId })]);
|
||||
|
||||
const popoverContent = () => {
|
||||
return othersOnSameVersionAndPage
|
||||
.slice(MAX_DISPLAY_USERS, othersOnSameVersionAndPage.length)
|
||||
|
|
|
|||
|
|
@ -8,13 +8,7 @@ import { ConfirmDialog } from '@/_components/ConfirmDialog';
|
|||
import { shallow } from 'zustand/shallow';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
|
||||
export const ReleaseVersionButton = function DeployVersionButton({
|
||||
appId,
|
||||
appName,
|
||||
fetchApp,
|
||||
onVersionRelease,
|
||||
saveEditingVersion,
|
||||
}) {
|
||||
export const ReleaseVersionButton = function DeployVersionButton({ appId, appName, fetchApp, onVersionRelease }) {
|
||||
const [isReleasing, setIsReleasing] = useState(false);
|
||||
const { isVersionReleased, editingVersion } = useAppVersionStore(
|
||||
(state) => ({
|
||||
|
|
@ -29,7 +23,7 @@ export const ReleaseVersionButton = function DeployVersionButton({
|
|||
const releaseVersion = (editingVersion) => {
|
||||
setShowPageDeletionConfirmation(false);
|
||||
setIsReleasing(true);
|
||||
saveEditingVersion();
|
||||
|
||||
appsService
|
||||
.saveApp(appId, {
|
||||
name: appName,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { useCurrentState } from '@/_stores/currentStateStore';
|
|||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useMounted } from '@/_hooks/use-mount';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
const NO_OF_GRIDS = 43;
|
||||
|
||||
|
|
@ -92,17 +94,19 @@ export const SubContainer = ({
|
|||
false;
|
||||
|
||||
const getChildWidgets = (components) => {
|
||||
let childWidgets = [];
|
||||
let childWidgets = {};
|
||||
Object.keys(components).forEach((key) => {
|
||||
if (components[key].parent === parent) {
|
||||
const componentParent = components[key].component.parent;
|
||||
if (componentParent === parent) {
|
||||
childWidgets[key] = { ...components[key], component: { ...components[key]['component'], parent } };
|
||||
}
|
||||
});
|
||||
|
||||
return childWidgets;
|
||||
};
|
||||
|
||||
const [boxes, setBoxes] = useState(allComponents);
|
||||
const [childWidgets, setChildWidgets] = useState(() => getChildWidgets(allComponents));
|
||||
const [childWidgets, setChildWidgets] = useState([]);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
// const [subContainerHeight, setSubContainerHeight] = useState('100%'); //used to determine the height of the sub container for modal
|
||||
|
|
@ -111,6 +115,7 @@ export const SubContainer = ({
|
|||
useEffect(() => {
|
||||
setBoxes(allComponents);
|
||||
setChildWidgets(() => getChildWidgets(allComponents));
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [allComponents, parent]);
|
||||
|
||||
|
|
@ -183,8 +188,11 @@ export const SubContainer = ({
|
|||
);
|
||||
|
||||
_.set(childrenBoxes, newComponent.id, {
|
||||
component: newComponent.component,
|
||||
parent: parentComponent.component === 'Tabs' ? parentId + '-' + tab : parentId,
|
||||
component: {
|
||||
...newComponent.component,
|
||||
parent: parentComponent.component === 'Tabs' ? parentId + '-' + tab : parentId,
|
||||
},
|
||||
|
||||
layouts: {
|
||||
[currentLayout]: {
|
||||
...layout,
|
||||
|
|
@ -233,7 +241,23 @@ export const SubContainer = ({
|
|||
},
|
||||
},
|
||||
};
|
||||
appDefinitionChanged(newDefinition);
|
||||
|
||||
const oldComponents = appDefinition.pages[currentPageId]?.components ?? {};
|
||||
const newComponents = boxes;
|
||||
|
||||
const componendAdded = Object.keys(newComponents).length > Object.keys(oldComponents).length;
|
||||
|
||||
const opts = { containerChanges: true };
|
||||
|
||||
if (componendAdded) {
|
||||
opts.componentAdded = true;
|
||||
}
|
||||
|
||||
const shouldUpdate = !_.isEmpty(diff(appDefinition, newDefinition));
|
||||
|
||||
if (shouldUpdate) {
|
||||
appDefinitionChanged(newDefinition, opts);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [boxes]);
|
||||
|
|
@ -271,6 +295,7 @@ export const SubContainer = ({
|
|||
}
|
||||
});
|
||||
|
||||
//!Todo: need to check: this never gets called as draggingState is always false
|
||||
useEffect(() => {
|
||||
setIsDragging(draggingState);
|
||||
}, [draggingState]);
|
||||
|
|
@ -306,8 +331,10 @@ export const SubContainer = ({
|
|||
setBoxes({
|
||||
...boxes,
|
||||
[newComponent.id]: {
|
||||
component: newComponent.component,
|
||||
parent: parentRef.current.id,
|
||||
component: {
|
||||
...newComponent.component,
|
||||
parent: parentRef.current.id,
|
||||
},
|
||||
layouts: {
|
||||
...newComponent.layout,
|
||||
},
|
||||
|
|
@ -357,6 +384,7 @@ export const SubContainer = ({
|
|||
enableReleasedVersionPopupState();
|
||||
return;
|
||||
}
|
||||
|
||||
const canvasWidth = getContainerCanvasWidth();
|
||||
const nodeBounds = direction.node.getBoundingClientRect();
|
||||
|
||||
|
|
@ -430,7 +458,12 @@ export const SubContainer = ({
|
|||
}
|
||||
|
||||
//round the width to nearest multiple of gridwidth before converting to %
|
||||
const currentWidth = (_containerCanvasWidth * width) / NO_OF_GRIDS;
|
||||
let currentWidth = (_containerCanvasWidth * width) / NO_OF_GRIDS;
|
||||
|
||||
if (currentWidth > _containerCanvasWidth) {
|
||||
currentWidth = _containerCanvasWidth;
|
||||
}
|
||||
|
||||
let newWidth = currentWidth + deltaWidth;
|
||||
newWidth = Math.round(newWidth / gridWidth) * gridWidth;
|
||||
width = (newWidth * NO_OF_GRIDS) / _containerCanvasWidth;
|
||||
|
|
@ -536,9 +569,11 @@ export const SubContainer = ({
|
|||
Object.keys(childWidgets).map((key) => {
|
||||
const addDefaultChildren = childWidgets[key]['withDefaultChildren'] || false;
|
||||
const box = childWidgets[key];
|
||||
|
||||
const canShowInCurrentLayout =
|
||||
box.component.definition.others[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'].value;
|
||||
if (box.parent && resolveReferences(canShowInCurrentLayout, currentState)) {
|
||||
|
||||
if (box.component.parent && resolveReferences(canShowInCurrentLayout, currentState)) {
|
||||
return (
|
||||
<DraggableBox
|
||||
onComponentClick={onComponentClick}
|
||||
|
|
@ -577,6 +612,7 @@ export const SubContainer = ({
|
|||
onComponentHover={onComponentHover}
|
||||
hoveredComponent={hoveredComponent}
|
||||
parentId={parentComponent?.name}
|
||||
parent={parent}
|
||||
sideBarDebugger={sideBarDebugger}
|
||||
exposedVariables={exposedVariables ?? {}}
|
||||
childComponents={childComponents[key]}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
appsService,
|
||||
authenticationService,
|
||||
orgEnvironmentVariableService,
|
||||
orgEnvironmentConstantService,
|
||||
dataqueryService,
|
||||
appService,
|
||||
} from '@/_services';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
|
@ -18,6 +19,7 @@ import {
|
|||
onEvent,
|
||||
runQuery,
|
||||
computeComponentState,
|
||||
buildAppDefinition,
|
||||
} from '@/_helpers/appUtils';
|
||||
import queryString from 'query-string';
|
||||
import ViewerLogoIcon from './Icons/viewer-logo.svg';
|
||||
|
|
@ -33,9 +35,8 @@ import { setCookie } from '@/_helpers/cookie';
|
|||
import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
||||
import { useCurrentStateStore } from '@/_stores/currentStateStore';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { getPreviewQueryParams, redirectToDashboard, redirectToErrorPage } from '@/_helpers/routes';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAppDataActions, useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { getPreviewQueryParams, redirectToErrorPage } from '@/_helpers/routes';
|
||||
import { ERROR_TYPES } from '@/_helpers/constants';
|
||||
|
||||
class ViewerComponent extends React.Component {
|
||||
|
|
@ -54,31 +55,44 @@ class ViewerComponent extends React.Component {
|
|||
isLoading: true,
|
||||
users: null,
|
||||
appDefinition: { pages: {} },
|
||||
queryConfirmationList: [],
|
||||
isAppLoaded: false,
|
||||
pages: {},
|
||||
homepage: null,
|
||||
};
|
||||
}
|
||||
|
||||
setStateForApp = (data) => {
|
||||
const copyDefinition = _.cloneDeep(data.definition);
|
||||
const pagesObj = copyDefinition.pages || {};
|
||||
|
||||
const newDefinition = {
|
||||
...copyDefinition,
|
||||
pages: pagesObj,
|
||||
getViewerRef() {
|
||||
return {
|
||||
appDefinition: this.state.appDefinition,
|
||||
queryConfirmationList: this.props.queryConfirmationList,
|
||||
updateQueryConfirmationList: this.updateQueryConfirmationList,
|
||||
navigate: this.props.navigate,
|
||||
switchPage: this.switchPage,
|
||||
currentPageId: this.state.currentPageId,
|
||||
};
|
||||
}
|
||||
|
||||
setStateForApp = (data, byAppSlug = false) => {
|
||||
const appDefData = buildAppDefinition(data);
|
||||
|
||||
if (byAppSlug) {
|
||||
appDefData.globalSettings = data.globalSettings;
|
||||
appDefData.homePageId = data.homePageId;
|
||||
appDefData.showViewerNavigation = data.showViewerNavigation;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
app: data,
|
||||
isLoading: false,
|
||||
isAppLoaded: true,
|
||||
appDefinition: newDefinition || { components: {} },
|
||||
appDefinition: { ...appDefData },
|
||||
pages: appDefData.pages,
|
||||
});
|
||||
};
|
||||
|
||||
setStateForContainer = async (data) => {
|
||||
setStateForContainer = async (data, appVersionId) => {
|
||||
const appDefData = buildAppDefinition(data);
|
||||
|
||||
const currentUser = this.state.currentUser;
|
||||
let userVars = {};
|
||||
|
||||
|
|
@ -94,38 +108,57 @@ class ViewerComponent extends React.Component {
|
|||
let mobileLayoutHasWidgets = false;
|
||||
|
||||
if (this.props.currentLayout === 'mobile') {
|
||||
const currentComponents = data.definition.pages[data.definition.homePageId].components;
|
||||
const currentComponents = appDefData.pages[appDefData.homePageId].components;
|
||||
mobileLayoutHasWidgets =
|
||||
Object.keys(currentComponents).filter((componentId) => currentComponents[componentId]['layouts']['mobile'])
|
||||
.length > 0;
|
||||
}
|
||||
|
||||
let queryState = {};
|
||||
data.data_queries.forEach((query) => {
|
||||
if (query.pluginId || query?.plugin?.id) {
|
||||
queryState[query.name] = {
|
||||
...query.plugin.manifestFile.data.source.exposedVariables,
|
||||
...this.props.currentState.queries[query.name],
|
||||
};
|
||||
} else {
|
||||
const dataSourceTypeDetail = DataSourceTypes.find((source) => source.kind === query.kind);
|
||||
queryState[query.name] = {
|
||||
...dataSourceTypeDetail.exposedVariables,
|
||||
...this.props.currentState.queries[query.name],
|
||||
};
|
||||
}
|
||||
});
|
||||
let dataQueries = [];
|
||||
|
||||
if (appVersionId) {
|
||||
const { data_queries } = await dataqueryService.getAll(appVersionId);
|
||||
dataQueries = data_queries;
|
||||
} else {
|
||||
dataQueries = data.data_queries;
|
||||
}
|
||||
const queryConfirmationList = [];
|
||||
|
||||
if (dataQueries.length > 0) {
|
||||
dataQueries.forEach((query) => {
|
||||
if (query?.options && query?.options?.requestConfirmation && query?.options?.runOnPageLoad) {
|
||||
queryConfirmationList.push({ queryId: query.id, queryName: query.name });
|
||||
}
|
||||
|
||||
if (query.pluginId || query?.plugin?.id) {
|
||||
queryState[query.name] = {
|
||||
...query.plugin.manifestFile.data.source.exposedVariables,
|
||||
...this.props.currentState.queries[query.name],
|
||||
};
|
||||
} else {
|
||||
const dataSourceTypeDetail = DataSourceTypes.find((source) => source.kind === query.kind);
|
||||
queryState[query.name] = {
|
||||
...dataSourceTypeDetail.exposedVariables,
|
||||
...this.props.currentState.queries[query.name],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryConfirmationList.length !== 0) {
|
||||
this.updateQueryConfirmationList(queryConfirmationList);
|
||||
}
|
||||
const variables = await this.fetchOrgEnvironmentVariables(data.slug, data.is_public);
|
||||
const constants = await this.fetchOrgEnvironmentConstants(data.slug, data.is_public);
|
||||
|
||||
const pages = Object.entries(data.definition.pages).map(([pageId, page]) => ({ id: pageId, ...page }));
|
||||
const homePageId = data.definition.homePageId;
|
||||
const pages = data.pages;
|
||||
const homePageId = appVersionId ? data.editing_version.homePageId : data?.homePageId;
|
||||
const startingPageHandle = this.props?.params?.pageHandle;
|
||||
const currentPageId = pages.filter((page) => page.handle === startingPageHandle)[0]?.id ?? homePageId;
|
||||
const currentPage = pages.find((page) => page.id === currentPageId);
|
||||
|
||||
useDataQueriesStore.getState().actions.setDataQueries(data.data_queries);
|
||||
useDataQueriesStore.getState().actions.setDataQueries(dataQueries);
|
||||
this.props.setCurrentState({
|
||||
queries: queryState,
|
||||
components: {},
|
||||
|
|
@ -148,6 +181,7 @@ class ViewerComponent extends React.Component {
|
|||
...constants,
|
||||
});
|
||||
useEditorStore.getState().actions.toggleCurrentLayout(mobileLayoutHasWidgets ? 'mobile' : 'desktop');
|
||||
this.props.updateState({ events: data.events ?? [] });
|
||||
this.setState(
|
||||
{
|
||||
currentUser,
|
||||
|
|
@ -159,21 +193,24 @@ class ViewerComponent extends React.Component {
|
|||
? `${this.state.deviceWindowWidth}px`
|
||||
: '1292px',
|
||||
selectedComponent: null,
|
||||
dataQueries: data.data_queries,
|
||||
dataQueries: dataQueries,
|
||||
currentPageId: currentPage.id,
|
||||
pages: {},
|
||||
homepage: this.state.appDefinition?.pages?.[this.state.appDefinition?.homePageId]?.handle,
|
||||
homepage: appDefData?.pages?.[this.state.appDefinition?.homePageId]?.handle,
|
||||
events: data.events ?? [],
|
||||
},
|
||||
() => {
|
||||
computeComponentState(this, data?.definition?.pages[currentPage.id]?.components).then(async () => {
|
||||
this.setState({ initialComputationOfStateDone: true });
|
||||
const components = appDefData?.pages[currentPageId]?.components || {};
|
||||
|
||||
computeComponentState(components).then(async () => {
|
||||
this.setState({ initialComputationOfStateDone: true, defaultComponentStateComputed: true });
|
||||
console.log('Default component state computed and set');
|
||||
this.runQueries(data.data_queries);
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
|
||||
for (const event of events ?? []) {
|
||||
await this.handleEvent(event.eventId, event);
|
||||
}
|
||||
this.runQueries(dataQueries);
|
||||
|
||||
const currentPageEvents = this.state.events.filter(
|
||||
(event) => event.target === 'page' && event.sourceId === this.state.currentPageId
|
||||
);
|
||||
|
||||
await this.handleEvent('onPageLoad', currentPageEvents);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
@ -182,7 +219,7 @@ class ViewerComponent extends React.Component {
|
|||
runQueries = (data_queries) => {
|
||||
data_queries.forEach((query) => {
|
||||
if (query.options.runOnPageLoad && isQueryRunnable(query)) {
|
||||
runQuery(this, query.id, query.name, undefined, 'view');
|
||||
runQuery(this.getViewerRef(), query.id, query.name, undefined, 'view');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -239,13 +276,14 @@ class ViewerComponent extends React.Component {
|
|||
};
|
||||
|
||||
loadApplicationBySlug = (slug, authentication_failed = false) => {
|
||||
appsService
|
||||
.getAppBySlug(slug)
|
||||
appService
|
||||
.fetchAppBySlug(slug)
|
||||
.then((data) => {
|
||||
if (authentication_failed && !data.current_version_id) {
|
||||
redirectToErrorPage(ERROR_TYPES.URL_UNAVAILABLE, {});
|
||||
const isAppPublic = data?.is_public;
|
||||
if (authentication_failed && !isAppPublic) {
|
||||
return redirectToErrorPage(ERROR_TYPES.URL_UNAVAILABLE, {});
|
||||
}
|
||||
this.setStateForApp(data);
|
||||
this.setStateForApp(data, true);
|
||||
this.setStateForContainer(data);
|
||||
this.setWindowTitle(data.name);
|
||||
})
|
||||
|
|
@ -265,11 +303,11 @@ class ViewerComponent extends React.Component {
|
|||
};
|
||||
|
||||
loadApplicationByVersion = (appId, versionId) => {
|
||||
appsService
|
||||
.getAppByVersion(appId, versionId)
|
||||
appService
|
||||
.fetchAppByVersion(appId, versionId)
|
||||
.then((data) => {
|
||||
this.setStateForApp(data);
|
||||
this.setStateForContainer(data);
|
||||
this.setStateForContainer(data, versionId);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
|
|
@ -278,6 +316,9 @@ class ViewerComponent extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
updateQueryConfirmationList = (queryConfirmationList) =>
|
||||
useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList);
|
||||
|
||||
setupViewer() {
|
||||
this.subscription = authenticationService.currentSession.subscribe((currentSession) => {
|
||||
const slug = this.props.params.slug;
|
||||
|
|
@ -306,6 +347,7 @@ class ViewerComponent extends React.Component {
|
|||
userVars,
|
||||
versionId,
|
||||
});
|
||||
|
||||
versionId ? this.loadApplicationByVersion(appId, versionId) : this.loadApplicationBySlug(slug);
|
||||
} else if (currentSession?.authentication_failed) {
|
||||
this.loadApplicationBySlug(slug, true);
|
||||
|
|
@ -352,9 +394,13 @@ class ViewerComponent extends React.Component {
|
|||
|
||||
handlePageSwitchingBasedOnURLparam() {
|
||||
const handleOnURL = this.props.params.pageHandle;
|
||||
const pageIdCorrespondingToHandleOnURL = handleOnURL
|
||||
? this.findPageIdFromHandle(handleOnURL)
|
||||
: this.state.appDefinition.homePageId;
|
||||
|
||||
const shouldShowPage = handleOnURL ? this.validatePageHandle(handleOnURL) : true;
|
||||
|
||||
if (!shouldShowPage) return this.switchPage(this.state.appDefinition.homePageId);
|
||||
|
||||
const pageIdCorrespondingToHandleOnURL =
|
||||
handleOnURL && shouldShowPage ? this.findPageIdFromHandle(handleOnURL) : this.state.appDefinition.homePageId;
|
||||
const currentPageId = this.state.currentPageId;
|
||||
|
||||
if (pageIdCorrespondingToHandleOnURL != this.state.currentPageId) {
|
||||
|
|
@ -388,20 +434,23 @@ class ViewerComponent extends React.Component {
|
|||
name: targetPage.name,
|
||||
},
|
||||
async () => {
|
||||
computeComponentState(this, this.state.appDefinition?.pages[this.state.currentPageId].components).then(
|
||||
async () => {
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
const { events } = this.state.appDefinition?.pages[this.state.currentPageId];
|
||||
for (const event of events ?? []) {
|
||||
await this.handleEvent(event.eventId, event);
|
||||
}
|
||||
}
|
||||
);
|
||||
computeComponentState(this.state.appDefinition?.pages[this.state.currentPageId].components).then(async () => {
|
||||
const currentPageEvents = this.state.events.filter(
|
||||
(event) => event.target === 'page' && event.sourceId === this.state.currentPageId
|
||||
);
|
||||
|
||||
await this.handleEvent('onPageLoad', currentPageEvents);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
validatePageHandle(handle) {
|
||||
const allPages = this.state.appDefinition.pages;
|
||||
return Object.values(allPages).some((page) => page.handle === handle && !page.disabled);
|
||||
}
|
||||
|
||||
findPageIdFromHandle(handle) {
|
||||
return (
|
||||
Object.entries(this.state.appDefinition.pages).filter(([_id, page]) => page.handle === handle)?.[0]?.[0] ??
|
||||
|
|
@ -466,7 +515,9 @@ class ViewerComponent extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
handleEvent = (eventName, options) => onEvent(this, eventName, options, 'view');
|
||||
handleEvent = (eventName, events, options) => {
|
||||
return onEvent(this.getViewerRef(), eventName, events, options, 'view');
|
||||
};
|
||||
|
||||
computeCanvasMaxWidth = () => {
|
||||
const { appDefinition } = this.state;
|
||||
|
|
@ -493,11 +544,11 @@ class ViewerComponent extends React.Component {
|
|||
deviceWindowWidth,
|
||||
defaultComponentStateComputed,
|
||||
dataQueries,
|
||||
queryConfirmationList,
|
||||
canvasWidth,
|
||||
} = this.state;
|
||||
|
||||
const currentCanvasWidth = canvasWidth;
|
||||
const queryConfirmationList = this.props?.queryConfirmationList ?? [];
|
||||
|
||||
const canvasMaxWidth = this.computeCanvasMaxWidth();
|
||||
|
||||
|
|
@ -531,8 +582,10 @@ class ViewerComponent extends React.Component {
|
|||
<Confirm
|
||||
show={queryConfirmationList.length > 0}
|
||||
message={'Do you want to run this query?'}
|
||||
onConfirm={(queryConfirmationData) => onQueryConfirmOrCancel(this, queryConfirmationData, true, 'view')}
|
||||
onCancel={() => onQueryConfirmOrCancel(this, queryConfirmationList[0], false, 'view')}
|
||||
onConfirm={(queryConfirmationData) =>
|
||||
onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationData, true, 'view')
|
||||
}
|
||||
onCancel={() => onQueryConfirmOrCancel(this.getViewerRef(), queryConfirmationList[0], false, 'view')}
|
||||
queryConfirmationData={queryConfirmationList[0]}
|
||||
key={queryConfirmationList[0]?.queryName}
|
||||
/>
|
||||
|
|
@ -591,7 +644,7 @@ class ViewerComponent extends React.Component {
|
|||
snapToGrid={true}
|
||||
appLoading={isLoading}
|
||||
darkMode={this.props.darkMode}
|
||||
onEvent={(eventName, options) => onEvent(this, eventName, options, 'view')}
|
||||
onEvent={this.handleEvent}
|
||||
mode="view"
|
||||
deviceWindowWidth={deviceWindowWidth}
|
||||
selectedComponent={this.state.selectedComponent}
|
||||
|
|
@ -602,11 +655,9 @@ class ViewerComponent extends React.Component {
|
|||
onComponentClick(this, id, component, 'view');
|
||||
}}
|
||||
onComponentOptionChanged={(component, optionName, value) => {
|
||||
return onComponentOptionChanged(this, component, optionName, value);
|
||||
return onComponentOptionChanged(component, optionName, value);
|
||||
}}
|
||||
onComponentOptionsChanged={(component, options) =>
|
||||
onComponentOptionsChanged(this, component, options)
|
||||
}
|
||||
onComponentOptionsChanged={onComponentOptionsChanged}
|
||||
canvasWidth={this.getCanvasWidth()}
|
||||
dataQueries={dataQueries}
|
||||
currentPageId={this.state.currentPageId}
|
||||
|
|
@ -629,19 +680,23 @@ class ViewerComponent extends React.Component {
|
|||
}
|
||||
const withStore = (Component) => (props) => {
|
||||
const currentState = useCurrentStateStore();
|
||||
const { currentLayout } = useEditorStore(
|
||||
const { currentLayout, queryConfirmationList } = useEditorStore(
|
||||
(state) => ({
|
||||
currentLayout: state?.currentLayout,
|
||||
queryConfirmationList: state?.queryConfirmationList,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
||||
const { updateState } = useAppDataActions();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
currentState={currentState}
|
||||
setCurrentState={currentState?.actions?.setCurrentState}
|
||||
currentLayout={currentLayout}
|
||||
updateState={updateState}
|
||||
queryConfirmationList={queryConfirmationList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export default function ExportAppModal({ title, show, closeModal, customClassNam
|
|||
const requestBody = {
|
||||
...appOpts,
|
||||
...(exportTjDb && { tooljet_database: tables }),
|
||||
organization_id: app.organization_id,
|
||||
organization_id: app.organization_id ?? app.organizationId,
|
||||
};
|
||||
|
||||
appsService
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import { useDataQueriesStore } from '@/_stores/dataQueriesStore';
|
|||
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import { useCurrentStateStore, getCurrentState } from '@/_stores/currentStateStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
import { camelizeKeys } from 'humps';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useEditorStore } from '@/_stores/editorStore';
|
||||
|
||||
const ERROR_TYPES = Object.freeze({
|
||||
|
|
@ -60,7 +62,7 @@ export function setCurrentStateAsync(_ref, changes) {
|
|||
});
|
||||
}
|
||||
|
||||
export function onComponentOptionsChanged(_ref, component, options) {
|
||||
export function onComponentOptionsChanged(component, options) {
|
||||
const componentName = component.name;
|
||||
const components = getCurrentState().components;
|
||||
let componentData = components[componentName];
|
||||
|
|
@ -76,7 +78,7 @@ export function onComponentOptionsChanged(_ref, component, options) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export function onComponentOptionChanged(_ref, component, option_name, value) {
|
||||
export function onComponentOptionChanged(component, option_name, value) {
|
||||
const componentName = component.name;
|
||||
const components = getCurrentState().components;
|
||||
let componentData = components[componentName];
|
||||
|
|
@ -322,12 +324,11 @@ export async function runTransformation(
|
|||
}
|
||||
}
|
||||
|
||||
export async function executeActionsForEventId(_ref, eventId, component, mode, customVariables) {
|
||||
const events = component?.definition?.events || [];
|
||||
const filteredEvents = events.filter((event) => event.eventId === eventId);
|
||||
export async function executeActionsForEventId(_ref, eventId, events = [], mode, customVariables) {
|
||||
const filteredEvents = events.filter((event) => event?.event.eventId === eventId);
|
||||
|
||||
for (const event of filteredEvents) {
|
||||
await executeAction(_ref, event, mode, customVariables); // skipcq: JS-0032
|
||||
await executeAction(_ref, event.event, mode, customVariables); // skipcq: JS-0032
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -336,13 +337,11 @@ export function onComponentClick(_ref, id, component, mode = 'edit') {
|
|||
}
|
||||
|
||||
export function onQueryConfirmOrCancel(_ref, queryConfirmationData, isConfirm = false, mode = 'edit') {
|
||||
const filtertedQueryConfirmation = _ref.state?.queryConfirmationList.filter(
|
||||
const filtertedQueryConfirmation = _ref?.queryConfirmationList.filter(
|
||||
(query) => query.queryId !== queryConfirmationData.queryId
|
||||
);
|
||||
|
||||
_ref.setState({
|
||||
queryConfirmationList: filtertedQueryConfirmation,
|
||||
});
|
||||
_ref.updateQueryConfirmationList(filtertedQueryConfirmation, 'check');
|
||||
isConfirm && runQuery(_ref, queryConfirmationData.queryId, queryConfirmationData.queryName, true, mode);
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +361,7 @@ function showModal(_ref, modal, show) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const modalMeta = _ref.state.appDefinition.pages[_ref.state.currentPageId].components[modalId];
|
||||
const modalMeta = _ref.appDefinition.pages[_ref.currentPageId].components[modalId]; //! NeedToFix
|
||||
|
||||
const _components = {
|
||||
...getCurrentState().components,
|
||||
|
|
@ -377,7 +376,7 @@ function showModal(_ref, modal, show) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function logoutAction(_ref) {
|
||||
function logoutAction() {
|
||||
localStorage.clear();
|
||||
authenticationService.logout(true);
|
||||
|
||||
|
|
@ -442,7 +441,7 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
return runQuery(_ref, queryId, name, undefined, mode, resolvedParams);
|
||||
}
|
||||
case 'logout': {
|
||||
return logoutAction(_ref);
|
||||
return logoutAction();
|
||||
}
|
||||
|
||||
case 'open-webpage': {
|
||||
|
|
@ -477,7 +476,7 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
if (mode === 'view') {
|
||||
_ref.props.navigate(url);
|
||||
_ref.navigate(url);
|
||||
} else {
|
||||
if (confirm('The app will be opened in a new tab as the action is triggered from the editor.')) {
|
||||
window.open(urlJoin(window.public_config?.TOOLJET_HOST, url));
|
||||
|
|
@ -522,7 +521,7 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'set-table-page': {
|
||||
setTablePageIndex(_ref, event.table, event.pageIndex);
|
||||
setTablePageIndex(event.table, event.pageIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -585,12 +584,13 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
|
||||
case 'switch-page': {
|
||||
const { name, disabled } = _ref.state.appDefinition.pages[event.pageId];
|
||||
const { name, disabled } = _ref.appDefinition.pages[event.pageId];
|
||||
|
||||
// Don't allow switching to disabled page in editor as well as viewer
|
||||
if (!disabled) {
|
||||
_ref.switchPage(event.pageId, resolveReferences(event.queryParams, getCurrentState(), [], customVariables));
|
||||
}
|
||||
if (_ref.state.appDefinition.pages[event.pageId]) {
|
||||
if (_ref.appDefinition.pages[event.pageId]) {
|
||||
if (disabled) {
|
||||
const generalProps = {
|
||||
navToDisablePage: {
|
||||
|
|
@ -612,12 +612,15 @@ function executeActionWithDebounce(_ref, event, mode, customVariables) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
||||
export async function onEvent(_ref, eventName, events, options = {}, mode = 'edit') {
|
||||
let _self = _ref;
|
||||
|
||||
const { customVariables } = options;
|
||||
if (eventName === 'onPageLoad') {
|
||||
await executeActionsForEventId(_ref, 'onPageLoad', { definition: { events: [options] } }, mode, customVariables);
|
||||
//hack to make sure that the page is loaded before executing the actions
|
||||
setTimeout(async () => {
|
||||
return await executeActionsForEventId(_ref, 'onPageLoad', events, mode, customVariables);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (eventName === 'onTrigger') {
|
||||
|
|
@ -635,6 +638,7 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
|
||||
if (eventName === 'onCalendarEventSelect') {
|
||||
const { component, calendarEvent } = options;
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
|
|
@ -644,7 +648,8 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
},
|
||||
},
|
||||
});
|
||||
executeActionsForEventId(_ref, 'onCalendarEventSelect', component, mode, customVariables);
|
||||
|
||||
executeActionsForEventId(_ref, 'onCalendarEventSelect', events, mode, customVariables);
|
||||
}
|
||||
|
||||
if (eventName === 'onCalendarSlotSelect') {
|
||||
|
|
@ -658,26 +663,18 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
},
|
||||
},
|
||||
});
|
||||
executeActionsForEventId(_ref, 'onCalendarSlotSelect', component, mode, customVariables);
|
||||
|
||||
executeActionsForEventId(_ref, 'onCalendarSlotSelect', events, mode, customVariables);
|
||||
}
|
||||
|
||||
if (eventName === 'onTableActionButtonClicked') {
|
||||
const { component, data, action, rowId } = options;
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
[component.name]: {
|
||||
...getCurrentState().components[component.name],
|
||||
selectedRow: data,
|
||||
selectedRowId: rowId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (action && action.events) {
|
||||
for (const event of action.events) {
|
||||
if (event.actionId) {
|
||||
// the event param uses a hacky workaround for using same format used by event manager ( multiple handlers )
|
||||
await executeAction(_self, { ...event, ...event.options }, mode, customVariables);
|
||||
const { action, tableActionEvents } = options;
|
||||
const executeableActions = tableActionEvents.filter((event) => event?.event?.ref === action?.name);
|
||||
|
||||
if (action && executeableActions) {
|
||||
for (const event of executeableActions) {
|
||||
if (event?.event?.actionId) {
|
||||
await executeAction(_self, event.event, mode, customVariables);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -686,23 +683,12 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
}
|
||||
|
||||
if (eventName === 'OnTableToggleCellChanged') {
|
||||
const { component, column, rowId, row } = options;
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...getCurrentState().components,
|
||||
[component.name]: {
|
||||
...getCurrentState().components[component.name],
|
||||
selectedRow: row,
|
||||
selectedRowId: rowId,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { column, tableColumnEvents } = options;
|
||||
|
||||
if (column && column.events) {
|
||||
for (const event of column.events) {
|
||||
if (event.actionId) {
|
||||
// the event param uses a hacky workaround for using same format used by event manager ( multiple handlers )
|
||||
await executeAction(_self, { ...event, ...event.options }, mode, customVariables);
|
||||
if (column && tableColumnEvents) {
|
||||
for (const event of tableColumnEvents) {
|
||||
if (event?.event?.actionId) {
|
||||
await executeAction(_self, event.event, mode, customVariables);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -763,18 +749,15 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
'onNewRowsAdded',
|
||||
].includes(eventName)
|
||||
) {
|
||||
const { component } = options;
|
||||
executeActionsForEventId(_ref, eventName, component, mode, customVariables);
|
||||
executeActionsForEventId(_ref, eventName, events, mode, customVariables);
|
||||
}
|
||||
|
||||
if (eventName === 'onBulkUpdate') {
|
||||
onComponentOptionChanged(_self, options.component, 'isSavingChanges', true);
|
||||
await executeActionsForEventId(_self, eventName, options.component, mode, customVariables);
|
||||
onComponentOptionChanged(_self, options.component, 'isSavingChanges', false);
|
||||
await executeActionsForEventId(_self, eventName, events, mode, customVariables);
|
||||
}
|
||||
|
||||
if (['onDataQuerySuccess', 'onDataQueryFailure'].includes(eventName)) {
|
||||
await executeActionsForEventId(_self, eventName, options, mode, customVariables);
|
||||
await executeActionsForEventId(_self, eventName, events, mode, customVariables);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -940,6 +923,10 @@ export function previewQuery(_ref, query, calledFromQuery = false, parameters =
|
|||
|
||||
export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode = 'edit', parameters = {}) {
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => query.id === queryId);
|
||||
const queryEvents = useAppDataStore
|
||||
.getState()
|
||||
.events.filter((event) => event.target === 'data_query' && event.sourceId === queryId);
|
||||
|
||||
let dataQuery = {};
|
||||
|
||||
if (query) {
|
||||
|
|
@ -951,9 +938,11 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
|
||||
const options = getQueryVariables(dataQuery.options, getCurrentState());
|
||||
|
||||
if (dataQuery.options.requestConfirmation) {
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
const queryConfirmationList = _ref.state?.queryConfirmationList ? [..._ref.state?.queryConfirmationList] : [];
|
||||
if (dataQuery.options?.requestConfirmation) {
|
||||
const queryConfirmationList = useEditorStore.getState().queryConfirmationList
|
||||
? [...useEditorStore.getState().queryConfirmationList]
|
||||
: [];
|
||||
|
||||
const queryConfirmation = {
|
||||
queryId,
|
||||
queryName,
|
||||
|
|
@ -963,9 +952,8 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
}
|
||||
|
||||
if (confirmed === undefined) {
|
||||
_ref.setState({
|
||||
queryConfirmationList,
|
||||
});
|
||||
//!check
|
||||
_ref.updateQueryConfirmationList(queryConfirmationList);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1071,9 +1059,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
},
|
||||
});
|
||||
resolve(data);
|
||||
onEvent(_self, 'onDataQueryFailure', {
|
||||
definition: { events: dataQuery.options.events },
|
||||
});
|
||||
onEvent(_self, 'onDataQueryFailure', queryEvents);
|
||||
if (mode !== 'view') {
|
||||
const err = query.kind == 'tooljetdb' ? data?.error || data : _.isEmpty(data.data) ? data : data.data;
|
||||
toast.error(err?.message);
|
||||
|
|
@ -1111,9 +1097,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
},
|
||||
});
|
||||
resolve(finalData);
|
||||
onEvent(_self, 'onDataQueryFailure', {
|
||||
definition: { events: dataQuery.options.events },
|
||||
});
|
||||
onEvent(_self, 'onDataQueryFailure', queryEvents);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1152,7 +1136,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
},
|
||||
});
|
||||
resolve({ status: 'ok', data: finalData });
|
||||
onEvent(_self, 'onDataQuerySuccess', { definition: { events: dataQuery.options.events } }, mode);
|
||||
onEvent(_self, 'onDataQuerySuccess', queryEvents, mode);
|
||||
}
|
||||
})
|
||||
.catch(({ error }) => {
|
||||
|
|
@ -1171,7 +1155,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode =
|
|||
});
|
||||
}
|
||||
|
||||
export function setTablePageIndex(_ref, tableId, index) {
|
||||
export function setTablePageIndex(tableId, index) {
|
||||
if (_.isEmpty(tableId)) {
|
||||
console.log('No table is associated with this event.');
|
||||
return Promise.resolve();
|
||||
|
|
@ -1192,52 +1176,69 @@ export function renderTooltip({ props, text }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function computeComponentState(_ref, components = {}) {
|
||||
let componentState = {};
|
||||
const currentComponents = getCurrentState().components;
|
||||
Object.keys(components).forEach((key) => {
|
||||
const component = components[key];
|
||||
const componentMeta = componentTypes.find((comp) => component.component.component === comp.component);
|
||||
/*
|
||||
@computeComponentState: (components = {}) => Promise<void>
|
||||
This change is made to enhance the code readability by optimizing the logic
|
||||
for computing component state. It replaces the previous try-catch block with
|
||||
a more efficient approach, precomputing the parent component types and using
|
||||
conditional checks for better performance and error handling.*/
|
||||
|
||||
const existingComponentName = Object.keys(currentComponents).find((comp) => currentComponents[comp].id === key);
|
||||
const existingValues = currentComponents[existingComponentName];
|
||||
export function computeComponentState(components = {}) {
|
||||
try {
|
||||
let componentState = {};
|
||||
const currentComponents = getCurrentState().components;
|
||||
|
||||
if (component.parent) {
|
||||
const parentComponent = components[component.parent];
|
||||
let isListView = false,
|
||||
isForm = false;
|
||||
try {
|
||||
isListView = parentComponent.component.component === 'Listview';
|
||||
isForm = parentComponent.component.component === 'Form';
|
||||
} catch {
|
||||
console.log('error');
|
||||
}
|
||||
// Precompute parent component types
|
||||
const parentComponentTypes = {};
|
||||
Object.keys(components).forEach((key) => {
|
||||
const { component } = components[key];
|
||||
parentComponentTypes[key] = component.component;
|
||||
});
|
||||
|
||||
if (!isListView && !isForm) {
|
||||
componentState[component.component.name] = {
|
||||
Object.keys(components).forEach((key) => {
|
||||
if (!components[key]) return;
|
||||
|
||||
const { component } = components[key];
|
||||
const componentMeta = componentTypes.find((comp) => component.component === comp.component);
|
||||
|
||||
const existingComponentName = Object.keys(currentComponents).find((comp) => currentComponents[comp].id === key);
|
||||
const existingValues = currentComponents[existingComponentName];
|
||||
|
||||
if (component.parent) {
|
||||
const parentComponentType = parentComponentTypes[component.parent];
|
||||
|
||||
if (parentComponentType !== 'Listview' && parentComponentType !== 'Form') {
|
||||
componentState[component.name] = {
|
||||
...componentMeta.exposedVariables,
|
||||
id: key,
|
||||
...existingValues,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
componentState[component.name] = {
|
||||
...componentMeta.exposedVariables,
|
||||
id: key,
|
||||
...existingValues,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
componentState[component.component.name] = {
|
||||
...componentMeta.exposedVariables,
|
||||
id: key,
|
||||
...existingValues,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...componentState,
|
||||
},
|
||||
});
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
components: {
|
||||
...componentState,
|
||||
},
|
||||
});
|
||||
|
||||
return setStateAsync(_ref, {
|
||||
defaultComponentStateComputed: true,
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
useEditorStore.getState().actions.updateEditorState({
|
||||
defaultComponentStateComputed: true,
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const getSvgIcon = (key, height = 50, width = 50, iconFile = undefined, styles = {}) => {
|
||||
|
|
@ -1253,13 +1254,13 @@ export const getSvgIcon = (key, height = 50, width = 50, iconFile = undefined, s
|
|||
};
|
||||
|
||||
export const debuggerActions = {
|
||||
error: (_self, errors) => {
|
||||
error: (errors) => {
|
||||
useCurrentStateStore.getState().actions.setErrors({
|
||||
...errors,
|
||||
});
|
||||
},
|
||||
|
||||
flush: (_self) => {
|
||||
flush: () => {
|
||||
useCurrentStateStore.getState().actions.setCurrentState({
|
||||
errors: {},
|
||||
});
|
||||
|
|
@ -1370,105 +1371,148 @@ export const getComponentName = (currentState, id) => {
|
|||
}
|
||||
};
|
||||
|
||||
const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefinition) => {
|
||||
const updateNewComponents = (pageId, appDefinition, newComponents, updateAppDefinition, componentMap, isCut) => {
|
||||
const newAppDefinition = JSON.parse(JSON.stringify(appDefinition));
|
||||
newComponents.forEach((newComponent) => {
|
||||
newComponent.component.name = computeComponentName(
|
||||
newComponent.component.component,
|
||||
newAppDefinition.pages[pageId].components
|
||||
);
|
||||
newAppDefinition.pages[pageId].components[newComponent.id] = newComponent;
|
||||
});
|
||||
updateAppDefinition(newAppDefinition);
|
||||
|
||||
newAppDefinition.pages[pageId].components = {
|
||||
...newAppDefinition.pages[pageId].components,
|
||||
...newComponents,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
componentAdded: true,
|
||||
containerChanges: true,
|
||||
};
|
||||
|
||||
if (!isCut) {
|
||||
opts.cloningComponent = componentMap;
|
||||
}
|
||||
|
||||
updateAppDefinition(newAppDefinition, opts);
|
||||
};
|
||||
|
||||
export const cloneComponents = (_ref, updateAppDefinition, isCloning = true, isCut = false) => {
|
||||
const { appDefinition, currentPageId } = _ref.state;
|
||||
const selectedComponents = useEditorStore.getState().selectedComponents;
|
||||
export const cloneComponents = (
|
||||
selectedComponents,
|
||||
appDefinition,
|
||||
currentPageId,
|
||||
updateAppDefinition,
|
||||
isCloning = true,
|
||||
isCut = false
|
||||
) => {
|
||||
if (selectedComponents.length < 1) return getSelectedText();
|
||||
|
||||
const { components: allComponents } = appDefinition.pages[currentPageId];
|
||||
|
||||
// if parent is selected, then remove the parent from the selected components
|
||||
const filteredSelectedComponents = selectedComponents.filter((component) => {
|
||||
const parentComponentId = component.component?.parent;
|
||||
if (parentComponentId) {
|
||||
// Check if the parent component is also selected
|
||||
const isParentSelected = selectedComponents.some((comp) => comp.id === parentComponentId);
|
||||
|
||||
// If the parent is selected, filter out the child component
|
||||
if (isParentSelected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
let newDefinition = _.cloneDeep(appDefinition);
|
||||
let newComponents = [],
|
||||
newComponentObj = {},
|
||||
addedComponentId = new Set();
|
||||
for (let selectedComponent of selectedComponents) {
|
||||
for (let selectedComponent of filteredSelectedComponents) {
|
||||
if (addedComponentId.has(selectedComponent.id)) continue;
|
||||
const component = {
|
||||
id: selectedComponent.id,
|
||||
component: allComponents[selectedComponent.id]?.component,
|
||||
layouts: allComponents[selectedComponent.id]?.layouts,
|
||||
parent: allComponents[selectedComponent.id]?.parent,
|
||||
componentId: selectedComponent.id,
|
||||
};
|
||||
addedComponentId.add(selectedComponent.id);
|
||||
let clonedComponent = JSON.parse(JSON.stringify(component));
|
||||
clonedComponent.parent = undefined;
|
||||
clonedComponent.children = [];
|
||||
clonedComponent.children = [...getChildComponents(allComponents, component, clonedComponent, addedComponentId)];
|
||||
newComponents = [...newComponents, clonedComponent];
|
||||
|
||||
newComponents.push(clonedComponent);
|
||||
const children = getAllChildComponents(allComponents, selectedComponent.id);
|
||||
|
||||
if (children.length > 0) {
|
||||
newComponents.push(...children);
|
||||
}
|
||||
|
||||
newComponentObj = {
|
||||
newComponents,
|
||||
isCloning,
|
||||
isCut,
|
||||
currentPageId,
|
||||
};
|
||||
}
|
||||
|
||||
if (isCloning) {
|
||||
addComponents(currentPageId, appDefinition, updateAppDefinition, undefined, newComponentObj);
|
||||
const parentId = selectedComponents[0]['component']?.parent ?? undefined;
|
||||
|
||||
addComponents(currentPageId, appDefinition, updateAppDefinition, parentId, newComponentObj, true);
|
||||
toast.success('Component cloned succesfully');
|
||||
} else if (isCut) {
|
||||
navigator.clipboard.writeText(JSON.stringify(newComponentObj));
|
||||
removeSelectedComponent(currentPageId, newDefinition, selectedComponents);
|
||||
updateAppDefinition(newDefinition);
|
||||
removeSelectedComponent(currentPageId, newDefinition, selectedComponents, updateAppDefinition);
|
||||
} else {
|
||||
navigator.clipboard.writeText(JSON.stringify(newComponentObj));
|
||||
const successMessage =
|
||||
newComponentObj.newComponents.length > 1 ? 'Components copied successfully' : 'Component copied successfully';
|
||||
toast.success(successMessage);
|
||||
}
|
||||
_ref.setState({ currentSidebarTab: 2 });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
useEditorStore.getState().actions.updateEditorState({
|
||||
currentSidebarTab: 2,
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const getChildComponents = (allComponents, component, parentComponent, addedComponentId) => {
|
||||
let childComponents = [],
|
||||
selectedChildComponents = [];
|
||||
const getAllChildComponents = (allComponents, parentId) => {
|
||||
const childComponents = [];
|
||||
|
||||
if (component.component.component === 'Tabs' || component.component.component === 'Calendar') {
|
||||
childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent?.startsWith(component.id));
|
||||
} else {
|
||||
childComponents = Object.keys(allComponents).filter((key) => allComponents[key].parent === component.id);
|
||||
}
|
||||
Object.keys(allComponents).forEach((componentId) => {
|
||||
const componentParentId = allComponents[componentId].component?.parent;
|
||||
|
||||
childComponents.forEach((componentId) => {
|
||||
let childComponent = JSON.parse(JSON.stringify(allComponents[componentId]));
|
||||
childComponent.id = componentId;
|
||||
const newComponent = JSON.parse(
|
||||
JSON.stringify({
|
||||
id: componentId,
|
||||
component: allComponents[componentId]?.component,
|
||||
layouts: allComponents[componentId]?.layouts,
|
||||
parent: allComponents[componentId]?.parent,
|
||||
})
|
||||
);
|
||||
addedComponentId.add(componentId);
|
||||
const isParentTabORCalendar =
|
||||
allComponents[parentId]?.component?.component === 'Tabs' ||
|
||||
allComponents[parentId]?.component?.component === 'Calendar';
|
||||
|
||||
if ((component.component.component === 'Tabs') | (component.component.component === 'Calendar')) {
|
||||
const childTabId = childComponent.parent.split('-').at(-1);
|
||||
childComponent.parent = `${parentComponent.id}-${childTabId}`;
|
||||
} else {
|
||||
childComponent.parent = parentComponent.id;
|
||||
if (componentParentId && isParentTabORCalendar) {
|
||||
const childComponent = allComponents[componentId];
|
||||
const childTabId = componentParentId.split('-').at(-1);
|
||||
if (componentParentId === `${parentId}-${childTabId}`) {
|
||||
childComponent.componentId = componentId;
|
||||
childComponents.push(childComponent);
|
||||
|
||||
// Recursively find children of the current child component
|
||||
const childrenOfChild = getAllChildComponents(allComponents, componentId);
|
||||
childComponents.push(...childrenOfChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (componentParentId === parentId) {
|
||||
const childComponent = allComponents[componentId];
|
||||
childComponent.componentId = componentId;
|
||||
childComponents.push(childComponent);
|
||||
|
||||
// Recursively find children of the current child component
|
||||
const childrenOfChild = getAllChildComponents(allComponents, componentId);
|
||||
childComponents.push(...childrenOfChild);
|
||||
}
|
||||
parentComponent.children = [...(parentComponent.children || []), childComponent];
|
||||
childComponent.children = [...getChildComponents(allComponents, newComponent, childComponent, addedComponentId)];
|
||||
selectedChildComponents.push(childComponent);
|
||||
});
|
||||
|
||||
return selectedChildComponents;
|
||||
return childComponents;
|
||||
};
|
||||
|
||||
const updateComponentLayout = (components, parentId, isCut = false) => {
|
||||
let prevComponent;
|
||||
components.forEach((component, index) => {
|
||||
Object.keys(component.layouts).map((layout) => {
|
||||
if (parentId !== undefined) {
|
||||
if (parentId !== undefined && !component?.component?.parent) {
|
||||
if (index > 0) {
|
||||
component.layouts[layout].top = prevComponent.layouts[layout].top + prevComponent.layouts[layout].height;
|
||||
component.layouts[layout].left = 0;
|
||||
|
|
@ -1477,56 +1521,100 @@ const updateComponentLayout = (components, parentId, isCut = false) => {
|
|||
component.layouts[layout].left = 0;
|
||||
}
|
||||
prevComponent = component;
|
||||
} else if (!isCut) {
|
||||
} else if (!isCut && !component.component.parent) {
|
||||
component.layouts[layout].top = component.layouts[layout].top + component.layouts[layout].height;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
//
|
||||
const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
|
||||
const parentId = componentParentId ?? component.component?.parent?.split('-').slice(0, -1).join('-');
|
||||
|
||||
export const addComponents = (pageId, appDefinition, appDefinitionChanged, parentId = undefined, newComponentObj) => {
|
||||
const finalComponents = [];
|
||||
const parentComponent = allComponents.find((comp) => comp.componentId === parentId);
|
||||
|
||||
if (parentComponent) {
|
||||
return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const addComponents = (
|
||||
pageId,
|
||||
appDefinition,
|
||||
appDefinitionChanged,
|
||||
parentId = undefined,
|
||||
newComponentObj,
|
||||
fromClipboard = false
|
||||
) => {
|
||||
const finalComponents = {};
|
||||
const componentMap = {};
|
||||
let parentComponent = undefined;
|
||||
const { isCloning, isCut, newComponents: pastedComponent = [] } = newComponentObj;
|
||||
const { isCloning, isCut, newComponents: pastedComponents = [], currentPageId } = newComponentObj;
|
||||
|
||||
if (parentId) {
|
||||
const id = Object.keys(appDefinition.pages[pageId].components).filter((key) => parentId.startsWith(key));
|
||||
parentComponent = JSON.parse(JSON.stringify(appDefinition.pages[pageId].components[id[0]]));
|
||||
parentComponent.id = parentId;
|
||||
}
|
||||
|
||||
!isCloning && updateComponentLayout(pastedComponent, parentId, isCut);
|
||||
pastedComponents.forEach((component) => {
|
||||
const newComponentId = isCut ? component.componentId : uuidv4();
|
||||
const componentName = computeComponentName(component.component.component, {
|
||||
...appDefinition.pages[pageId].components,
|
||||
...finalComponents,
|
||||
});
|
||||
|
||||
const buildComponents = (components, parentComponent = undefined, skipTabCalendarCheck = false) => {
|
||||
if (Array.isArray(components) && components.length > 0) {
|
||||
components.forEach((component) => {
|
||||
const newComponent = {
|
||||
id: uuidv4(),
|
||||
component: component?.component,
|
||||
layouts: component?.layouts,
|
||||
};
|
||||
if (parentComponent) {
|
||||
if (
|
||||
!skipTabCalendarCheck &&
|
||||
(parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar')
|
||||
) {
|
||||
const childTabId = component.parent.split('-').at(-1);
|
||||
newComponent.parent = `${parentComponent.id}-${childTabId}`;
|
||||
} else {
|
||||
newComponent.parent = parentComponent.id;
|
||||
}
|
||||
}
|
||||
finalComponents.push(newComponent);
|
||||
if (component.children.length > 0) {
|
||||
buildComponents(component.children, newComponent);
|
||||
}
|
||||
});
|
||||
const isParentTabOrCalendar = isChildOfTabsOrCalendar(component, pastedComponents, parentId);
|
||||
const parentRef = isParentTabOrCalendar
|
||||
? component.component.parent.split('-').slice(0, -1).join('-')
|
||||
: component.component.parent;
|
||||
const isParentAlsoCopied = parentRef && componentMap[parentRef];
|
||||
|
||||
componentMap[component.componentId] = newComponentId;
|
||||
let isChild = isParentAlsoCopied ? component.component.parent : parentId;
|
||||
const componentData = JSON.parse(JSON.stringify(component.component));
|
||||
|
||||
if (isCloning && parentId && !componentData.parent) {
|
||||
isChild = component.component.parent;
|
||||
}
|
||||
};
|
||||
|
||||
buildComponents(pastedComponent, parentComponent, true);
|
||||
if (!parentComponent && !isParentAlsoCopied && fromClipboard) {
|
||||
isChild = undefined;
|
||||
componentData.parent = undefined;
|
||||
}
|
||||
|
||||
updateNewComponents(pageId, appDefinition, finalComponents, appDefinitionChanged);
|
||||
if (!isCloning && parentComponent && fromClipboard) {
|
||||
componentData.parent = isParentAlsoCopied ?? parentId;
|
||||
} else if (isChild && isChildOfTabsOrCalendar(component, pastedComponents, parentId)) {
|
||||
const parentId = component.component.parent.split('-').slice(0, -1).join('-');
|
||||
const childTabId = component.component.parent.split('-').at(-1);
|
||||
|
||||
componentData.parent = `${componentMap[parentId]}-${childTabId}`;
|
||||
} else if (isChild) {
|
||||
const isParentInMap = componentMap[isChild] !== undefined;
|
||||
|
||||
componentData.parent = isParentInMap ? componentMap[isChild] : isChild;
|
||||
}
|
||||
|
||||
const newComponent = {
|
||||
component: {
|
||||
...componentData,
|
||||
name: componentName,
|
||||
},
|
||||
layouts: component.layouts,
|
||||
};
|
||||
|
||||
finalComponents[newComponentId] = newComponent;
|
||||
|
||||
// const doesComponentHaveChildren = getAllChildComponents
|
||||
});
|
||||
|
||||
if (currentPageId === pageId) {
|
||||
updateComponentLayout(pastedComponents, parentId, isCut);
|
||||
}
|
||||
|
||||
updateNewComponents(pageId, appDefinition, finalComponents, appDefinitionChanged, componentMap, isCut);
|
||||
!isCloning && toast.success('Component pasted succesfully');
|
||||
};
|
||||
|
||||
|
|
@ -1594,6 +1682,7 @@ export const addNewWidgetToTheEditor = (
|
|||
|
||||
const widgetsWithDefaultComponents = ['Listview', 'Tabs', 'Form', 'Kanban'];
|
||||
|
||||
const nonActiveLayout = currentLayout === 'desktop' ? 'mobile' : 'desktop';
|
||||
const newComponent = {
|
||||
id: uuidv4(),
|
||||
component: componentData,
|
||||
|
|
@ -1604,6 +1693,12 @@ export const addNewWidgetToTheEditor = (
|
|||
width: defaultWidth,
|
||||
height: defaultHeight,
|
||||
},
|
||||
[nonActiveLayout]: {
|
||||
top: top,
|
||||
left: left,
|
||||
width: defaultWidth,
|
||||
height: defaultHeight,
|
||||
},
|
||||
},
|
||||
|
||||
withDefaultChildren: widgetsWithDefaultComponents.includes(componentData.component),
|
||||
|
|
@ -1619,26 +1714,38 @@ export function snapToGrid(canvasWidth, x, y) {
|
|||
const snappedY = Math.round(y / 10) * 10;
|
||||
return [snappedX, snappedY];
|
||||
}
|
||||
export const removeSelectedComponent = (pageId, newDefinition, selectedComponents) => {
|
||||
selectedComponents.forEach((component) => {
|
||||
let childComponents = [];
|
||||
export const removeSelectedComponent = (pageId, newDefinition, selectedComponents, updateAppDefinition) => {
|
||||
const toDeleteComponents = [];
|
||||
|
||||
if (newDefinition.pages[pageId].components[component.id]?.component?.component === 'Tabs') {
|
||||
childComponents = Object.keys(newDefinition.pages[pageId].components).filter((key) =>
|
||||
newDefinition.pages[pageId].components[key].parent?.startsWith(component.id)
|
||||
);
|
||||
} else {
|
||||
childComponents = Object.keys(newDefinition.pages[pageId].components).filter(
|
||||
(key) => newDefinition.pages[pageId].components[key].parent === component.id
|
||||
);
|
||||
if (selectedComponents.length < 1) return getSelectedText();
|
||||
|
||||
const { components: allComponents } = newDefinition.pages[pageId];
|
||||
|
||||
const findAllChildComponents = (componentId) => {
|
||||
if (!toDeleteComponents.includes(componentId)) {
|
||||
toDeleteComponents.push(componentId);
|
||||
|
||||
// Find the children of this component
|
||||
const children = getAllChildComponents(allComponents, componentId).map((child) => child.componentId);
|
||||
|
||||
if (children.length > 0) {
|
||||
// Recursively find children of children
|
||||
children.forEach((child) => {
|
||||
findAllChildComponents(child);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
childComponents.forEach((componentId) => {
|
||||
delete newDefinition.pages[pageId].components[componentId];
|
||||
});
|
||||
|
||||
delete newDefinition.pages[pageId].components[component.id];
|
||||
selectedComponents.forEach((component) => {
|
||||
findAllChildComponents(component.id);
|
||||
});
|
||||
|
||||
toDeleteComponents.forEach((componentId) => {
|
||||
delete newDefinition.pages[pageId].components[componentId];
|
||||
});
|
||||
|
||||
updateAppDefinition(newDefinition, { componentDefinitionChanged: true, componentDeleted: true, componentCut: true });
|
||||
};
|
||||
|
||||
const getSelectedText = () => {
|
||||
|
|
@ -1678,7 +1785,7 @@ export const runQueries = (queries, _ref) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const computeQueryState = (queries, _ref) => {
|
||||
export const computeQueryState = (queries) => {
|
||||
let queryState = {};
|
||||
queries.forEach((query) => {
|
||||
if (query.plugin?.plugin_id) {
|
||||
|
|
@ -1705,6 +1812,87 @@ export const computeQueryState = (queries, _ref) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const buildComponentMetaDefinition = (components = {}) => {
|
||||
for (const componentId in components) {
|
||||
const currentComponentData = components[componentId];
|
||||
|
||||
const componentMeta = componentTypes.find((comp) => currentComponentData.component.component === comp.component);
|
||||
|
||||
const mergedDefinition = {
|
||||
...componentMeta.definition,
|
||||
|
||||
properties: {
|
||||
...componentMeta.definition.properties,
|
||||
...currentComponentData?.component.definition.properties,
|
||||
},
|
||||
|
||||
styles: {
|
||||
...componentMeta.definition.styles,
|
||||
...currentComponentData?.component.definition.styles,
|
||||
},
|
||||
generalStyles: {
|
||||
...componentMeta.definition.generalStyles,
|
||||
...currentComponentData?.component.definition.generalStyles,
|
||||
},
|
||||
validation: {
|
||||
...componentMeta.definition.validation,
|
||||
...currentComponentData?.component.definition.validation,
|
||||
},
|
||||
others: {
|
||||
...componentMeta.definition.others,
|
||||
...currentComponentData?.component.definition.others,
|
||||
},
|
||||
general: {
|
||||
...componentMeta.definition.general,
|
||||
...currentComponentData?.component.definition.general,
|
||||
},
|
||||
};
|
||||
|
||||
const mergedComponent = {
|
||||
component: {
|
||||
...componentMeta,
|
||||
...currentComponentData.component,
|
||||
},
|
||||
layouts: {
|
||||
...currentComponentData.layouts,
|
||||
},
|
||||
withDefaultChildren: componentMeta.withDefaultChildren ?? false,
|
||||
};
|
||||
|
||||
mergedComponent.component.definition = mergedDefinition;
|
||||
|
||||
components[componentId] = mergedComponent;
|
||||
}
|
||||
|
||||
return components;
|
||||
};
|
||||
|
||||
export const buildAppDefinition = (data) => {
|
||||
const editingVersion = _.omit(camelizeKeys(data.editing_version), ['definition', 'updatedAt', 'createdAt', 'name']);
|
||||
|
||||
editingVersion['currentVersionId'] = editingVersion.id;
|
||||
_.unset(editingVersion, 'id');
|
||||
|
||||
const pages = data.pages.reduce((acc, page) => {
|
||||
const currentComponents = buildComponentMetaDefinition(_.cloneDeep(page?.components));
|
||||
|
||||
page.components = currentComponents;
|
||||
|
||||
acc[page.id] = page;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const appJSON = {
|
||||
globalSettings: editingVersion.globalSettings,
|
||||
homePageId: editingVersion.homePageId,
|
||||
showViewerNavigation: editingVersion.showViewerNavigation ?? true,
|
||||
pages: pages,
|
||||
};
|
||||
|
||||
return appJSON;
|
||||
};
|
||||
|
||||
export const removeFunctionObjects = (obj) => {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'function') {
|
||||
|
|
|
|||
|
|
@ -570,10 +570,11 @@ export const hightlightMentionedUserInComment = (comment) => {
|
|||
};
|
||||
|
||||
export const generateAppActions = (_ref, queryId, mode, isPreview = false) => {
|
||||
const currentPageId = _ref.state.currentPageId;
|
||||
const currentComponents = _ref.state?.appDefinition?.pages[currentPageId]?.components
|
||||
? Object.entries(_ref.state.appDefinition.pages[currentPageId]?.components)
|
||||
const currentPageId = _ref.currentPageId;
|
||||
const currentComponents = _ref.appDefinition?.pages[currentPageId]?.components
|
||||
? Object.entries(_ref.appDefinition.pages[currentPageId]?.components)
|
||||
: {};
|
||||
|
||||
const runQuery = (queryName = '', parameters) => {
|
||||
const query = useDataQueriesStore.getState().dataQueries.find((query) => {
|
||||
const isFound = query.name === queryName;
|
||||
|
|
@ -743,7 +744,7 @@ export const generateAppActions = (_ref, queryId, mode, isPreview = false) => {
|
|||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
const pages = _ref.state.appDefinition.pages;
|
||||
const pages = _ref.appDefinition.pages;
|
||||
const pageId = Object.keys(pages).find((key) => pages[key].handle === pageHandle);
|
||||
|
||||
if (!pageId) {
|
||||
|
|
|
|||
37
frontend/src/_hooks/useDebouncedArrowKeyPress.js
Normal file
37
frontend/src/_hooks/useDebouncedArrowKeyPress.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// useDebouncedArrowKeyPress.js
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function useDebouncedArrowKeyPress(delay) {
|
||||
const [lastKeyPressTimestamp, setLastKeyPressTimestamp] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
|
||||
function handleKeyPress(event) {
|
||||
if (
|
||||
event.key === 'ArrowUp' ||
|
||||
event.key === 'ArrowDown' ||
|
||||
event.key === 'ArrowLeft' ||
|
||||
event.key === 'ArrowRight'
|
||||
) {
|
||||
// Arrow key was pressed; debounce the update
|
||||
clearTimeout(timer);
|
||||
|
||||
timer = setTimeout(() => {
|
||||
// Trigger the update only after the specified delay
|
||||
setLastKeyPressTimestamp(Date.now());
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyPress);
|
||||
};
|
||||
}, [delay]);
|
||||
|
||||
return lastKeyPressTimestamp;
|
||||
}
|
||||
|
||||
export default useDebouncedArrowKeyPress;
|
||||
|
|
@ -3,6 +3,24 @@ import { authHeader, handleResponse } from '@/_helpers';
|
|||
|
||||
export const appService = {
|
||||
getConfig,
|
||||
getAll,
|
||||
createApp,
|
||||
cloneApp,
|
||||
exportApp,
|
||||
importApp,
|
||||
exportResource,
|
||||
importResource,
|
||||
cloneResource,
|
||||
changeIcon,
|
||||
deleteApp,
|
||||
getApp,
|
||||
fetchApp,
|
||||
getAppBySlug,
|
||||
fetchAppBySlug,
|
||||
getAppByVersion,
|
||||
fetchAppByVersion,
|
||||
saveApp,
|
||||
getAppUsers,
|
||||
createAppUser,
|
||||
setPasswordFromToken,
|
||||
acceptInvite,
|
||||
|
|
@ -13,6 +31,142 @@ function getConfig() {
|
|||
return fetch(`${config.apiUrl}/config`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getAll(page, folder, searchKey) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
if (page === 0) return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse);
|
||||
else
|
||||
return fetch(
|
||||
`${config.apiUrl}/apps?page=${page}&folder=${folder || ''}&searchKey=${searchKey}`,
|
||||
requestOptions
|
||||
).then(handleResponse);
|
||||
}
|
||||
|
||||
function createApp(body = {}) {
|
||||
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
|
||||
return fetch(`${config.apiUrl}/apps`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function cloneApp(id) {
|
||||
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}/clone`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function exportApp(id, versionId) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}/export${versionId ? `?versionId=${versionId}` : ''}`, requestOptions).then(
|
||||
handleResponse
|
||||
);
|
||||
}
|
||||
|
||||
function exportResource(body) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: authHeader(),
|
||||
body: JSON.stringify(body),
|
||||
credentials: 'include',
|
||||
};
|
||||
|
||||
return fetch(`${config.apiUrl}/v2/resources/export`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function importResource(body) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
return fetch(`${config.apiUrl}/v2/resources/import`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function cloneResource(body) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: authHeader(),
|
||||
body: JSON.stringify(body),
|
||||
credentials: 'include',
|
||||
};
|
||||
|
||||
return fetch(`${config.apiUrl}/v2/resources/clone`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getVersions(id) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}/versions`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function importApp(body) {
|
||||
const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) };
|
||||
return fetch(`${config.apiUrl}/apps/import`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getTables(id) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}/tables`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function changeIcon(icon, appId) {
|
||||
const requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ icon }),
|
||||
};
|
||||
return fetch(`${config.apiUrl}/apps/${appId}/icons`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getApp(id, accessType) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}${accessType ? `?access_type=${accessType}` : ''}`, requestOptions).then(
|
||||
handleResponse
|
||||
);
|
||||
}
|
||||
|
||||
// v2 api for fetching app
|
||||
function fetchApp(id) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/v2/apps/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function deleteApp(id) {
|
||||
const requestOptions = { method: 'DELETE', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getAppBySlug(slug) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/slugs/${slug}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function fetchAppBySlug(slug) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/v2/apps/slugs/${slug}`, requestOptions).then((resp) => handleResponse(resp, true));
|
||||
}
|
||||
|
||||
function getAppByVersion(appId, versionId) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
function fetchAppByVersion(appId, versionId) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function saveApp(id, attributes) {
|
||||
const requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ app: attributes }),
|
||||
};
|
||||
return fetch(`${config.apiUrl}/apps/${id}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function getAppUsers(id) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${id}/users`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function createAppUser(app_id, org_user_id, role) {
|
||||
const body = {
|
||||
app_id,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,16 @@ import { authHeader, handleResponse } from '@/_helpers';
|
|||
export const appVersionService = {
|
||||
getAll,
|
||||
getOne,
|
||||
getAppVersionData,
|
||||
create,
|
||||
del,
|
||||
save,
|
||||
autoSaveApp,
|
||||
saveAppVersionEventHandlers,
|
||||
createAppVersionEventHandler,
|
||||
deleteAppVersionEventHandler,
|
||||
clonePage,
|
||||
findAllEventsWithSourceId,
|
||||
};
|
||||
|
||||
function getAll(appId) {
|
||||
|
|
@ -18,6 +25,10 @@ function getOne(appId, versionId) {
|
|||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
function getAppVersionData(appId, versionId) {
|
||||
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function create(appId, versionName, versionFromId) {
|
||||
const body = {
|
||||
|
|
@ -47,6 +58,7 @@ function save(appId, versionId, values, isUserSwitchedVersion = false) {
|
|||
const body = { is_user_switched_version: isUserSwitchedVersion };
|
||||
if (values.definition) body['definition'] = values.definition;
|
||||
if (values.name) body['name'] = values.name;
|
||||
if (values.diff) body['app_diff'] = values.diff;
|
||||
|
||||
const requestOptions = {
|
||||
method: 'PUT',
|
||||
|
|
@ -56,3 +68,118 @@ function save(appId, versionId, values, isUserSwitchedVersion = false) {
|
|||
};
|
||||
return fetch(`${config.apiUrl}/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function autoSaveApp(
|
||||
appId,
|
||||
versionId,
|
||||
diff,
|
||||
type,
|
||||
pageId,
|
||||
operation,
|
||||
isUserSwitchedVersion = false,
|
||||
isComponentCutProcess = false
|
||||
) {
|
||||
const OPERATION = {
|
||||
create: 'POST',
|
||||
update: 'PUT',
|
||||
delete: 'DELETE',
|
||||
};
|
||||
|
||||
const bodyMappings = {
|
||||
pages: {
|
||||
create: { ...diff },
|
||||
delete: { ...diff },
|
||||
},
|
||||
global_settings: {
|
||||
update: { ...diff },
|
||||
},
|
||||
};
|
||||
|
||||
const body = !type
|
||||
? { ...diff }
|
||||
: bodyMappings[type]?.[operation] || {
|
||||
is_user_switched_version: isUserSwitchedVersion,
|
||||
pageId,
|
||||
diff,
|
||||
};
|
||||
|
||||
if (type === 'components' && operation === 'delete' && isComponentCutProcess) {
|
||||
body['is_component_cut'] = true;
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
method: OPERATION[operation],
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
|
||||
const url = `${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/${type ?? ''}`;
|
||||
|
||||
return fetch(url, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function saveAppVersionEventHandlers(appId, versionId, events, updateType = 'update') {
|
||||
const body = {
|
||||
events,
|
||||
updateType,
|
||||
};
|
||||
|
||||
const requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/events`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function createAppVersionEventHandler(appId, versionId, event) {
|
||||
const body = {
|
||||
...event,
|
||||
};
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(body),
|
||||
};
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/events`, requestOptions).then(handleResponse);
|
||||
}
|
||||
|
||||
function deleteAppVersionEventHandler(appId, versionId, eventId) {
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
};
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/events/${eventId}`, requestOptions).then(
|
||||
handleResponse
|
||||
);
|
||||
}
|
||||
|
||||
function clonePage(appId, versionId, pageId) {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
};
|
||||
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/pages/${pageId}/clone`, requestOptions).then(
|
||||
handleResponse
|
||||
);
|
||||
}
|
||||
|
||||
function findAllEventsWithSourceId(appId, versionId, sourceId = undefined) {
|
||||
const requestOptions = {
|
||||
method: 'GET',
|
||||
headers: authHeader(),
|
||||
credentials: 'include',
|
||||
};
|
||||
|
||||
return fetch(
|
||||
`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/events${sourceId ? `?sourceId=${sourceId}` : ''}
|
||||
`,
|
||||
requestOptions
|
||||
).then(handleResponse);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,126 @@
|
|||
import { appVersionService } from '@/_services';
|
||||
import { create, zustandDevTools } from './utils';
|
||||
|
||||
const initialState = {
|
||||
editingVersion: null,
|
||||
currentUser: null,
|
||||
apps: [],
|
||||
appName: null,
|
||||
slug: null,
|
||||
isPublic: null,
|
||||
isMaintenanceOn: null,
|
||||
organizationId: null,
|
||||
currentVersionId: null,
|
||||
userId: null,
|
||||
app: {},
|
||||
components: [],
|
||||
pages: [],
|
||||
layouts: [],
|
||||
events: [],
|
||||
eventHandlers: [],
|
||||
appDefinitionDiff: null,
|
||||
appDiffOptions: {},
|
||||
isSaving: false,
|
||||
appId: null,
|
||||
areOthersOnSameVersionAndPage: false,
|
||||
appVersionPreviewLink: null,
|
||||
};
|
||||
|
||||
export const useAppDataStore = create(
|
||||
zustandDevTools(
|
||||
(set) => ({
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
updateEditingVersion: (version) => set(() => ({ editingVersion: version })),
|
||||
updateApps: (apps) => set(() => ({ apps: apps })),
|
||||
updateState: (state) => set((prev) => ({ ...prev, ...state })),
|
||||
updateAppDefinitionDiff: (appDefinitionDiff) => set(() => ({ appDefinitionDiff: appDefinitionDiff })),
|
||||
updateAppVersion: (appId, versionId, pageId, appDefinitionDiff, isUserSwitchedVersion = false) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const isComponentCutProcess = get().appDiffOptions?.componentCut === true;
|
||||
|
||||
appVersionService
|
||||
.autoSaveApp(
|
||||
appId,
|
||||
versionId,
|
||||
appDefinitionDiff.updateDiff,
|
||||
appDefinitionDiff.type,
|
||||
pageId,
|
||||
appDefinitionDiff.operation,
|
||||
isUserSwitchedVersion,
|
||||
isComponentCutProcess
|
||||
)
|
||||
.then(() => {
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
reject(error);
|
||||
})
|
||||
.finally(() => resolve());
|
||||
});
|
||||
},
|
||||
updateAppVersionEventHandlers: async (events, updateType = 'update') => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const response = await appVersionService.saveAppVersionEventHandlers(appId, versionId, events, updateType);
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
const updatedEvents = get().events;
|
||||
|
||||
updatedEvents.forEach((e, index) => {
|
||||
const toUpdate = response.find((r) => r.id === e.id);
|
||||
if (toUpdate) {
|
||||
updatedEvents[index] = toUpdate;
|
||||
}
|
||||
});
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
},
|
||||
|
||||
createAppVersionEventHandlers: async (event) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
const response = await appVersionService.createAppVersionEventHandler(appId, versionId, event);
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
updatedEvents.push(response);
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
},
|
||||
|
||||
deleteAppVersionEventHandler: async (eventId) => {
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
const appId = get().appId;
|
||||
const versionId = get().currentVersionId;
|
||||
|
||||
const updatedEvents = get().events;
|
||||
|
||||
const response = await appVersionService.deleteAppVersionEventHandler(appId, versionId, eventId);
|
||||
useAppDataStore.getState().actions.setIsSaving(false);
|
||||
if (response?.affected === 1) {
|
||||
updatedEvents.splice(
|
||||
updatedEvents.findIndex((e) => e.id === eventId),
|
||||
1
|
||||
);
|
||||
|
||||
set(() => ({ events: updatedEvents }));
|
||||
}
|
||||
},
|
||||
autoUpdateEventStore: async (versionId) => {
|
||||
const appId = get().appId;
|
||||
const response = await appVersionService.findAllEventsWithSourceId(appId, versionId);
|
||||
|
||||
set(() => ({ events: response }));
|
||||
},
|
||||
setIsSaving: (isSaving) => set(() => ({ isSaving })),
|
||||
setAppId: (appId) => set(() => ({ appId })),
|
||||
setAppPreviewLink: (appVersionPreviewLink) => set(() => ({ appVersionPreviewLink })),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Data Store' }
|
||||
|
|
@ -23,3 +130,6 @@ export const useAppDataStore = create(
|
|||
export const useEditingVersion = () => useAppDataStore((state) => state.editingVersion);
|
||||
export const useIsSaving = () => useAppDataStore((state) => state.isSaving);
|
||||
export const useUpdateEditingVersion = () => useAppDataStore((state) => state.actions);
|
||||
export const useCurrentUser = () => useAppDataStore((state) => state.currentUser);
|
||||
export const useAppInfo = () => useAppDataStore((state) => state);
|
||||
export const useAppDataActions = () => useAppDataStore((state) => state.actions);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const initialState = {
|
|||
isUserEditingTheVersion: false,
|
||||
releasedVersionId: null,
|
||||
isVersionReleased: false,
|
||||
appVersions: [],
|
||||
};
|
||||
|
||||
export const useAppVersionStore = create(
|
||||
|
|
@ -21,8 +22,12 @@ export const useAppVersionStore = create(
|
|||
releasedVersionId: versionId,
|
||||
isVersionReleased: get().editingVersion?.id ? get().editingVersion?.id === versionId : false,
|
||||
}),
|
||||
setAppVersions: (versions) => set({ appVersions: versions }),
|
||||
},
|
||||
}),
|
||||
{ name: 'App Version Manager Store' }
|
||||
)
|
||||
);
|
||||
|
||||
export const useAppVersionActions = () => useAppVersionStore((state) => state.actions);
|
||||
export const useAppVersionState = () => useAppVersionStore((state) => state);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export const useCurrentState = () =>
|
|||
page: state.page,
|
||||
succededQuery: state.succededQuery,
|
||||
constants: state.constants,
|
||||
layout: state.layout,
|
||||
};
|
||||
}, shallow);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { create, zustandDevTools } from './utils';
|
||||
import { getDefaultOptions } from './storeHelper';
|
||||
import { dataqueryService } from '@/_services';
|
||||
import debounce from 'lodash/debounce';
|
||||
// import debounce from 'lodash/debounce';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import { useAppVersionStore } from '@/_stores/appVersionStore';
|
||||
|
|
@ -9,6 +9,7 @@ import { runQueries } from '@/_helpers/appUtils';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { isEmpty, throttle } from 'lodash';
|
||||
import { useEditorStore } from './editorStore';
|
||||
|
||||
const initialState = {
|
||||
dataQueries: [],
|
||||
|
|
@ -30,15 +31,27 @@ export const useDataQueriesStore = create(
|
|||
...initialState,
|
||||
actions: {
|
||||
// TODO: Remove editor state while changing currentState
|
||||
fetchDataQueries: async (appId, selectFirstQuery = false, runQueriesOnAppLoad = false, editorRef) => {
|
||||
fetchDataQueries: async (appVersionId, selectFirstQuery = false, runQueriesOnAppLoad = false, ref) => {
|
||||
set({ loadingDataQueries: true });
|
||||
const data = await dataqueryService.getAll(appId);
|
||||
const data = await dataqueryService.getAll(appVersionId);
|
||||
set((state) => ({
|
||||
dataQueries: sortByAttribute(data.data_queries, state.sortBy, state.sortOrder),
|
||||
loadingDataQueries: false,
|
||||
}));
|
||||
// Runs query on loading application
|
||||
if (runQueriesOnAppLoad) runQueries(data.data_queries, editorRef);
|
||||
|
||||
if (data.data_queries.length !== 0) {
|
||||
const queryConfirmationList = [];
|
||||
data.data_queries.forEach(({ id, name, options }) => {
|
||||
if (options && options?.requestConfirmation && options?.runOnPageLoad) {
|
||||
queryConfirmationList.push({ queryId: id, queryName: name });
|
||||
}
|
||||
});
|
||||
|
||||
if (queryConfirmationList.length !== 0) {
|
||||
useEditorStore.getState().actions.updateQueryConfirmationList(queryConfirmationList);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute query state to be added in the current state
|
||||
const { actions, selectedQuery } = useQueryPanelStore.getState();
|
||||
if (selectFirstQuery) {
|
||||
|
|
@ -47,6 +60,9 @@ export const useDataQueriesStore = create(
|
|||
const query = data.data_queries.find((query) => query.id === selectedQuery?.id);
|
||||
actions.setSelectedQuery(query?.id);
|
||||
}
|
||||
|
||||
// Runs query on loading application
|
||||
if (runQueriesOnAppLoad) runQueries(data.data_queries, ref);
|
||||
},
|
||||
setDataQueries: (dataQueries) => set({ dataQueries }),
|
||||
deleteDataQueries: (queryId) => {
|
||||
|
|
@ -232,7 +248,7 @@ export const useDataQueriesStore = create(
|
|||
newName = queryToClone.name + '_copy' + count.toString();
|
||||
}
|
||||
queryToClone.name = newName;
|
||||
delete queryToClone.id;
|
||||
|
||||
useAppDataStore.getState().actions.setIsSaving(true);
|
||||
dataqueryService
|
||||
.create(
|
||||
|
|
@ -250,6 +266,26 @@ export const useDataQueriesStore = create(
|
|||
dataQueries: [{ ...data, data_source_id: queryToClone.data_source_id }, ...state.dataQueries],
|
||||
}));
|
||||
actions.setSelectedQuery(data.id, { ...data, data_source_id: queryToClone.data_source_id });
|
||||
|
||||
const dataQueryEvents = useAppDataStore
|
||||
.getState()
|
||||
.events?.filter((event) => event.target === 'data_query' && event.sourceId === queryToClone.id);
|
||||
|
||||
if (dataQueryEvents?.length === 0) return;
|
||||
|
||||
return Promise.all(
|
||||
dataQueryEvents.map((event) => {
|
||||
const newEvent = {
|
||||
event: {
|
||||
...event?.event,
|
||||
},
|
||||
eventType: event?.target,
|
||||
attachedTo: data.id,
|
||||
index: event?.index,
|
||||
};
|
||||
useAppDataStore.getState().actions?.createAppVersionEventHandlers(newEvent);
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('error', error);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { create, zustandDevTools } from './utils';
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
const STORE_NAME = 'Editor';
|
||||
|
||||
const ACTIONS = {
|
||||
|
|
@ -19,53 +19,79 @@ const initialState = {
|
|||
selectionInProgress: false,
|
||||
selectedComponents: [],
|
||||
isEditorActive: false,
|
||||
currentSidebarTab: 2,
|
||||
selectedComponent: null,
|
||||
scrollOptions: {
|
||||
container: null,
|
||||
throttleTime: 0,
|
||||
threshold: 0,
|
||||
},
|
||||
canUndo: false,
|
||||
canRedo: false,
|
||||
currentVersion: {},
|
||||
noOfVersionsSupported: 100,
|
||||
appDefinition: {},
|
||||
// isSaving: false,
|
||||
isUpdatingEditorStateInProcess: false,
|
||||
saveError: false,
|
||||
isLoading: true,
|
||||
defaultComponentStateComputed: false,
|
||||
showLeftSidebar: true,
|
||||
queryConfirmationList: [],
|
||||
currentPageId: null,
|
||||
currentSessionId: uuid(),
|
||||
};
|
||||
|
||||
export const useEditorStore = create(
|
||||
zustandDevTools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
setShowComments: (showComments) =>
|
||||
set({ showComments }, false, {
|
||||
type: ACTIONS.SET_HOVERED_COMPONENT,
|
||||
showComments,
|
||||
}),
|
||||
toggleComments: () =>
|
||||
set({ showComments: !get().showComments }, false, {
|
||||
type: ACTIONS.TOGGLE_COMMENTS,
|
||||
}),
|
||||
toggleCurrentLayout: (currentLayout) =>
|
||||
set({ currentLayout }, false, {
|
||||
type: ACTIONS.TOGGLE_CURRENT_LAYOUT,
|
||||
currentLayout,
|
||||
}),
|
||||
setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })),
|
||||
setHoveredComponent: (hoveredComponent) =>
|
||||
set({ hoveredComponent }, false, {
|
||||
type: ACTIONS.SET_HOVERED_COMPONENT,
|
||||
hoveredComponent,
|
||||
}),
|
||||
setSelectionInProgress: (isSelectionInProgress) => {
|
||||
set(
|
||||
{
|
||||
isSelectionInProgress,
|
||||
},
|
||||
false,
|
||||
{ type: ACTIONS.SET_SELECTION_IN_PROGRESS }
|
||||
);
|
||||
},
|
||||
setSelectedComponents: (selectedComponents) => {
|
||||
set(
|
||||
{
|
||||
selectedComponents,
|
||||
},
|
||||
false,
|
||||
{ type: ACTIONS.SET_SELECTED_COMPONENTS }
|
||||
);
|
||||
},
|
||||
// Dev tools for this store are disabled comments since its freezing chrome tab
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
actions: {
|
||||
setShowComments: (showComments) =>
|
||||
set({ showComments }, false, {
|
||||
type: ACTIONS.SET_HOVERED_COMPONENT,
|
||||
showComments,
|
||||
}),
|
||||
toggleComments: () =>
|
||||
set({ showComments: !get().showComments }, false, {
|
||||
type: ACTIONS.TOGGLE_COMMENTS,
|
||||
}),
|
||||
toggleCurrentLayout: (currentLayout) =>
|
||||
set({ currentLayout }, false, {
|
||||
type: ACTIONS.TOGGLE_CURRENT_LAYOUT,
|
||||
currentLayout,
|
||||
}),
|
||||
setIsEditorActive: (isEditorActive) => set(() => ({ isEditorActive })),
|
||||
updateEditorState: (state) => set((prev) => ({ ...prev, ...state })),
|
||||
updateQueryConfirmationList: (queryConfirmationList) => set({ queryConfirmationList }),
|
||||
setHoveredComponent: (hoveredComponent) =>
|
||||
set({ hoveredComponent }, false, {
|
||||
type: ACTIONS.SET_HOVERED_COMPONENT,
|
||||
hoveredComponent,
|
||||
}),
|
||||
setSelectionInProgress: (isSelectionInProgress) => {
|
||||
set(
|
||||
{
|
||||
isSelectionInProgress,
|
||||
},
|
||||
false,
|
||||
{ type: ACTIONS.SET_SELECTION_IN_PROGRESS }
|
||||
);
|
||||
},
|
||||
}),
|
||||
{ name: STORE_NAME }
|
||||
)
|
||||
setSelectedComponents: (selectedComponents, isMulti = false) => {
|
||||
const newSelectedComponents = isMulti
|
||||
? [...get().selectedComponents, ...selectedComponents]
|
||||
: selectedComponents;
|
||||
|
||||
set({
|
||||
selectedComponents: newSelectedComponents,
|
||||
});
|
||||
},
|
||||
setCurrentPageId: (currentPageId) => set({ currentPageId }),
|
||||
},
|
||||
}),
|
||||
{ name: STORE_NAME }
|
||||
);
|
||||
|
||||
export const useEditorActions = () => useEditorStore((state) => state.actions);
|
||||
export const useEditorState = () => useEditorStore((state) => state);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { create as _create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { diff } from 'deep-object-diff';
|
||||
import { componentTypes } from '@/Editor/WidgetManager/components';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const zustandDevTools = (fn, options = {}) =>
|
||||
devtools(fn, { ...options, enabled: process.env.NODE_ENV === 'production' ? false : true });
|
||||
|
|
@ -21,3 +25,304 @@ export const resetAllStores = () => {
|
|||
resetter();
|
||||
}
|
||||
};
|
||||
|
||||
const defaultComponent = {
|
||||
name: '',
|
||||
properties: {},
|
||||
styles: {},
|
||||
validation: {},
|
||||
type: '',
|
||||
others: {
|
||||
showOnDesktop: { value: '{{true}}' },
|
||||
showOnMobile: { value: '{{false}}' },
|
||||
},
|
||||
};
|
||||
|
||||
const updateType = Object.freeze({
|
||||
pageDefinitionChanged: 'pages',
|
||||
containerChanges: 'components/layout',
|
||||
componentAdded: 'components',
|
||||
componentDefinitionChanged: 'components',
|
||||
componentDeleted: 'components',
|
||||
});
|
||||
|
||||
export const computeAppDiff = (appDiff, currentPageId, opts, currentLayout) => {
|
||||
const { updateDiff, type, operation, error } = updateFor(appDiff, currentPageId, opts, currentLayout);
|
||||
|
||||
return { updateDiff, type, operation, error };
|
||||
};
|
||||
|
||||
// for table column diffs, we need to compute the diff for each column separately and send the the entire column data
|
||||
function generatePath(obj, targetKey, currentPath = '') {
|
||||
for (const key in obj) {
|
||||
const newPath = currentPath ? currentPath + '.' + key : key;
|
||||
|
||||
if (key === targetKey) {
|
||||
return newPath;
|
||||
}
|
||||
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
const result = generatePath(obj[key], targetKey, newPath);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getValueFromJson(json, path) {
|
||||
if (!path || typeof path !== 'string') return null;
|
||||
|
||||
let value = json;
|
||||
path.split('.').forEach((key) => {
|
||||
value = value[key];
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
function updateValueInJson(json, path, value) {
|
||||
let obj = json;
|
||||
const keys = path?.split('.');
|
||||
|
||||
if (!keys) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastKey = keys.pop();
|
||||
keys.forEach((key) => {
|
||||
obj = obj[key];
|
||||
});
|
||||
obj[lastKey] = value;
|
||||
return json;
|
||||
}
|
||||
|
||||
export function isParamFromTableColumn(appDiff, definition) {
|
||||
const path = generatePath(appDiff, 'columns') || generatePath(appDiff, 'actions');
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const value2 = getValueFromJson(definition, path);
|
||||
|
||||
return value2 !== undefined;
|
||||
}
|
||||
|
||||
export const computeComponentPropertyDiff = (appDiff, definition, opts) => {
|
||||
if (!opts?.isParamFromTableColumn) {
|
||||
return appDiff;
|
||||
}
|
||||
const columnsPath = generatePath(appDiff, 'columns');
|
||||
const actionsPath = generatePath(appDiff, 'actions');
|
||||
const deletionHistoryPath = generatePath(appDiff, 'columnDeletionHistory');
|
||||
|
||||
let _diff = _.cloneDeep(appDiff);
|
||||
|
||||
if (columnsPath) {
|
||||
const columnsValue = getValueFromJson(definition, columnsPath);
|
||||
_diff = updateValueInJson(_diff, columnsPath, columnsValue);
|
||||
}
|
||||
|
||||
if (actionsPath) {
|
||||
const actionsValue = getValueFromJson(definition, actionsPath);
|
||||
_diff = updateValueInJson(_diff, actionsPath, actionsValue);
|
||||
}
|
||||
|
||||
if (deletionHistoryPath) {
|
||||
const deletionHistoryValue = getValueFromJson(definition, deletionHistoryPath);
|
||||
_diff = updateValueInJson(_diff, deletionHistoryPath, deletionHistoryValue);
|
||||
}
|
||||
|
||||
return _diff;
|
||||
};
|
||||
|
||||
const updateFor = (appDiff, currentPageId, opts, currentLayout) => {
|
||||
const updateTypeMappings = [
|
||||
{
|
||||
updateTypes: ['componentAdded', 'componentDefinitionChanged', 'componentDeleted', 'containerChanges'],
|
||||
processingFunction: computeComponentDiff,
|
||||
},
|
||||
{
|
||||
updateTypes: ['pageDefinitionChanged', 'pageSortingChanged', 'deletePageRequest', 'addNewPage'],
|
||||
processingFunction: computePageUpdate,
|
||||
},
|
||||
{
|
||||
updateTypes: ['homePageChanged'],
|
||||
processingFunction: () => ({
|
||||
updateDiff: appDiff,
|
||||
type: null,
|
||||
operation: 'update',
|
||||
}),
|
||||
},
|
||||
{
|
||||
updateTypes: ['globalSettings', 'generalAppDefinitionChanged'],
|
||||
processingFunction: () => ({
|
||||
updateDiff: appDiff,
|
||||
type: 'global_settings',
|
||||
operation: 'update',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const options = _.keys(opts);
|
||||
|
||||
for (const { updateTypes, processingFunction } of updateTypeMappings) {
|
||||
const optionsTypes = _.intersection(options, updateTypes);
|
||||
|
||||
if (optionsTypes.length > 0) {
|
||||
try {
|
||||
return processingFunction(appDiff, currentPageId, optionsTypes, currentLayout);
|
||||
} catch (error) {
|
||||
return { error, updateDiff: {}, type: null, operation: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const computePageUpdate = (appDiff, currentPageId, opts) => {
|
||||
let type;
|
||||
let updateDiff;
|
||||
let operation = 'update';
|
||||
|
||||
if (opts.includes('deletePageRequest')) {
|
||||
const deletePageId = _.keys(appDiff?.pages).map((pageId) => {
|
||||
if (appDiff?.pages[pageId]?.pageId === undefined) {
|
||||
return pageId;
|
||||
}
|
||||
})[0];
|
||||
|
||||
updateDiff = {
|
||||
pageId: deletePageId,
|
||||
};
|
||||
|
||||
type = updateType.pageDefinitionChanged;
|
||||
operation = 'delete';
|
||||
} else if (opts.includes('pageSortingChanged')) {
|
||||
updateDiff = appDiff?.pages;
|
||||
|
||||
type = updateType.pageDefinitionChanged;
|
||||
} else if (opts.includes('pageDefinitionChanged')) {
|
||||
updateDiff = appDiff?.pages[currentPageId];
|
||||
|
||||
type = updateType.pageDefinitionChanged;
|
||||
|
||||
if (opts.includes('addNewPage')) {
|
||||
operation = 'create';
|
||||
}
|
||||
}
|
||||
|
||||
return { updateDiff, type, operation };
|
||||
};
|
||||
|
||||
const computeComponentDiff = (appDiff, currentPageId, opts, currentLayout) => {
|
||||
let type;
|
||||
let updateDiff;
|
||||
let operation = 'update';
|
||||
|
||||
if (opts.includes('componentDeleted')) {
|
||||
const currentPageComponents = appDiff?.pages[currentPageId]?.components;
|
||||
|
||||
updateDiff = _.keys(currentPageComponents);
|
||||
|
||||
type = updateType.componentDeleted;
|
||||
|
||||
operation = 'delete';
|
||||
} else if (opts.includes('componentAdded')) {
|
||||
const currentPageComponents = appDiff?.pages[currentPageId]?.components;
|
||||
|
||||
updateDiff = _.toPairs(currentPageComponents ?? []).reduce((result, [id, component]) => {
|
||||
if (_.keys(component).length === 1 && component.withDefaultChildren !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const componentMeta = componentTypes.find((comp) => comp.component === component.component.component);
|
||||
|
||||
if (!componentMeta) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const metaDiff = diff(componentMeta, component.component);
|
||||
|
||||
result[id] = _.defaultsDeep(metaDiff, defaultComponent);
|
||||
|
||||
if (metaDiff.definition && !_.isEmpty(metaDiff.definition)) {
|
||||
const metaAttributes = _.keys(metaDiff.definition);
|
||||
|
||||
metaAttributes.forEach((attribute) => {
|
||||
const doesActionsExist =
|
||||
metaDiff.definition[attribute]?.actions && !_.isEmpty(metaDiff.definition[attribute]?.actions?.value);
|
||||
const doesColumnsExist =
|
||||
metaDiff.definition[attribute]?.columns && !_.isEmpty(metaDiff.definition[attribute]?.columns?.value);
|
||||
|
||||
if (doesActionsExist || doesColumnsExist) {
|
||||
const actions = _.toArray(metaDiff.definition[attribute]?.actions?.value) || [];
|
||||
const columns = _.toArray(metaDiff.definition[attribute]?.columns?.value) || [];
|
||||
|
||||
metaDiff.definition = {
|
||||
...metaDiff.definition,
|
||||
[attribute]: {
|
||||
...metaDiff.definition[attribute],
|
||||
actions: {
|
||||
value: actions,
|
||||
},
|
||||
columns: {
|
||||
value: columns,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
result[id][attribute] = metaDiff.definition[attribute];
|
||||
});
|
||||
}
|
||||
|
||||
const currentDisplayPreference = currentLayout;
|
||||
|
||||
if (currentDisplayPreference === 'mobile') {
|
||||
result[id].others.showOnMobile = { value: '{{true}}' };
|
||||
result[id].others.showOnDesktop = { value: '{{false}}' };
|
||||
}
|
||||
|
||||
if (result[id]?.definition) {
|
||||
delete result[id].definition;
|
||||
}
|
||||
|
||||
result[id].type = componentMeta.component;
|
||||
result[id].parent = component.component.parent ?? null;
|
||||
result[id].layouts = appDiff.pages[currentPageId].components[id].layouts;
|
||||
|
||||
operation = 'create';
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
type = updateType.componentDefinitionChanged;
|
||||
} else if (
|
||||
(opts.includes('containerChanges') || opts.includes('componentDefinitionChanged')) &&
|
||||
!opts.includes('componentAdded')
|
||||
) {
|
||||
const currentPageComponents = appDiff?.pages[currentPageId]?.components;
|
||||
|
||||
updateDiff = toRemoveExposedvariablesFromComponentDiff(currentPageComponents);
|
||||
|
||||
type = opts.includes('containerChanges') ? updateType.containerChanges : updateType.componentDefinitionChanged;
|
||||
}
|
||||
|
||||
return { updateDiff, type, operation };
|
||||
};
|
||||
|
||||
function toRemoveExposedvariablesFromComponentDiff(object) {
|
||||
const copy = _.cloneDeep(object);
|
||||
const componentIds = _.keys(copy);
|
||||
|
||||
componentIds.forEach((componentId) => {
|
||||
const { component } = copy[componentId];
|
||||
|
||||
if (component?.exposedVariables) {
|
||||
delete component.exposedVariables;
|
||||
}
|
||||
});
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import useRouter from '@/_hooks/use-router';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.23.0
|
||||
2.24.0
|
||||
|
|
|
|||
|
|
@ -6,39 +6,28 @@ export class ListviewDefaultMode1688977149516 implements MigrationInterface {
|
|||
const entityManager = queryRunner.manager;
|
||||
const appVersions = await entityManager.find(AppVersion);
|
||||
for (const version of appVersions) {
|
||||
const definition = version['definition'];
|
||||
const definition = JSON.parse(JSON.stringify(version?.definition));
|
||||
|
||||
if (definition) {
|
||||
const pages = definition['pages'];
|
||||
if (pages) {
|
||||
if (Object.keys(pages).length > 0) {
|
||||
for (const pageId of Object.keys(pages)) {
|
||||
const components = definition['pages'][pageId]['components'];
|
||||
if (components) {
|
||||
if (Object.keys(components).length > 0) {
|
||||
for (const componentId of Object.keys(components)) {
|
||||
const component = components[componentId];
|
||||
|
||||
if (component?.component?.component === 'Listview') {
|
||||
component['component']['definition']['properties']['mode'] = {
|
||||
value: 'list',
|
||||
};
|
||||
|
||||
components[componentId] = {
|
||||
...component,
|
||||
component: {
|
||||
...component.component,
|
||||
definition: {
|
||||
...component.component.definition,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (
|
||||
component?.component?.component === 'Listview' &&
|
||||
component.component?.definition?.properties?.mode
|
||||
) {
|
||||
component.component.definition.properties.mode['value'] = 'list';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
definition['components'] = components;
|
||||
version.definition = definition;
|
||||
}
|
||||
}
|
||||
version.definition = definition;
|
||||
await entityManager.update(AppVersion, { id: version.id }, { definition });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,25 +4,23 @@ import { AppVersion } from '../src/entities/app_version.entity';
|
|||
export class CellSizeRegularCondensed1692973078520 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const entityManager = queryRunner.manager;
|
||||
const queryBuilder = queryRunner.connection.createQueryBuilder();
|
||||
|
||||
const appVersionRepository = entityManager.getRepository(AppVersion);
|
||||
|
||||
const appVersions = await appVersionRepository.find();
|
||||
|
||||
for (const version of appVersions) {
|
||||
const definition = version?.['definition'];
|
||||
const definition = JSON.parse(JSON.stringify(version?.definition));
|
||||
if (definition) {
|
||||
const pages = definition?.['pages'];
|
||||
if (pages) {
|
||||
if (Object.keys(pages).length > 0) {
|
||||
for (const pageId of Object.keys(pages)) {
|
||||
const components = pages?.[pageId]?.['components'];
|
||||
if (components) {
|
||||
const components = pages[pageId]?.['components'];
|
||||
|
||||
if (Object.keys(components).length > 0) {
|
||||
for (const componentId of Object.keys(components)) {
|
||||
const component = components[componentId];
|
||||
if (component?.component?.component === 'Table') {
|
||||
component.component.definition.styles.cellSize = {
|
||||
value: 'regular',
|
||||
};
|
||||
if (component?.component?.component === 'Table' && component.component?.definition?.styles?.cellSize) {
|
||||
component.component.styles.cellSize = {
|
||||
...component.component.styles.cellSize,
|
||||
options: [
|
||||
|
|
@ -30,27 +28,19 @@ export class CellSizeRegularCondensed1692973078520 implements MigrationInterface
|
|||
{ name: 'Regular', value: 'regular' },
|
||||
],
|
||||
};
|
||||
components[componentId] = {
|
||||
...component,
|
||||
component: {
|
||||
...component.component,
|
||||
definition: {
|
||||
...component.component.definition,
|
||||
},
|
||||
},
|
||||
|
||||
component.component.definition.styles.cellSize = {
|
||||
value: 'regular',
|
||||
};
|
||||
}
|
||||
}
|
||||
pages[pageId]['components'] = components;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
definition['pages'] = pages;
|
||||
|
||||
version.definition = definition;
|
||||
|
||||
await queryBuilder.update(AppVersion).set({ definition }).where('id = :id', { id: version.id }).execute();
|
||||
await entityManager.update(AppVersion, { id: version.id }, { definition });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ import { AppVersion } from '../src/entities/app_version.entity';
|
|||
export class TableRowCellStyle1692974311591 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const entityManager = queryRunner.manager;
|
||||
const queryBuilder = queryRunner.connection.createQueryBuilder();
|
||||
|
||||
const appVersionRepository = entityManager.getRepository(AppVersion);
|
||||
|
||||
const appVersions = await appVersionRepository.find();
|
||||
|
||||
for (const version of appVersions) {
|
||||
const definition = version['definition'];
|
||||
const definition = JSON.parse(JSON.stringify(version?.definition));
|
||||
|
||||
if (definition) {
|
||||
const pages = definition['pages'];
|
||||
if (pages) {
|
||||
if (Object.keys(pages).length > 0) {
|
||||
for (const pageId of Object.keys(pages)) {
|
||||
const components = pages[pageId]['components'];
|
||||
if (components) {
|
||||
if (Object.keys(components).length > 0) {
|
||||
for (const componentId of Object.keys(components)) {
|
||||
const component = components[componentId];
|
||||
if (component?.component?.component === 'Table') {
|
||||
|
|
@ -32,27 +32,15 @@ export class TableRowCellStyle1692974311591 implements MigrationInterface {
|
|||
{ name: 'Striped', value: 'table-striped' },
|
||||
],
|
||||
};
|
||||
components[componentId] = {
|
||||
...component,
|
||||
component: {
|
||||
...component.component,
|
||||
definition: {
|
||||
...component.component.definition,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
pages[pageId]['components'] = components;
|
||||
}
|
||||
}
|
||||
|
||||
definition['pages'] = pages;
|
||||
|
||||
version.definition = definition;
|
||||
|
||||
await queryBuilder.update(AppVersion).set({ definition }).where('id = :id', { id: version.id }).execute();
|
||||
await entityManager.update(AppVersion, { id: version.id }, { definition });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,361 @@
|
|||
import { In, MigrationInterface, QueryRunner, EntityManager } from 'typeorm';
|
||||
import { AppVersion } from '../src/entities/app_version.entity';
|
||||
import { Component } from 'src/entities/component.entity';
|
||||
import { Page } from 'src/entities/page.entity';
|
||||
import { Layout } from 'src/entities/layout.entity';
|
||||
import { EventHandler, Target } from 'src/entities/event_handler.entity';
|
||||
import { DataQuery } from 'src/entities/data_query.entity';
|
||||
import { MigrationProgress, processDataInBatches } from 'src/helpers/utils.helper';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
interface AppResourceMappings {
|
||||
pagesMapping: Record<string, string>;
|
||||
componentsMapping: Record<string, string>;
|
||||
}
|
||||
|
||||
export class MigrateAppsDefinitionSchemaTransition1697473340856 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const entityManager = queryRunner.manager;
|
||||
const appVersionRepository = entityManager.getRepository(AppVersion);
|
||||
const appVersions = await appVersionRepository.find();
|
||||
const totalVersions = appVersions.length;
|
||||
|
||||
const migrationProgress = new MigrationProgress(
|
||||
'MigrateAppsDefinitionSchemaTransition1697473340856',
|
||||
totalVersions
|
||||
);
|
||||
|
||||
const batchSize = 100; // Number of apps to migrate at a time
|
||||
|
||||
await processDataInBatches(
|
||||
entityManager,
|
||||
async (entityManager: EntityManager, skip: number, take: number) => {
|
||||
return entityManager.find(AppVersion, {
|
||||
where: { id: In(appVersions.map((appVersion) => appVersion.id)) },
|
||||
take,
|
||||
skip,
|
||||
});
|
||||
},
|
||||
async (entityManager: EntityManager, versions: AppVersion[]) => {
|
||||
await this.processVersions(entityManager, versions, migrationProgress);
|
||||
},
|
||||
batchSize
|
||||
);
|
||||
}
|
||||
|
||||
private async processVersions(
|
||||
entityManager: EntityManager,
|
||||
versions: AppVersion[],
|
||||
migrationProgress: MigrationProgress
|
||||
) {
|
||||
for (const version of versions) {
|
||||
const definition = version['definition'];
|
||||
|
||||
if (!definition) return;
|
||||
|
||||
const dataQueriesRepository = entityManager.getRepository(DataQuery);
|
||||
const dataQueries = await dataQueriesRepository.find({
|
||||
where: { appVersionId: version.id },
|
||||
});
|
||||
|
||||
let updateHomepageId = null;
|
||||
|
||||
const appResourceMappings: AppResourceMappings = {
|
||||
pagesMapping: {},
|
||||
componentsMapping: {},
|
||||
};
|
||||
if (definition?.pages) {
|
||||
for (const pageId of Object.keys(definition?.pages)) {
|
||||
const page = definition.pages[pageId];
|
||||
const pagePositionInTheList = Object.keys(definition?.pages).indexOf(pageId);
|
||||
const pageEvents = page.events || [];
|
||||
const pageComponents = page.components;
|
||||
|
||||
const isHomepage = (definition['homePageId'] as any) === pageId;
|
||||
|
||||
const componentEvents = [];
|
||||
const componentLayouts = [];
|
||||
const transformedComponents = this.transformComponentData(
|
||||
pageComponents,
|
||||
componentEvents,
|
||||
appResourceMappings.componentsMapping
|
||||
);
|
||||
|
||||
const newPage = entityManager.create(Page, {
|
||||
name: page.name,
|
||||
handle: page.handle,
|
||||
appVersionId: version.id,
|
||||
disabled: page.disabled || false,
|
||||
hidden: page.hidden || false,
|
||||
index: pagePositionInTheList,
|
||||
});
|
||||
|
||||
const pageCreated = await entityManager.save(newPage);
|
||||
|
||||
appResourceMappings.pagesMapping[pageId] = pageCreated.id;
|
||||
|
||||
transformedComponents.forEach((component) => {
|
||||
component.page = pageCreated;
|
||||
});
|
||||
|
||||
const savedComponents = await entityManager.save(Component, transformedComponents);
|
||||
|
||||
for (const componentId in pageComponents) {
|
||||
const componentLayout = pageComponents[componentId]['layouts'];
|
||||
|
||||
if (componentLayout && appResourceMappings.componentsMapping[componentId]) {
|
||||
for (const type in componentLayout) {
|
||||
const layout = componentLayout[type];
|
||||
const newLayout = new Layout();
|
||||
newLayout.type = type;
|
||||
newLayout.top = layout.top;
|
||||
newLayout.left = layout.left;
|
||||
newLayout.width = layout.width;
|
||||
newLayout.height = layout.height;
|
||||
newLayout.componentId = appResourceMappings.componentsMapping[componentId];
|
||||
|
||||
componentLayouts.push(newLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await entityManager.save(Layout, componentLayouts);
|
||||
|
||||
if (pageEvents.length > 0) {
|
||||
pageEvents.forEach(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: pageCreated.id,
|
||||
target: Target.page,
|
||||
event: event,
|
||||
index: pageEvents.index || index,
|
||||
appVersionId: version.id,
|
||||
};
|
||||
|
||||
await entityManager.save(EventHandler, newEvent);
|
||||
});
|
||||
}
|
||||
|
||||
componentEvents.forEach((eventObj) => {
|
||||
if (eventObj.event?.length === 0) return;
|
||||
|
||||
eventObj.event.forEach(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: appResourceMappings.componentsMapping[eventObj.componentId],
|
||||
target: Target.component,
|
||||
event: event,
|
||||
index: eventObj.index || index,
|
||||
appVersionId: version.id,
|
||||
};
|
||||
|
||||
await entityManager.save(EventHandler, newEvent);
|
||||
});
|
||||
});
|
||||
|
||||
savedComponents.forEach(async (component) => {
|
||||
if (component.type === 'Table') {
|
||||
const tableActions = component.properties?.actions?.value || [];
|
||||
const tableColumns = component.properties?.columns?.value || [];
|
||||
const tableActionAndColumnEvents = [];
|
||||
|
||||
tableActions.forEach((action) => {
|
||||
const actionEvents = action.events || [];
|
||||
|
||||
actionEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableAction,
|
||||
event: { ...event, ref: action.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: version.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tableColumns.forEach((column) => {
|
||||
if (column?.columnType !== 'toggle') return;
|
||||
const columnEvents = column.events || [];
|
||||
|
||||
columnEvents.forEach((event, index) => {
|
||||
tableActionAndColumnEvents.push({
|
||||
name: event.eventId,
|
||||
sourceId: component.id,
|
||||
target: Target.tableColumn,
|
||||
event: { ...event, ref: column.name },
|
||||
index: event.index ?? index,
|
||||
appVersionId: version.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await entityManager.save(EventHandler, tableActionAndColumnEvents);
|
||||
}
|
||||
});
|
||||
|
||||
if (isHomepage) {
|
||||
updateHomepageId = pageCreated.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dataQuery of dataQueries) {
|
||||
const queryEvents = dataQuery?.options?.events || [];
|
||||
|
||||
if (queryEvents.length > 0) {
|
||||
queryEvents.forEach(async (event, index) => {
|
||||
const newEvent = {
|
||||
name: event.eventId,
|
||||
sourceId: dataQuery.id,
|
||||
target: Target.dataQuery,
|
||||
event: event,
|
||||
index: queryEvents.index || index,
|
||||
appVersionId: version.id,
|
||||
};
|
||||
|
||||
await entityManager.save(EventHandler, newEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await entityManager.update(
|
||||
AppVersion,
|
||||
{ id: version.id },
|
||||
{
|
||||
homePageId: updateHomepageId,
|
||||
showViewerNavigation: definition?.showViewerNavigation || true,
|
||||
globalSettings: definition.globalSettings,
|
||||
}
|
||||
);
|
||||
|
||||
await this.updateEventActionsForNewVersionWithNewMappingIds(
|
||||
entityManager,
|
||||
version.id,
|
||||
appResourceMappings.componentsMapping,
|
||||
appResourceMappings.pagesMapping
|
||||
);
|
||||
|
||||
migrationProgress.show();
|
||||
}
|
||||
}
|
||||
|
||||
async updateEventActionsForNewVersionWithNewMappingIds(
|
||||
manager: EntityManager,
|
||||
versionId: string,
|
||||
oldComponentToNewComponentMapping: Record<string, unknown>,
|
||||
oldPageToNewPageMapping: Record<string, unknown>
|
||||
) {
|
||||
const allEvents = await manager.find(EventHandler, {
|
||||
where: { appVersionId: versionId },
|
||||
});
|
||||
|
||||
for (const event of allEvents) {
|
||||
const eventDefinition = event.event;
|
||||
|
||||
if (eventDefinition?.actionId === 'switch-page') {
|
||||
eventDefinition.pageId = oldPageToNewPageMapping[eventDefinition.pageId];
|
||||
}
|
||||
|
||||
if (eventDefinition?.actionId === 'control-component') {
|
||||
eventDefinition.componentId = oldComponentToNewComponentMapping[eventDefinition.componentId];
|
||||
}
|
||||
|
||||
if (eventDefinition?.actionId == 'show-modal' || eventDefinition?.actionId === 'close-modal') {
|
||||
eventDefinition.modal = oldComponentToNewComponentMapping[eventDefinition.modal];
|
||||
}
|
||||
|
||||
event.event = eventDefinition;
|
||||
|
||||
await manager.save(event);
|
||||
}
|
||||
}
|
||||
|
||||
private transformComponentData(
|
||||
data: object,
|
||||
componentEvents: any[],
|
||||
componentsMapping: Record<string, string>
|
||||
): Component[] {
|
||||
const transformedComponents: Component[] = [];
|
||||
|
||||
const allComponents = Object.keys(data).map((key) => {
|
||||
return {
|
||||
id: key,
|
||||
...data[key],
|
||||
};
|
||||
});
|
||||
|
||||
for (const componentId in data) {
|
||||
const component = data[componentId];
|
||||
const componentData = component['component'];
|
||||
|
||||
let skipComponent = false;
|
||||
const transformedComponent: Component = new Component();
|
||||
|
||||
let parentId = component.parent ? component.parent : null;
|
||||
|
||||
const isParentTabOrCalendar = this.isChildOfTabsOrCalendar(component, allComponents, parentId);
|
||||
|
||||
if (isParentTabOrCalendar) {
|
||||
const childTabId = component.parent.split('-')[component.parent.split('-').length - 1];
|
||||
const _parentId = component?.parent?.split('-').slice(0, -1).join('-');
|
||||
const mappedParentId = componentsMapping[_parentId];
|
||||
|
||||
parentId = `${mappedParentId}-${childTabId}`;
|
||||
} else {
|
||||
if (component.parent && !componentsMapping[parentId]) {
|
||||
skipComponent = true;
|
||||
}
|
||||
parentId = componentsMapping[parentId];
|
||||
}
|
||||
|
||||
if (!skipComponent) {
|
||||
transformedComponent.id = uuid();
|
||||
transformedComponent.name = componentData.name;
|
||||
transformedComponent.type = componentData.component;
|
||||
transformedComponent.properties = componentData.definition.properties || {};
|
||||
transformedComponent.styles = componentData.definition.styles || {};
|
||||
transformedComponent.validation = componentData.definition.validation || {};
|
||||
transformedComponent.general = componentData.definition.general || {};
|
||||
transformedComponent.generalStyles = componentData.definition.generalStyles || {};
|
||||
transformedComponent.displayPreferences = componentData.definition.others || {};
|
||||
transformedComponent.parent = component.parent ? parentId : null;
|
||||
|
||||
transformedComponents.push(transformedComponent);
|
||||
|
||||
componentEvents.push({
|
||||
componentId: componentId,
|
||||
event: componentData.definition.events,
|
||||
});
|
||||
componentsMapping[componentId] = transformedComponent.id;
|
||||
}
|
||||
}
|
||||
|
||||
return transformedComponents;
|
||||
}
|
||||
|
||||
isChildOfTabsOrCalendar = (component, allComponents = [], componentParentId = undefined) => {
|
||||
if (componentParentId) {
|
||||
const parentId = component?.parent?.split('-').slice(0, -1).join('-');
|
||||
|
||||
const parentComponent = allComponents.find((comp) => comp.id === parentId);
|
||||
|
||||
if (parentComponent) {
|
||||
return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DELETE FROM page');
|
||||
await queryRunner.query('DELETE FROM component');
|
||||
await queryRunner.query('DELETE FROM layout');
|
||||
await queryRunner.query('DELETE FROM event_handler');
|
||||
|
||||
await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS homePageId');
|
||||
await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS globalSettings');
|
||||
await queryRunner.query('ALTER TABLE app_version DROP COLUMN IF EXISTS showViewerNavigation');
|
||||
}
|
||||
}
|
||||
41
server/migrations/1691004576222-UpdateAppVersionEntity.ts
Normal file
41
server/migrations/1691004576222-UpdateAppVersionEntity.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
|
||||
|
||||
export class UpdateAppVersionEntity1691006886222 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Add the new columns to the app_versions table
|
||||
await queryRunner.addColumn(
|
||||
'app_versions',
|
||||
new TableColumn({
|
||||
name: 'global_settings',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.addColumn(
|
||||
'app_versions',
|
||||
new TableColumn({
|
||||
name: 'show_viewer_navigation',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
isNullable: false,
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.addColumn(
|
||||
'app_versions',
|
||||
new TableColumn({
|
||||
name: 'home_page_id',
|
||||
type: 'uuid',
|
||||
isNullable: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Remove the new columns from the app_versions table (if necessary)
|
||||
await queryRunner.dropColumn('app_versions', 'global_settings');
|
||||
await queryRunner.dropColumn('app_versions', 'show_viewer_navigation');
|
||||
await queryRunner.dropColumn('app_versions', 'home_page_id');
|
||||
}
|
||||
}
|
||||
76
server/migrations/1691004576333-CreatePageTable.ts
Normal file
76
server/migrations/1691004576333-CreatePageTable.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
|
||||
|
||||
export class CreatePageTable1691004576333 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'pages',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isPrimary: true,
|
||||
default: 'gen_random_uuid()',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'index',
|
||||
type: 'int',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'page_handle',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'disabled',
|
||||
type: 'boolean',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
type: 'boolean',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'app_version_id',
|
||||
type: 'uuid',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Add foreign key to relate Page with AppVersion
|
||||
await queryRunner.createForeignKey(
|
||||
'pages',
|
||||
new TableForeignKey({
|
||||
columnNames: ['app_version_id'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'app_versions',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('pages');
|
||||
}
|
||||
}
|
||||
78
server/migrations/1691004706564-CreateEventHandlerTable.ts
Normal file
78
server/migrations/1691004706564-CreateEventHandlerTable.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
|
||||
|
||||
export class CreateEventHandlerTable1691004706564 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'event_handlers',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isPrimary: true,
|
||||
default: 'gen_random_uuid()',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'index',
|
||||
type: 'int',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'event',
|
||||
type: 'jsonb',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'app_version_id',
|
||||
type: 'uuid',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'source_id',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'target',
|
||||
type: 'enum',
|
||||
enum: ['page', 'component', 'data_query', 'table_column', 'table_action'],
|
||||
default: "'page'",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Add foreign key to relate EventHandler with AppVersion
|
||||
await queryRunner.createForeignKey(
|
||||
'event_handlers',
|
||||
new TableForeignKey({
|
||||
columnNames: ['app_version_id'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'app_versions',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('event_handlers');
|
||||
}
|
||||
}
|
||||
108
server/migrations/1691006952074-CreateComponentTable.ts
Normal file
108
server/migrations/1691006952074-CreateComponentTable.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey, TableIndex } from 'typeorm';
|
||||
|
||||
export class CreateComponentTable1691006952074 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'components',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isPrimary: true,
|
||||
default: 'gen_random_uuid()',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'varchar',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'page_id',
|
||||
type: 'uuid',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'parent',
|
||||
type: 'varchar',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'properties',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'general_properties',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'styles',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'general_styles',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'display_preferences',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'validation',
|
||||
type: 'json',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
type: 'timestamp',
|
||||
isNullable: true,
|
||||
default: 'now()',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'components',
|
||||
new TableForeignKey({
|
||||
columnNames: ['page_id'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'pages',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
);
|
||||
|
||||
await queryRunner.createIndex('components', new TableIndex({ columnNames: ['name'] }));
|
||||
await queryRunner.createIndex('components', new TableIndex({ columnNames: ['type'] }));
|
||||
await queryRunner.createIndex('components', new TableIndex({ columnNames: ['page_id'] }));
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop indexes
|
||||
await queryRunner.dropIndex('components', 'IDX_COMPONENT_NAME');
|
||||
await queryRunner.dropIndex('components', 'IDX_COMPONENT_TYPE');
|
||||
await queryRunner.dropIndex('components', 'IDX_COMPONENT_PAGE');
|
||||
|
||||
// Drop foreign key
|
||||
await queryRunner.dropForeignKey('components', 'FK_COMPONENT_PAGE');
|
||||
|
||||
// Drop table
|
||||
await queryRunner.dropTable('components');
|
||||
}
|
||||
}
|
||||
66
server/migrations/1691007037021-CreateLayoutTable.ts
Normal file
66
server/migrations/1691007037021-CreateLayoutTable.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
|
||||
|
||||
export class CreateLayoutTable1691007037021 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'layouts',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
isPrimary: true,
|
||||
default: 'gen_random_uuid()',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'enum',
|
||||
enumName: 'layput_type',
|
||||
enum: ['desktop', 'mobile'],
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'top',
|
||||
type: 'double precision',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'left',
|
||||
type: 'double precision',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'double precision',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'double precision',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'component_id',
|
||||
type: 'uuid',
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
// Add foreign key to relate Layout with Component
|
||||
await queryRunner.createForeignKey(
|
||||
'layouts',
|
||||
new TableForeignKey({
|
||||
columnNames: ['component_id'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'components',
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('layouts');
|
||||
}
|
||||
}
|
||||
12
server/package-lock.json
generated
12
server/package-lock.json
generated
|
|
@ -55,7 +55,7 @@
|
|||
"request-ip": "^3.3.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"semver": "^7.3.5",
|
||||
"semver": "^7.5.4",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "^3.10.1",
|
||||
"typeorm": "^0.2.38",
|
||||
|
|
@ -11858,8 +11858,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.5",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
|
|
@ -22603,8 +22604,9 @@
|
|||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
"request-ip": "^3.3.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"semver": "^7.3.5",
|
||||
"semver": "^7.5.4",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "^3.10.1",
|
||||
"typeorm": "^0.2.38",
|
||||
|
|
|
|||
502
server/src/controllers/apps.controller.v2.ts
Normal file
502
server/src/controllers/apps.controller.v2.ts
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
import {
|
||||
Controller,
|
||||
ForbiddenException,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Query,
|
||||
UseGuards,
|
||||
Body,
|
||||
BadRequestException,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
|
||||
import { AppAuthGuard } from 'src/modules/auth/app-auth.guard';
|
||||
import { AppsService } from '../services/apps.service';
|
||||
import { camelizeKeys, decamelizeKeys } from 'humps';
|
||||
import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.factory';
|
||||
|
||||
import { App } from 'src/entities/app.entity';
|
||||
import { User } from 'src/decorators/user.decorator';
|
||||
|
||||
import { CreatePageDto, DeletePageDto } from '@dto/pages.dto';
|
||||
import { CreateComponentDto, DeleteComponentDto, UpdateComponentDto, LayoutUpdateDto } from '@dto/component.dto';
|
||||
|
||||
import { ValidAppInterceptor } from 'src/interceptors/valid.app.interceptor';
|
||||
import { AppDecorator } from 'src/decorators/app.decorator';
|
||||
|
||||
import { ComponentsService } from '@services/components.service';
|
||||
import { PageService } from '@services/page.service';
|
||||
import { EventsService } from '@services/events_handler.service';
|
||||
import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
|
||||
import { CreateEventHandlerDto, UpdateEventHandlerDto } from '@dto/event-handler.dto';
|
||||
|
||||
@Controller({
|
||||
path: 'apps',
|
||||
version: '2',
|
||||
})
|
||||
export class AppsControllerV2 {
|
||||
constructor(
|
||||
private appsService: AppsService,
|
||||
private componentsService: ComponentsService,
|
||||
private pageService: PageService,
|
||||
private eventsService: EventsService,
|
||||
private eventService: EventsService,
|
||||
private appsAbilityFactory: AppsAbilityFactory
|
||||
) {}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Get(':id')
|
||||
async show(@User() user, @AppDecorator() app: App, @Query('access_type') accessType: string) {
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
|
||||
if (!ability.can('viewApp', app)) {
|
||||
throw new ForbiddenException(
|
||||
JSON.stringify({
|
||||
organizationId: app.organizationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (accessType === 'edit' && !ability.can('editApp', app)) {
|
||||
throw new ForbiddenException(
|
||||
JSON.stringify({
|
||||
organizationId: app.organizationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const response = decamelizeKeys(app);
|
||||
|
||||
const seralizedQueries = [];
|
||||
const dataQueriesForVersion = app.editingVersion
|
||||
? await this.appsService.findDataQueriesForVersion(app.editingVersion.id)
|
||||
: [];
|
||||
|
||||
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(app.editingVersion.id) : [];
|
||||
const eventsForVersion = app.editingVersion
|
||||
? await this.eventsService.findEventsForVersion(app.editingVersion.id)
|
||||
: [];
|
||||
|
||||
// serialize queries
|
||||
for (const query of dataQueriesForVersion) {
|
||||
const decamelizedQuery = decamelizeKeys(query);
|
||||
decamelizedQuery['options'] = query.options;
|
||||
seralizedQueries.push(decamelizedQuery);
|
||||
}
|
||||
|
||||
response['data_queries'] = seralizedQueries;
|
||||
response['definition'] = app.editingVersion?.definition;
|
||||
response['pages'] = pagesForVersion;
|
||||
response['events'] = eventsForVersion;
|
||||
|
||||
//! if editing version exists, camelize the definition
|
||||
if (app.editingVersion && app.editingVersion.definition) {
|
||||
response['editing_version'] = {
|
||||
...response['editing_version'],
|
||||
definition: camelizeKeys(app.editingVersion.definition),
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@UseGuards(AppAuthGuard) // This guard will allow access for unauthenticated user if the app is public
|
||||
@Get('slugs/:slug')
|
||||
async appFromSlug(@User() user, @AppDecorator() app: App) {
|
||||
if (user) {
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
|
||||
|
||||
if (!ability.can('viewApp', app)) {
|
||||
throw new ForbiddenException(
|
||||
JSON.stringify({
|
||||
organizationId: app.organizationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const versionToLoad = app.currentVersionId
|
||||
? await this.appsService.findVersion(app.currentVersionId)
|
||||
: await this.appsService.findVersion(app.editingVersion?.id);
|
||||
|
||||
const pagesForVersion = app.editingVersion ? await this.pageService.findPagesForVersion(versionToLoad.id) : [];
|
||||
const eventsForVersion = app.editingVersion ? await this.eventsService.findEventsForVersion(versionToLoad.id) : [];
|
||||
|
||||
// serialize
|
||||
return {
|
||||
current_version_id: app['currentVersionId'],
|
||||
data_queries: versionToLoad?.dataQueries,
|
||||
definition: versionToLoad?.definition,
|
||||
is_public: app.isPublic,
|
||||
is_maintenance_on: app.isMaintenanceOn,
|
||||
name: app.name,
|
||||
slug: app.slug,
|
||||
events: eventsForVersion,
|
||||
pages: pagesForVersion,
|
||||
homePageId: versionToLoad.homePageId,
|
||||
globalSettings: versionToLoad.globalSettings,
|
||||
showViewerNavigation: versionToLoad.showViewerNavigation,
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Get(':id/versions/:versionId')
|
||||
async version(@User() user, @Param('id') id, @Param('versionId') versionId) {
|
||||
const appVersion = await this.appsService.findVersion(versionId);
|
||||
const app = appVersion.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
|
||||
|
||||
if (!ability.can('fetchVersions', app)) {
|
||||
throw new ForbiddenException(
|
||||
JSON.stringify({
|
||||
organizationId: app.organizationId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const pagesForVersion = await this.pageService.findPagesForVersion(versionId);
|
||||
const eventsForVersion = await this.eventsService.findEventsForVersion(versionId);
|
||||
|
||||
const appCurrentEditingVersion = JSON.parse(JSON.stringify(appVersion));
|
||||
|
||||
delete appCurrentEditingVersion['app'];
|
||||
|
||||
const appData = {
|
||||
...app,
|
||||
};
|
||||
|
||||
delete appData['editingVersion'];
|
||||
|
||||
return {
|
||||
...appData,
|
||||
editing_version: camelizeKeys(appCurrentEditingVersion),
|
||||
pages: pagesForVersion,
|
||||
events: eventsForVersion,
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId')
|
||||
async updateVersion(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() appVersionUpdateDto: AppVersionUpdateDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
return await this.appsService.updateAppVersion(version, appVersionUpdateDto);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId/global_settings')
|
||||
async updateGlobalSettings(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() appVersionUpdateDto: AppVersionUpdateDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
return await this.appsService.updateAppVersion(version, appVersionUpdateDto);
|
||||
}
|
||||
|
||||
//components api
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Post(':id/versions/:versionId/components')
|
||||
async createComponent(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() createComponentDto: CreateComponentDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.componentsService.create(createComponentDto.diff, createComponentDto.pageId, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId/components')
|
||||
async updateComponent(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() updateComponentDto: UpdateComponentDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.componentsService.update(updateComponentDto.diff, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Delete(':id/versions/:versionId/components')
|
||||
async deleteComponents(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() deleteComponentDto: DeleteComponentDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.componentsService.delete(deleteComponentDto.diff, versionId, deleteComponentDto.is_component_cut);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId/components/layout')
|
||||
async updateComponentLayout(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() updateComponentLayout: LayoutUpdateDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.componentsService.componentLayoutChange(updateComponentLayout.diff, versionId);
|
||||
}
|
||||
|
||||
// pages api
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Post(':id/versions/:versionId/pages')
|
||||
async createPages(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() createPageDto: CreatePageDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.pageService.createPage(createPageDto, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Post(':id/versions/:versionId/pages/:pageId/clone')
|
||||
async clonePage(@User() user, @Param('id') id, @Param('versionId') versionId, @Param('pageId') pageId) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
return await this.pageService.clonePage(pageId, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId/pages')
|
||||
async updatePages(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() updatePageDto) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.pageService.updatePage(updatePageDto, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Delete(':id/versions/:versionId/pages')
|
||||
async deletePage(@User() user, @Param('id') id, @Param('versionId') versionId, @Body() deletePageDto: DeletePageDto) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
await this.pageService.deletePage(deletePageDto.pageId, versionId);
|
||||
}
|
||||
|
||||
// events api
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Get(':id/versions/:versionId/events')
|
||||
async getEvents(@User() user, @Param('id') id, @Param('versionId') versionId, @Query('sourceId') sourceId) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('viewApp', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
if (!sourceId) {
|
||||
return this.eventService.findEventsForVersion(versionId);
|
||||
}
|
||||
|
||||
return this.eventService.findAllEventsWithSourceId(sourceId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Post(':id/versions/:versionId/events')
|
||||
async createEvent(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() createEventHandlerDto: CreateEventHandlerDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
return this.eventService.createEvent(createEventHandlerDto, versionId);
|
||||
}
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Put(':id/versions/:versionId/events')
|
||||
async updateEvents(
|
||||
@User() user,
|
||||
@Param('id') id,
|
||||
@Param('versionId') versionId,
|
||||
@Body() updateEventHandlerDto: UpdateEventHandlerDto
|
||||
) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
const { events, updateType } = updateEventHandlerDto;
|
||||
|
||||
return await this.eventService.updateEvent(events, updateType, versionId);
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@UseInterceptors(ValidAppInterceptor)
|
||||
@Delete(':id/versions/:versionId/events/:eventId')
|
||||
async deleteEvents(@User() user, @Param('id') id, @Param('versionId') versionId, @Param('eventId') eventId) {
|
||||
const version = await this.appsService.findVersion(versionId);
|
||||
const app = version.app;
|
||||
|
||||
if (app.id !== id) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
const ability = await this.appsAbilityFactory.appsActions(user, id);
|
||||
|
||||
if (!ability.can('updateVersions', app)) {
|
||||
throw new ForbiddenException('You do not have permissions to perform this action');
|
||||
}
|
||||
|
||||
return await this.eventService.deleteEvent(eventId, versionId);
|
||||
}
|
||||
}
|
||||
26
server/src/dto/app-version-update.dto.ts
Normal file
26
server/src/dto/app-version-update.dto.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { sanitizeInput } from '../helpers/utils.helper';
|
||||
|
||||
export class AppVersionUpdateDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
const newValue = sanitizeInput(value);
|
||||
return newValue.trim();
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@MaxLength(50, { message: 'Maximum length has been reached.' })
|
||||
name: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
showViewerNavigation: boolean;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
homePageId: string;
|
||||
|
||||
@IsOptional()
|
||||
globalSettings: any;
|
||||
}
|
||||
159
server/src/dto/component.dto.ts
Normal file
159
server/src/dto/component.dto.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidationArguments,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
Validate,
|
||||
} from 'class-validator';
|
||||
|
||||
export class ComponentLayoutDto {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
top?: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
left?: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
width?: number;
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export class LayoutData {
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
desktop?: ComponentLayoutDto;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
mobile?: ComponentLayoutDto;
|
||||
}
|
||||
|
||||
@ValidatorConstraint({ name: 'LayoutDataValidator', async: false })
|
||||
class LayoutDataValidator implements ValidatorConstraintInterface {
|
||||
validate(value: any) {
|
||||
if (value) {
|
||||
for (const key in value) {
|
||||
if (!value[key] || typeof value[key] !== 'object' || !value[key].layouts) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `Each key in "diff" must have the structure { layouts: LayoutData }`;
|
||||
}
|
||||
}
|
||||
|
||||
export class LayoutUpdateDto {
|
||||
@IsBoolean()
|
||||
is_user_switched_version: boolean;
|
||||
|
||||
@IsUUID()
|
||||
pageId: string;
|
||||
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
@Validate(LayoutDataValidator, { each: true })
|
||||
diff: Record<string, { layouts: LayoutData }>;
|
||||
}
|
||||
|
||||
class ComponentDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsObject()
|
||||
properties: Record<string, any>;
|
||||
|
||||
@IsObject()
|
||||
styles: Record<string, any>;
|
||||
|
||||
@IsObject()
|
||||
validation: Record<string, any>;
|
||||
|
||||
@IsString()
|
||||
type: string;
|
||||
|
||||
@IsObject()
|
||||
others: Record<string, any>;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => ComponentLayoutDto)
|
||||
layouts: ComponentLayoutDto;
|
||||
|
||||
@IsOptional()
|
||||
parent: string;
|
||||
}
|
||||
|
||||
@ValidatorConstraint({ name: 'CreateComponentDtoValidator', async: false })
|
||||
class CreateComponentDtoValidator implements ValidatorConstraintInterface {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
// Check if the diff structure is valid
|
||||
for (const key in value.diff) {
|
||||
if (!value.diff[key] || typeof value.diff[key] !== 'object') {
|
||||
return false;
|
||||
}
|
||||
// You can add additional checks for the component structure here
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
return `Invalid structure in diff for CreateComponentDto`;
|
||||
}
|
||||
}
|
||||
|
||||
export class CreateComponentDto {
|
||||
@IsBoolean()
|
||||
is_user_switched_version: boolean;
|
||||
|
||||
@IsUUID()
|
||||
pageId: string;
|
||||
|
||||
@IsObject()
|
||||
@Validate(CreateComponentDtoValidator)
|
||||
diff: Record<string, ComponentDto>;
|
||||
}
|
||||
|
||||
export class UpdateComponentDto {
|
||||
@IsBoolean()
|
||||
is_user_switched_version: boolean;
|
||||
|
||||
@IsUUID()
|
||||
pageId: string;
|
||||
|
||||
@IsObject()
|
||||
@Validate(CreateComponentDtoValidator)
|
||||
diff: Record<string, ComponentDto>;
|
||||
}
|
||||
|
||||
export class DeleteComponentDto {
|
||||
@IsBoolean()
|
||||
is_user_switched_version: boolean;
|
||||
|
||||
@IsUUID()
|
||||
pageId: string;
|
||||
|
||||
@IsArray()
|
||||
diff: string[];
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_component_cut: boolean;
|
||||
}
|
||||
44
server/src/dto/event-handler.dto.ts
Normal file
44
server/src/dto/event-handler.dto.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { IsArray, IsIn, IsNumber, IsObject, IsString, IsUUID, ValidateNested } from 'class-validator';
|
||||
import { Target } from 'src/entities/event_handler.entity';
|
||||
|
||||
export class CreateEventHandlerDto {
|
||||
@IsObject()
|
||||
event: any;
|
||||
|
||||
@IsString()
|
||||
eventType: Target;
|
||||
|
||||
@IsString()
|
||||
attachedTo: string;
|
||||
|
||||
@IsNumber()
|
||||
index: number;
|
||||
}
|
||||
|
||||
class UpdateEventDiff {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNumber()
|
||||
index: number;
|
||||
|
||||
@IsObject()
|
||||
@ValidateNested()
|
||||
event: any;
|
||||
}
|
||||
|
||||
export class UpdateEvent {
|
||||
@IsUUID()
|
||||
event_id: string;
|
||||
|
||||
@IsObject()
|
||||
diff: UpdateEventDiff;
|
||||
}
|
||||
|
||||
export class UpdateEventHandlerDto {
|
||||
@IsArray()
|
||||
events: UpdateEvent[];
|
||||
|
||||
@IsIn(['update', 'reorder'])
|
||||
updateType: 'update' | 'reorder';
|
||||
}
|
||||
38
server/src/dto/pages.dto.ts
Normal file
38
server/src/dto/pages.dto.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, MaxLength } from 'class-validator';
|
||||
|
||||
export class CreatePageDto {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(32)
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(50)
|
||||
handle: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
index: number;
|
||||
|
||||
@IsOptional()
|
||||
disabled: boolean;
|
||||
|
||||
@IsOptional()
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export class DeletePageDto {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
export class UpdatePageDto {
|
||||
pageId: string;
|
||||
diff: Partial<CreatePageDto>;
|
||||
}
|
||||
|
|
@ -20,4 +20,11 @@ export class VersionEditDto {
|
|||
@IsOptional()
|
||||
@IsBoolean()
|
||||
is_user_switched_version: boolean;
|
||||
|
||||
@IsOptional()
|
||||
diff: any;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pageId: string;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue