mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
Merge pull request #12997 from ToolJet/appbuilder/sprint-13
Appbuilder/sprint 13
This commit is contained in:
commit
917b80fe65
238 changed files with 6054 additions and 1968 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
3.14.0
|
||||
3.15.0
|
||||
|
|
|
|||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -24,5 +24,7 @@
|
|||
"url": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json"
|
||||
}
|
||||
],
|
||||
"CodeGPT.apiKey": "CodeGPT Plus Beta"
|
||||
"CodeGPT.apiKey": "CodeGPT Plus Beta",
|
||||
"workbench.colorTheme": "Cursor Dark",
|
||||
"workbench.iconTheme": "vs-seti"
|
||||
}
|
||||
|
|
@ -76,8 +76,9 @@ module.exports = defineConfig({
|
|||
experimentalRunAllSpecs: true,
|
||||
baseUrl: "http://localhost:8082",
|
||||
specPattern: [
|
||||
"cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/**/*.cy.js",
|
||||
// "cypress/e2e/happyPath/appbuilder/ceTestcases/**/*.cy.js"
|
||||
// "cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/**/*.cy.js",
|
||||
// "cypress/e2e/happyPath/appbuilder/ceTestcases/**/*.cy.js",
|
||||
"cypress/e2e/happyPath/appbuilder/commonTestcases/newSuits/globalSetingsHappyPath.cy.js"
|
||||
],
|
||||
numTestsKeptInMemory: 1,
|
||||
redirectionLimit: 7,
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ export const commonWidgetSelector = {
|
|||
buttonCloseEditorSideBar: "[data-cy='inspector-close-icon']",
|
||||
buttonStylesEditorSideBar: "#inspector-tab-styles",
|
||||
WidgetNameInputField: "[data-cy=edit-widget-name]",
|
||||
constantInspectorIcon: '[data-cy="inspector-node-constants"] > .node-key',
|
||||
constantInspectorIcon: '[data-cy="inspector-constants-expand-button"]',
|
||||
inspectorIcon: '[data-cy="left-sidebar-inspect-button"]',
|
||||
tooltipInputField: "[data-cy='tooltip-input-field']",
|
||||
tooltipLabel: "[id=button-tooltip]",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe("Editor title", () => {
|
|||
afterEach(() => {
|
||||
cy.apiDeleteApp();
|
||||
});
|
||||
it("should verify titles", () => {
|
||||
it.skip("should verify titles", () => {
|
||||
cy.url().should("include", "/tooljets-workspace");
|
||||
cy.title().should("eq", "Dashboard | ToolJet");
|
||||
// cy.title().should("eq", "ToolJet");
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Button Component Tests', () => {
|
||||
|
|
@ -75,22 +75,21 @@ describe('Button Component Tests', () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateApp(`${fake.companyName}-Button-App`);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget("Button", 50, 50);
|
||||
cy.dragAndDropWidget("Button", 500, 500);
|
||||
cy.get('[data-cy="query-manager-toggle-button"]').click();
|
||||
});
|
||||
|
||||
it('should verify all the exposed values on inspector', () => {
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("button1", exposedValues, verifyValue);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
openAndVerifyNode("button1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyNodeData);
|
||||
//id is pending
|
||||
|
||||
});
|
||||
|
||||
it.skip('should verify all the events from the button', () => {
|
||||
it('should verify all the events from the button', () => {
|
||||
const events = [
|
||||
{ event: "On hover", message: "On hover Event" },
|
||||
{ event: "On Click", message: "On Click Event" },
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Checkbox Component Tests', () => {
|
||||
|
|
@ -83,7 +83,7 @@ describe('Checkbox Component Tests', () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateApp(`${fake.companyName}-Checkbox-App`);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget("Checkbox", 50, 50);
|
||||
cy.dragAndDropWidget("Checkbox", 500, 500);
|
||||
cy.get('[data-cy="query-manager-toggle-button"]').click();
|
||||
});
|
||||
|
||||
|
|
@ -92,8 +92,8 @@ describe('Checkbox Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("checkbox1", exposedValues, verifyValue);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
openAndVerifyNode("checkbox1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyNodeData);
|
||||
//id is pending
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Dropdown Component Tests', () => {
|
||||
|
|
@ -101,7 +101,7 @@ describe('Dropdown Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("dropdown1", exposedValues, verifyValue);
|
||||
openAndVerifyNode("dropdown1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
//id is pending
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { deleteDownloadsFolder } from "Support/utils/common";
|
|||
import {
|
||||
resizeQueryPanel
|
||||
} from "Support/utils/dataSource";
|
||||
import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector";
|
||||
import { openNode, verifyNodeData } from "Support/utils/inspector";
|
||||
import {
|
||||
addNewPage
|
||||
} from "Support/utils/multipage";
|
||||
|
|
@ -49,11 +49,11 @@ describe("Global Actions", () => {
|
|||
verifyNodeData("variables", "Object", "1 entry ");
|
||||
openNode("variables", 0);
|
||||
|
||||
verifyValue("var", "String", `"test"`);
|
||||
verifyNodeData("var", "String", `"test"`);
|
||||
|
||||
openNode("page");
|
||||
openNode("variables", 1);
|
||||
verifyValue("pageVar", "String", `"pageTest"`);
|
||||
verifyNodeData("pageVar", "String", `"pageTest"`);
|
||||
|
||||
addInputOnQueryField(
|
||||
"runjs",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Multiselect Component Tests', () => {
|
||||
|
|
@ -104,7 +104,7 @@ describe('Multiselect Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("multiselect1", exposedValues, verifyValue);
|
||||
openAndVerifyNode("multiselect1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
//id is pending
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Number Input Component Tests', () => {
|
||||
|
|
@ -86,7 +86,7 @@ describe('Number Input Component Tests', () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateApp(`${fake.companyName}-Numberinput-App`);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget("Number Input", 50, 50);
|
||||
cy.dragAndDropWidget("Number Input", 500, 500);
|
||||
cy.get('[data-cy="query-manager-toggle-button"]').click();
|
||||
});
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ describe('Number Input Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("numberinput1", exposedValues, verifyValue);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
openAndVerifyNode("numberinput1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyNodeData);
|
||||
//id is pending
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Password Input Component Tests', () => {
|
||||
|
|
@ -95,7 +95,7 @@ describe('Password Input Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("passwordinput1", exposedValues, verifyValue);
|
||||
openAndVerifyNode("passwordinput1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
//id is pending
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('Text Input Component Tests', () => {
|
||||
|
|
@ -27,14 +27,14 @@ describe('Text Input Component Tests', () => {
|
|||
"key": "setBlur",
|
||||
"type": "Function"
|
||||
},
|
||||
{
|
||||
"key": "disable",
|
||||
"type": "Function"
|
||||
},
|
||||
{
|
||||
"key": "visibility",
|
||||
"type": "Function"
|
||||
},
|
||||
// {
|
||||
// "key": "disable",
|
||||
// "type": "Function"
|
||||
// },
|
||||
// {
|
||||
// "key": "visibility",
|
||||
// "type": "Function"
|
||||
// },
|
||||
{
|
||||
"key": "setVisibility",
|
||||
"type": "Function"
|
||||
|
|
@ -94,17 +94,17 @@ describe('Text Input Component Tests', () => {
|
|||
cy.apiLogin();
|
||||
cy.apiCreateApp(`${fake.companyName}-Textinput-App`);
|
||||
cy.openApp();
|
||||
cy.dragAndDropWidget("Text Input", 50, 50);
|
||||
cy.dragAndDropWidget("Text Input", 500, 500);
|
||||
cy.get('[data-cy="query-manager-toggle-button"]').click();
|
||||
});
|
||||
|
||||
it.skip('should verify all the exposed values on inspector', () => {
|
||||
it('should verify all the exposed values on inspector', () => {
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("textinput1", exposedValues, verifyValue);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
openAndVerifyNode("textinput1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyNodeData);
|
||||
//id is pending
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
verifyCSA
|
||||
} from "Support/utils/editor/textInput";
|
||||
import { addMultiEventsWithAlert } from "Support/utils/events";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyValue } from "Support/utils/inspector";
|
||||
import { openAndVerifyNode, openNode, verifyfunctions, verifyNodes, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
|
||||
describe('ToggleSwitch Component Tests', () => {
|
||||
|
|
@ -88,7 +88,7 @@ describe('ToggleSwitch Component Tests', () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openNode("components");
|
||||
openAndVerifyNode("toggleswitch1", exposedValues, verifyValue);
|
||||
openAndVerifyNode("toggleswitch1", exposedValues, verifyNodeData);
|
||||
verifyNodes(functions, verifyfunctions);
|
||||
//id is pending
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
selectColourFromColourPicker,
|
||||
verifyWidgetColorCss,
|
||||
} from "Support/utils/commonWidget";
|
||||
import { verifyNodeData, openNode, verifyValue } from "Support/utils/inspector";
|
||||
// import { verifyNodeData, openNode, verifyNodeData } from "Support/utils/inspector";
|
||||
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
|
||||
import {
|
||||
commonText,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { commonWidgetSelector } from "Selectors/common";
|
|||
import { multipageSelector } from "Selectors/multipage";
|
||||
import { addSupportCSAData, selectEvent } from "Support/utils/events";
|
||||
import { createNewVersion } from "Support/utils/exportImport";
|
||||
import { deleteComponentFromInspector, openNode, verifyNodeData, verifyValue, verifyNodes, openAndVerifyNode } from "Support/utils/inspector";
|
||||
import { deleteComponentFromInspector, openNode, verifyNodeData, verifyNodes, openAndVerifyNode } from "Support/utils/inspector";
|
||||
import { addNewPage } from "Support/utils/multipage";
|
||||
import { navigateToCreateNewVersionModal } from "Support/utils/version";
|
||||
import testData from "Fixtures/inspectorItems.json";
|
||||
|
|
@ -20,15 +20,15 @@ describe("Editor- Inspector", () => {
|
|||
cy.viewport(1800, 1800);
|
||||
});
|
||||
|
||||
it("should verify the values of inspector", () => {
|
||||
it.skip("should verify the values of inspector", () => {
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
|
||||
openAndVerifyNode("globals", testData.globalsNodes, verifyNodeData);
|
||||
openAndVerifyNode("currentUser", testData.currentUserNodes, verifyValue);
|
||||
openAndVerifyNode("theme", testData.themeNodes, verifyValue);
|
||||
openAndVerifyNode("mode", testData.modeNodes, verifyValue);
|
||||
openAndVerifyNode("urlparams", testData.urlparamsNode, verifyValue);
|
||||
openAndVerifyNode("currentUser", testData.currentUserNodes, verifyNodeData);
|
||||
openAndVerifyNode("theme", testData.themeNodes, verifyNodeData);
|
||||
openAndVerifyNode("mode", testData.modeNodes, verifyNodeData);
|
||||
openAndVerifyNode("urlparams", testData.urlparamsNode, verifyNodeData);
|
||||
|
||||
if (Cypress.env("environment") !== "Community") {
|
||||
const ssoUserInfoNode = '[data-cy="inspector-node-ssouserinfo"]';
|
||||
|
|
@ -39,7 +39,7 @@ describe("Editor- Inspector", () => {
|
|||
|
||||
openNode("theme");
|
||||
openNode("environment");
|
||||
verifyValue("name", "String", `"development"`);
|
||||
verifyNodeData("name", "String", `"development"`);
|
||||
cy.get(`${inspectorNodeId} > .node-key`).should("have.text", "id");
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ describe("Editor- Inspector", () => {
|
|||
cy.get(commonWidgetSelector.draggableWidget("button3")).click();
|
||||
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
openAndVerifyNode("variables", testData.variablesNodes, verifyValue);
|
||||
openAndVerifyNode("variables", testData.variablesNodes, verifyNodeData);
|
||||
|
||||
cy.forceClickOnCanvas()
|
||||
cy.wait(500)
|
||||
|
|
@ -101,9 +101,9 @@ describe("Editor- Inspector", () => {
|
|||
|
||||
// openNode("page");
|
||||
|
||||
openAndVerifyNode("page", testData.testPageNodes, verifyValue);
|
||||
openAndVerifyNode("page", testData.testPageNodes, verifyNodeData);
|
||||
openNode("variables", 1);
|
||||
verifyValue("pageVar", "String", `"pageVar"`);
|
||||
verifyNodeData("pageVar", "String", `"pageVar"`);
|
||||
|
||||
openAndVerifyNode("components", testData.componentsNodes, verifyNodeData);
|
||||
|
||||
|
|
@ -111,10 +111,10 @@ describe("Editor- Inspector", () => {
|
|||
cy.get(commonWidgetSelector.draggableWidget("button1")).click();
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
|
||||
openAndVerifyNode("page", testData.pageNodes, verifyValue);
|
||||
openAndVerifyNode("page", testData.pageNodes, verifyNodeData);
|
||||
openNode("globals");
|
||||
openNode("urlparams");
|
||||
verifyValue("key", "String", `"value"`);
|
||||
verifyNodeData("key", "String", `"value"`);
|
||||
|
||||
cy.get(`[data-cy="inspector-node-key"] > .mx-1`)
|
||||
.realHover()
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ describe("Chaining of queries", () => {
|
|||
cy.wait(1000)
|
||||
cy.get('[data-cy="query-tab-setup"]').click();
|
||||
|
||||
cy.wait(1500);
|
||||
openEditorSidebar(buttonText.defaultWidgetName);
|
||||
selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0);
|
||||
cy.wait(500);
|
||||
|
|
@ -122,7 +123,7 @@ describe("Chaining of queries", () => {
|
|||
// cy.verifyToastMessage(commonSelectors.toastMessage, "Hello World");
|
||||
});
|
||||
|
||||
it.skip("should verify query duplication", () => {
|
||||
it("should verify query duplication", () => {
|
||||
|
||||
const data = {};
|
||||
let dsName = fake.companyName;
|
||||
|
|
@ -146,6 +147,7 @@ describe("Chaining of queries", () => {
|
|||
chainQuery("runjs", "runpy");
|
||||
addSuccessNotification("runjs");
|
||||
|
||||
cy.wait(1500);
|
||||
openEditorSidebar(buttonText.defaultWidgetName);
|
||||
selectEvent("On Click", "Run Query", 0, `[data-cy="add-event-handler"]`, 0);
|
||||
cy.wait(500);
|
||||
|
|
@ -170,6 +172,8 @@ describe("Chaining of queries", () => {
|
|||
"have.text",
|
||||
"runjs_copy "
|
||||
);
|
||||
|
||||
cy.get('[data-cy="query-tab-settings"]').click();
|
||||
cy.get('[data-cy="notification-on-success-toggle-switch"]').should(
|
||||
"have.value",
|
||||
"on"
|
||||
|
|
@ -184,7 +188,7 @@ describe("Chaining of queries", () => {
|
|||
});
|
||||
cy.get(
|
||||
`[data-cy="action-selection"] > .select-search > .react-select__control > .react-select__value-container > `
|
||||
).should("have.text", "Run Query");
|
||||
).should("have.text", "Run query");
|
||||
cy.get('[data-cy="query-selection-field"]').should("have.text", "runpy");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
resizeQueryPanel,
|
||||
verifypreview
|
||||
} from "Support/utils/dataSource";
|
||||
import { openNode, verifyValue } from "Support/utils/inspector";
|
||||
import { openNode, verifyNodeData } from "Support/utils/inspector";
|
||||
|
||||
describe("RunJS", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -42,15 +42,15 @@ describe("RunJS", () => {
|
|||
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField("runjs", "return true");
|
||||
query("preview");
|
||||
query("run");
|
||||
verifypreview("raw", "true");
|
||||
query("run");
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get(".tooltip-inner").invoke("hide");
|
||||
openNode("queries");
|
||||
openNode("runjs1");
|
||||
verifyValue("data", "Boolean", "true");
|
||||
verifyValue("rawData", "Boolean", "true");
|
||||
verifyNodeData("data", "Boolean", "true");
|
||||
verifyNodeData("rawData", "Boolean", "true");
|
||||
cy.apiDeleteApp();
|
||||
});
|
||||
|
||||
|
|
@ -60,11 +60,11 @@ describe("RunJS", () => {
|
|||
|
||||
selectQueryFromLandingPage("runjs", "JavaScript");
|
||||
addInputOnQueryField("runjs", "return [page.handle,page.name]");
|
||||
query("preview");
|
||||
query("run");
|
||||
verifypreview("raw", `["home","Home"]`);
|
||||
|
||||
addInputOnQueryField("runjs", "return globals.theme");
|
||||
query("preview");
|
||||
query("run");
|
||||
verifypreview("raw", `{"name":"light"}`);
|
||||
|
||||
// addInputOnQueryField("runjs", "return globals.currentUser");
|
||||
|
|
@ -24,7 +24,7 @@ import {
|
|||
resizeQueryPanel,
|
||||
verifypreview
|
||||
} from "Support/utils/dataSource";
|
||||
import { openNode, verifyNodeData, verifyValue } from "Support/utils/inspector";
|
||||
import { openNode, verifyNodeData } from "Support/utils/inspector";
|
||||
import {
|
||||
addNewPage
|
||||
} from "Support/utils/multipage";
|
||||
|
|
@ -54,8 +54,8 @@ describe("runpy", () => {
|
|||
cy.get(".tooltip-inner").invoke("hide");
|
||||
openNode("queries");
|
||||
openNode("runpy1");
|
||||
verifyValue("data", "Boolean", "true");
|
||||
verifyValue("rawData", "Boolean", "true");
|
||||
verifyNodeData("data", "Boolean", "true");
|
||||
verifyNodeData("rawData", "Boolean", "true");
|
||||
cy.apiDeleteApp();
|
||||
});
|
||||
|
||||
|
|
@ -76,11 +76,11 @@ actions.setPageVariable('pageVar', 'pageTest')`
|
|||
verifyNodeData("variables", "Object", "1 entry ");
|
||||
openNode("variables", 0);
|
||||
|
||||
verifyValue("var", "String", `"test"`);
|
||||
verifyNodeData("var", "String", `"test"`);
|
||||
|
||||
openNode("page");
|
||||
openNode("variables", 1);
|
||||
verifyValue("pageVar", "String", `"pageTest"`);
|
||||
verifyNodeData("pageVar", "String", `"pageTest"`);
|
||||
|
||||
addInputOnQueryField(
|
||||
"runpy",
|
||||
|
|
@ -84,7 +84,7 @@ describe("App Slug", () => {
|
|||
|
||||
// Release and verify URLs
|
||||
releaseApp();
|
||||
verifyURLs(workspaceId, data.slug, false);
|
||||
verifyURLs(workspaceId, data.slug, true);
|
||||
|
||||
// Verify duplicate slug validation
|
||||
cy.visit("/my-workspace");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from "Support/utils/common";
|
||||
import { inviteUserBasedOnRole } from "Support/utils/manageGroups";
|
||||
import { resolveHost } from "Support/utils/apps";
|
||||
import { addSuccessNotification } from "Support/utils/queries";
|
||||
|
||||
const data = {};
|
||||
data.firstName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", "");
|
||||
|
|
@ -32,7 +33,7 @@ describe("Datasource Manager", () => {
|
|||
beforeEach(() => {
|
||||
cy.apiLogin();
|
||||
cy.visit(`${workspaceSlug}`);
|
||||
cy.viewport(1200, 1300);
|
||||
cy.viewport(1800, 1800);
|
||||
cy.skipWalkthrough();
|
||||
});
|
||||
|
||||
|
|
@ -199,7 +200,7 @@ describe("Datasource Manager", () => {
|
|||
|
||||
cy.apiCreateApp(data.appName);
|
||||
cy.openApp();
|
||||
pinInspector();
|
||||
// pinInspector();
|
||||
|
||||
addQuery(
|
||||
"table_preview",
|
||||
|
|
@ -212,9 +213,11 @@ describe("Datasource Manager", () => {
|
|||
"table_preview "
|
||||
);
|
||||
|
||||
cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
cy.get('[data-cy="query-tab-settings"]').click();
|
||||
addSuccessNotification("table_preview");
|
||||
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
|
||||
verifyValueOnInspector("table_preview", "10 items ");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "table_preview");
|
||||
|
||||
cy.get('[data-cy="show-ds-popover-button"]').click();
|
||||
|
||||
cy.get(".p-2 > .tj-base-btn")
|
||||
|
|
@ -247,9 +250,13 @@ describe("Datasource Manager", () => {
|
|||
cy.get("#react-select-4-listbox")
|
||||
.contains(`cypress-${data.dsName2}-postgresql`)
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy="query-tab-settings"]').click();
|
||||
addSuccessNotification("postgresql");
|
||||
cy.waitForAutoSave();
|
||||
cy.get(dataSourceSelector.queryCreateAndRunButton).click();
|
||||
verifyValueOnInspector("table_preview", "4 items ");
|
||||
cy.verifyToastMessage(commonSelectors.toastMessage, "postgresql");
|
||||
|
||||
});
|
||||
|
||||
it.skip("Should verify the query creation and scope changing functionality.", () => {
|
||||
|
|
|
|||
|
|
@ -143,11 +143,11 @@ describe("Workspace constants", () => {
|
|||
cy.get(dataSourceSelector.previewTabRawContainer).contains("secrets is not defined");
|
||||
|
||||
//verify global const should be visible, secrets and deleted const are not in Inspector
|
||||
cy.get(commonWidgetSelector.inspectorIcon).click();
|
||||
cy.get(commonWidgetSelector.constantInspectorIcon).click();
|
||||
cy.get('[data-cy="inspector-node-restapiheaderkey"]').should('exist');
|
||||
cy.get('[data-cy="inspector-node-deleteconst"]').should('not.exist');
|
||||
cy.get('[data-cy="inspector-node-sconst"]').should('not.exist');
|
||||
// cy.get(commonWidgetSelector.sidebarinspector).click();
|
||||
// cy.get(commonWidgetSelector.constantInspectorIcon).click();
|
||||
// cy.get('[data-cy="inspector-node-restapiheaderkey"]').should('exist');
|
||||
// cy.get('[data-cy="inspector-node-deleteconst"]').should('not.exist');
|
||||
// cy.get('[data-cy="inspector-node-sconst"]').should('not.exist');
|
||||
|
||||
//Preview app and verify components
|
||||
cy.openInCurrentTab(commonWidgetSelector.previewButton);
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export const resolveHost = () => {
|
|||
const baseUrl = Cypress.config("baseUrl");
|
||||
|
||||
const urlMapping = {
|
||||
"http://localhost:8082": "http://localhost:8082",
|
||||
"http://localhost:3000": "http://localhost:3000",
|
||||
"http://localhost:3000/apps": "http://localhost:3000/apps",
|
||||
"http://localhost:4001": "http://localhost:3000",
|
||||
|
|
|
|||
|
|
@ -31,8 +31,11 @@ export const verifyAndModifyParameter = (paramName, value) => {
|
|||
export const openEditorSidebar = (widgetName = "") => {
|
||||
cy.hideTooltip();
|
||||
|
||||
cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover()
|
||||
cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click();
|
||||
|
||||
cy.get(`${commonWidgetSelector.draggableWidget(widgetName)}:eq(0)`).realHover().then(() => {
|
||||
cy.wait(1000);
|
||||
cy.get(commonWidgetSelector.widgetConfigHandle(widgetName)).click();
|
||||
})
|
||||
};
|
||||
|
||||
export const verifyAndModifyToggleFx = (
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const query = (operation) => {
|
|||
};
|
||||
|
||||
export const verifypreview = (type, data) => {
|
||||
cy.get(`[data-cy="preview-tab-${type}"]`).click();
|
||||
cy.get(`[data-cy="preview-tab-${type}"]`, { timeout: 15000 }).click();
|
||||
cy.get(`[data-cy="preview-${type}-data-container"]`).verifyVisibleElement(
|
||||
"contain.text",
|
||||
data,
|
||||
|
|
|
|||
|
|
@ -1,60 +1,49 @@
|
|||
export const verifyNodeData = (node, type, children, index = 0) => {
|
||||
cy.get(
|
||||
`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-length-color`
|
||||
)
|
||||
.eq(index)
|
||||
.realHover()
|
||||
.verifyVisibleElement("have.text", `${children}`);
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("have.text", node);
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-type`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("have.text", type);
|
||||
};
|
||||
import { commonWidgetSelector } from "Selectors/common";
|
||||
|
||||
export const openNode = (node, index = 0, time = 1000) => {
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
|
||||
.eq(index)
|
||||
.click();
|
||||
cy.wait(time);
|
||||
};
|
||||
|
||||
export const verifyValue = (node, type, children, index = 0) => {
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-2`)
|
||||
.eq(index)
|
||||
.realHover()
|
||||
.verifyVisibleElement("contain.text", `${children}`);
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("contain.text", node);
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("contain.text", type);
|
||||
};
|
||||
export const deleteComponentFromInspector = (node) => {
|
||||
cy.get('[data-cy="inspector-node-components"] > .node-key').click();
|
||||
cy.get(`[data-cy="inspector-node-${node}"] > .node-key`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click();
|
||||
};
|
||||
|
||||
export const verifyfunctions = (node, type, index = 0) => {
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .fs-10`)
|
||||
.eq(index)
|
||||
.realHover()
|
||||
.verifyVisibleElement("contain.text", `${type}`);
|
||||
cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .node-key`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("contain.text", node);
|
||||
// cy.get(`[data-cy="inspector-node-${node.toLowerCase()}"] > .mx-1`)
|
||||
// .eq(index)
|
||||
// .verifyVisibleElement("contain.text", type);
|
||||
export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => {
|
||||
openStateFromComponent(nodeName);
|
||||
verifyNodes(nodes, verificationFunction);
|
||||
};
|
||||
|
||||
export const verifyNodes = (nodes, verificationFunction) => {
|
||||
nodes.forEach(node => verificationFunction(node.key, node.type, node.value));
|
||||
};
|
||||
|
||||
export const openAndVerifyNode = (nodeName, nodes, verificationFunction) => {
|
||||
openNode(nodeName);
|
||||
verifyNodes(nodes, verificationFunction);
|
||||
export const openNode = (node, index = 0, time = 1000) => {
|
||||
cy.get(`[data-cy="inspector-${node.toLowerCase()}-expand-button"]`, { timeout: time })
|
||||
.eq(index)
|
||||
.click();
|
||||
};
|
||||
|
||||
export const openStateFromComponent = (widgetName) => {
|
||||
cy.get(commonWidgetSelector.draggableWidget(widgetName))
|
||||
.realHover()
|
||||
.realHover();
|
||||
|
||||
cy.get(commonWidgetSelector.draggableWidget(widgetName))
|
||||
.realHover()
|
||||
.then(() => {
|
||||
cy.get(`[data-cy="${widgetName}-inspect-button"]`)
|
||||
.realHover({ position: "topRight" })
|
||||
.last()
|
||||
.realClick();
|
||||
});
|
||||
}
|
||||
|
||||
export const verifyNodeData = (node, type, value, index = 0) => {
|
||||
cy.get(
|
||||
`[data-cy="inspector-${node.toLowerCase()}-label"]`
|
||||
)
|
||||
.eq(index)
|
||||
.realHover()
|
||||
.verifyVisibleElement("have.text", `${node}`);
|
||||
|
||||
cy.get(`[data-cy="inspector-${node.toLowerCase()}-value"]`)
|
||||
.eq(index)
|
||||
.verifyVisibleElement("have.text", type == 'Function' ? 'function' : value);
|
||||
};
|
||||
|
||||
export const deleteComponentFromInspector = (node) => {
|
||||
cy.get('[data-cy="inspector-menu-icon"]').click();
|
||||
cy.get(`[data-cy="inspector-delete-component-action"`).realHover().parent().find('[style="height: 13px; width: 13px;"] > img').last().click();
|
||||
};
|
||||
|
|
@ -1 +1 @@
|
|||
3.14.0
|
||||
3.15.0
|
||||
|
|
|
|||
3
frontend/assets/images/icons/editor/file-code.svg
Normal file
3
frontend/assets/images/icons/editor/file-code.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.02811 1.31381C2.22904 1.11288 2.50157 1 2.78572 1H7.07144C7.16616 1 7.257 1.03763 7.32397 1.10461L10.1812 3.96175C10.2481 4.02872 10.2857 4.11956 10.2857 4.21429V9.92857C10.2857 10.2127 10.1729 10.4853 9.97194 10.6862C9.77101 10.8871 9.49844 11 9.21429 11H2.78572C2.50156 11 2.22904 10.8871 2.02811 10.6862C1.82718 10.4853 1.71429 10.2127 1.71429 9.92857V2.07143C1.71429 1.78727 1.82718 1.51475 2.02811 1.31381ZM5.30739 5.26405C5.51659 5.47326 5.51659 5.81246 5.30739 6.02166L4.25762 7.07143L5.30739 8.12119C5.51659 8.33043 5.51659 8.66957 5.30739 8.87879C5.09818 9.088 4.75898 9.088 4.54977 8.87879L3.1212 7.45024C2.91199 7.24103 2.91199 6.90183 3.1212 6.69262L4.54977 5.26405C4.75898 5.05484 5.09818 5.05484 5.30739 5.26405ZM6.69263 6.02166C6.48342 5.81246 6.48342 5.47326 6.69263 5.26405C6.90184 5.05484 7.24104 5.05484 7.45024 5.26405L8.87879 6.69262C9.08801 6.90183 9.08801 7.24103 8.87879 7.45024L7.45024 8.87879C7.24104 9.088 6.90184 9.088 6.69263 8.87879C6.48342 8.66957 6.48342 8.33043 6.69263 8.12119L7.74239 7.07143L6.69263 6.02166Z" fill="#6A727C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
frontend/assets/images/icons/module-editor.svg
Normal file
3
frontend/assets/images/icons/module-editor.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75815 0.483576C6.95842 0.283307 7.28312 0.283307 7.48338 0.483576L9.79108 2.79127C9.99135 2.99154 9.99135 3.31624 9.79108 3.5165L7.48338 5.8242C7.28312 6.02447 6.95842 6.02447 6.75815 5.8242L4.45046 3.5165C4.25018 3.31624 4.25018 2.99154 4.45046 2.79127L6.75815 0.483576ZM2.912 4.32973C3.11227 4.12946 3.43696 4.12946 3.63723 4.32973L5.94492 6.63743C6.1452 6.83769 6.1452 7.16239 5.94492 7.36266L3.63723 9.67035C3.43696 9.87063 3.11227 9.87063 2.912 9.67035L0.604304 7.36266C0.404034 7.16239 0.404034 6.83769 0.604304 6.63743L2.912 4.32973ZM11.3296 4.32973C11.1293 4.12946 10.8046 4.12946 10.6043 4.32973L8.29662 6.63743C8.09634 6.83769 8.09634 7.16239 8.29662 7.36266L10.6043 9.67035C10.8046 9.87063 11.1293 9.87063 11.3296 9.67035L13.6373 7.36266C13.8375 7.16239 13.8375 6.83769 13.6373 6.63743L11.3296 4.32973ZM7.48338 8.17589C7.28312 7.97561 6.95842 7.97561 6.75815 8.17589L4.45046 10.4835C4.25018 10.6838 4.25018 11.0086 4.45046 11.2089L6.75815 13.5166C6.95842 13.7168 7.28312 13.7168 7.48338 13.5166L9.79108 11.2089C9.99135 11.0086 9.99135 10.6838 9.79108 10.4835L7.48338 8.17589Z" fill="#1E823B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="25" viewBox="0 0 20 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.60152 6.16675C2.53311 6.16675 1.66699 7.03286 1.66699 8.10128C1.66699 8.5122 2.00011 8.84532 2.41104 8.84532C2.82197 8.84532 3.15509 8.5122 3.15509 8.10128C3.15509 7.85472 3.35496 7.65485 3.60152 7.65485C4.01245 7.65485 4.34557 7.32172 4.34557 6.9108C4.34557 6.49987 4.01245 6.16675 3.60152 6.16675ZM5.98248 6.16675C5.57155 6.16675 5.23843 6.49987 5.23843 6.9108C5.23843 7.32172 5.57155 7.65485 5.98248 7.65485H7.7682C8.17912 7.65485 8.51224 7.32172 8.51224 6.9108C8.51224 6.49987 8.17912 6.16675 7.7682 6.16675H5.98248ZM9.4051 6.9108C9.4051 6.49987 9.73822 6.16675 10.1492 6.16675C11.2176 6.16675 12.0837 7.03286 12.0837 8.10128C12.0837 8.5122 11.7506 8.84532 11.3396 8.84532C10.9287 8.84532 10.5956 8.5122 10.5956 8.10128C10.5956 7.85472 10.3957 7.65485 10.1492 7.65485C9.73822 7.65485 9.4051 7.32172 9.4051 6.9108ZM3.15509 10.4822C3.15509 10.0713 2.82197 9.73818 2.41104 9.73818C2.00011 9.73818 1.66699 10.0713 1.66699 10.4822V12.2679C1.66699 12.6789 2.00011 13.012 2.41104 13.012C2.82197 13.012 3.15509 12.6789 3.15509 12.2679V10.4822ZM2.41104 13.9049C2.82197 13.9049 3.15509 14.238 3.15509 14.6489C3.15509 14.8955 3.35496 15.0953 3.60152 15.0953C4.01245 15.0953 4.34557 15.4285 4.34557 15.8394C4.34557 16.2503 4.01245 16.5834 3.60152 16.5834C2.53311 16.5834 1.66699 15.7173 1.66699 14.6489C1.66699 14.238 2.00011 13.9049 2.41104 13.9049ZM5.23843 11.5239C5.23843 10.5377 6.03792 9.73818 7.02415 9.73818H13.5718C14.558 9.73818 15.3575 10.5377 15.3575 11.5239V14.9372L12.1665 14.1394C10.6407 13.758 9.25865 15.14 9.6401 16.6658L10.438 19.8572H7.02415C6.03792 19.8572 5.23843 19.0577 5.23843 18.0715V11.5239ZM18.1596 21.7151L17.2157 22.659C16.9832 22.8915 16.6063 22.8915 16.3739 22.659L14.4138 20.699L13.3715 21.7413C13.0443 22.0684 12.4853 21.9137 12.3731 21.4648L11.083 16.3043C10.974 15.8683 11.3689 15.4735 11.8048 15.5825L16.9654 16.8726C17.4142 16.9848 17.569 17.5438 17.2419 17.8709L16.1995 18.9133L18.1596 20.8733C18.392 21.1058 18.392 21.4827 18.1596 21.7151Z" fill="#CCD1D5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="25" viewBox="0 0 20 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.91058 8.08983C3.73357 8.08983 3.59007 8.23333 3.59007 8.41034V9.05136C3.59007 9.5824 3.15957 10.0129 2.62853 10.0129C2.09749 10.0129 1.66699 9.5824 1.66699 9.05136V8.41034C1.66699 7.17124 2.67148 6.16675 3.91058 6.16675H4.55161C5.08265 6.16675 5.51315 6.59724 5.51315 7.12829C5.51315 7.65933 5.08265 8.08983 4.55161 8.08983H3.91058ZM14.4875 7.12829C14.4875 6.59724 14.918 6.16675 15.449 6.16675H16.0901C17.3292 6.16675 18.3337 7.17124 18.3337 8.41034V9.05136C18.3337 9.5824 17.9031 10.0129 17.3721 10.0129C16.8411 10.0129 16.4106 9.5824 16.4106 9.05136V8.41034C16.4106 8.23333 16.2671 8.08983 16.0901 8.08983H15.449C14.918 8.08983 14.4875 7.65933 14.4875 7.12829ZM18.3337 19.9488C18.3337 19.4178 17.9031 18.9873 17.3721 18.9873C16.8411 18.9873 16.4106 19.4178 16.4106 19.9488V20.5898C16.4106 20.7669 16.2671 20.9103 16.0901 20.9103H15.449C14.918 20.9103 14.4875 21.3409 14.4875 21.8719C14.4875 22.4029 14.918 22.8334 15.449 22.8334H16.0901C17.3292 22.8334 18.3337 21.8289 18.3337 20.5898V19.9488ZM2.62853 18.9873C3.15957 18.9873 3.59007 19.4178 3.59007 19.9488V20.5898C3.59007 20.7669 3.73357 20.9103 3.91058 20.9103H4.55161C5.08265 20.9103 5.51315 21.3409 5.51315 21.8719C5.51315 22.4029 5.08265 22.8334 4.55161 22.8334H3.91058C2.67148 22.8334 1.66699 21.8289 1.66699 20.5898V19.9488C1.66699 19.4178 2.09749 18.9873 2.62853 18.9873ZM8.39776 20.9103C7.86672 20.9103 7.43622 21.3409 7.43622 21.8719C7.43622 22.4029 7.86672 22.8334 8.39776 22.8334H11.6029C12.1339 22.8334 12.5644 22.4029 12.5644 21.8719C12.5644 21.3409 12.1339 20.9103 11.6029 20.9103H8.39776ZM7.43622 7.12829C7.43622 6.59724 7.86672 6.16675 8.39776 6.16675H11.6029C12.1339 6.16675 12.5644 6.59724 12.5644 7.12829C12.5644 7.65933 12.1339 8.08983 11.6029 8.08983H8.39776C7.86672 8.08983 7.43622 7.65933 7.43622 7.12829ZM3.59007 12.8975C3.59007 12.3665 3.15957 11.936 2.62853 11.936C2.09749 11.936 1.66699 12.3665 1.66699 12.8975V16.1026C1.66699 16.6337 2.09749 17.0642 2.62853 17.0642C3.15957 17.0642 3.59007 16.6337 3.59007 16.1026V12.8975ZM17.3721 11.936C17.9031 11.936 18.3337 12.3665 18.3337 12.8975V16.1026C18.3337 16.6337 17.9031 17.0642 17.3721 17.0642C16.8411 17.0642 16.4106 16.6337 16.4106 16.1026V12.8975C16.4106 12.3665 16.8411 11.936 17.3721 11.936ZM7.43622 10.0123C6.37413 10.0123 5.51315 10.8733 5.51315 11.9354V17.0636C5.51315 18.1256 6.37413 18.9866 7.43622 18.9866H12.5644C13.6265 18.9866 14.4875 18.1256 14.4875 17.0636V11.9354C14.4875 10.8733 13.6265 10.0123 12.5644 10.0123H7.43622Z" fill="#CCD1D5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
frontend/assets/images/modules/blank-module.svg
Normal file
3
frontend/assets/images/modules/blank-module.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="103" height="42" viewBox="0 0 103 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.8298 0.809768C39.1863 -0.0104135 41.9933 -0.35598 44.1701 0.486052C45.5729 1.02867 46.4514 2.04627 46.896 3.25806C47.3183 4.40936 47.3337 5.68124 47.1897 6.83915C46.9018 9.15345 45.9098 11.5232 45.1884 12.8463C42.2889 18.1639 38.4247 22.5794 33.9787 26.2876C34.5399 26.5461 35.1642 26.7825 35.8578 26.9918C41.9402 28.8269 49.132 29.0238 56.4351 28.3262C63.7231 27.6301 71.0241 26.0534 77.2795 24.4159C82.4789 23.0549 88.4083 21.1217 93.2877 18.5872H89.6629V15.5872C92.9426 15.5872 96.5411 15.0656 99.4739 14.2229C99.907 14.0984 100.423 13.9818 100.902 14.0288C101.15 14.0531 101.586 14.1389 101.965 14.489C102.401 14.8919 102.519 15.4129 102.498 15.8141C102.48 16.1636 102.362 16.4455 102.281 16.6106C102.192 16.7928 102.085 16.9574 101.986 17.0947C101.788 17.3689 101.541 17.6478 101.3 17.9028C100.838 18.3892 100.271 18.9171 99.8104 19.3451L99.7374 19.4131C99.4924 19.6411 99.2855 19.8344 99.1255 19.9913C98.9808 20.1332 98.9314 20.1911 98.9302 20.1901C98.9301 20.19 98.9304 20.1894 98.931 20.1883C98.2154 21.2383 97.4441 22.6219 96.8553 24.0608C96.2546 25.5287 95.8916 26.9391 95.8916 28.062C95.8916 28.8904 95.22 29.562 94.3916 29.562C93.5632 29.562 92.8916 28.8904 92.8916 28.062C92.8916 26.3835 93.4101 24.5588 94.0788 22.9247C94.3303 22.3099 94.6099 21.7058 94.9046 21.1268C89.6711 23.8822 83.3845 25.9189 78.0392 27.3182C71.7006 28.9774 64.229 30.5954 56.7204 31.3126C49.227 32.0284 41.5983 31.8574 34.9912 29.8639C33.6317 29.4537 32.4359 28.9358 31.3973 28.3162C22.759 34.7179 12.321 38.8406 2.44859 41.9316C1.65801 42.1791 0.816456 41.7389 0.568925 40.9483C0.321395 40.1577 0.761626 39.3162 1.55221 39.0687C11.0603 36.0917 20.8465 32.2185 28.9544 26.3854C26.3865 23.6838 25.8234 20.144 26.3038 16.6476C26.8596 12.6029 28.8172 8.40718 30.9138 5.00092C32.0894 3.09103 34.4739 1.6298 36.8298 0.809768ZM31.3635 24.5523C35.8622 20.9286 39.7152 16.6174 42.5545 11.4101C43.1627 10.2948 43.9859 8.29206 44.2126 6.46889C44.3259 5.55807 44.2734 4.81998 44.0795 4.29123C43.9077 3.82297 43.6215 3.49045 43.0878 3.28402C41.8659 2.81135 39.8705 2.92794 37.816 3.64305C35.761 4.35831 34.126 5.50551 33.4687 6.57347C31.4736 9.81466 29.7509 13.5989 29.2759 17.056C28.8625 20.0646 29.3991 22.6539 31.3635 24.5523Z" fill="#ACB2B9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -1 +1 @@
|
|||
Subproject commit 02711ae77f060db2b232bf8d4bab2f66dbbc2c1e
|
||||
Subproject commit 1e9e019c9e9045c62ce3045c0e979c933add717b
|
||||
35
frontend/package-lock.json
generated
35
frontend/package-lock.json
generated
|
|
@ -89,6 +89,7 @@
|
|||
"query-string": "^8.1.0",
|
||||
"rc-slider": "^10.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-accessible-treeview": "^2.11.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.6.5",
|
||||
"react-bootstrap": "^2.7.2",
|
||||
|
|
@ -105,6 +106,7 @@
|
|||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-hotkeys-hook": "^4.3.5",
|
||||
"react-i18next": "^12.1.5",
|
||||
|
|
@ -20550,6 +20552,11 @@
|
|||
"hermes-estree": "0.25.1"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight-words-core": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.3.tgz",
|
||||
"integrity": "sha512-m1O9HW3/GNHxzSIXWw1wCNXXsgLlxrP0OI6+ycGUhiUHkikqW3OrwVHz+lxeNBe5yqLESdIcj8PowHQ2zLvUvQ=="
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
|
|
@ -28043,6 +28050,17 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-accessible-treeview": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/react-accessible-treeview/-/react-accessible-treeview-2.11.1.tgz",
|
||||
"integrity": "sha512-lFegHjFJp2OvtoHMtbIqjby7N3MGDRASlbJsMLqElxQHwZ97xIYho2S4QvXKK7l3/nII0IKDQFJXZNBj6ecG3g==",
|
||||
"peerDependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-base16-styling": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
|
||||
|
|
@ -28657,6 +28675,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-highlight-words": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.21.0.tgz",
|
||||
"integrity": "sha512-SdWEeU9fIINArEPO1rO5OxPyuhdEKZQhHzZZP1ie6UeXQf+CjycT1kWaB+9bwGcVbR0NowuHK3RqgqNg6bgBDQ==",
|
||||
"dependencies": {
|
||||
"highlight-words-core": "^1.2.0",
|
||||
"memoize-one": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-highlight-words/node_modules/memoize-one": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz",
|
||||
"integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw=="
|
||||
},
|
||||
"node_modules/react-hot-toast": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@
|
|||
"query-string": "^8.1.0",
|
||||
"rc-slider": "^10.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-accessible-treeview": "^2.11.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-big-calendar": "^1.6.5",
|
||||
"react-bootstrap": "^2.7.2",
|
||||
|
|
@ -101,6 +102,7 @@
|
|||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-highlight-words": "^0.21.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-hotkeys-hook": "^4.3.5",
|
||||
"react-i18next": "^12.1.5",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { Suspense } from 'react';
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { authorizeWorkspace, updateCurrentSession } from '@/_helpers/authorizeWorkspace';
|
||||
import { authenticationService, tooljetService } from '@/_services';
|
||||
import { authenticationService, tooljetService, licenseService } from '@/_services';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import { PrivateRoute, AdminRoute, AppsRoute, SwitchWorkspaceRoute } from '@/Routes';
|
||||
import { HomePage } from '@/HomePage';
|
||||
|
|
@ -42,7 +42,6 @@ import { shallow } from 'zustand/shallow';
|
|||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { checkIfToolJetCloud } from '@/_helpers/utils';
|
||||
import { BasicPlanMigrationBanner } from '@/HomePage/BasicPlanMigrationBanner/BasicPlanMigrationBanner';
|
||||
import { licenseService } from '@/_services';
|
||||
|
||||
const AppWrapper = (props) => {
|
||||
const { isAppDarkMode } = useAppDarkMode();
|
||||
|
|
@ -295,6 +294,15 @@ class AppComponent extends React.Component {
|
|||
></Route>
|
||||
<Route path="settings/*" element={<InstanceSettings {...this.props} />}></Route>
|
||||
<Route path="/:workspaceId/settings/*" element={<Settings {...this.props} />}></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/:workspaceId/modules"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<HomePage switchDarkMode={this.switchDarkMode} darkMode={darkMode} appType={'module'} />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{getAuditLogsRoutes(this.props)}
|
||||
<Route
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import EditorHeader from '@/AppBuilder/Header';
|
|||
import LeftSidebar from '@/AppBuilder/LeftSidebar';
|
||||
import Popups from './Popups';
|
||||
import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
// const EditorHeader = lazy(() => import('@/AppBuilder/Header'));
|
||||
// const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar'));
|
||||
|
|
@ -22,12 +23,13 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
|
|||
// const QueryPanel = lazy(() => import('@/AppBuilder/QueryPanel'));
|
||||
|
||||
// TODO: split Loader into separate component and remove editor loading state from Editor
|
||||
export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode }) => {
|
||||
export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMode, appType = 'front-end' }) => {
|
||||
useAppData(appId, moduleId, darkMode);
|
||||
const isEditorLoading = useStore((state) => state.isEditorLoading);
|
||||
const currentMode = useStore((state) => state.currentMode);
|
||||
const isEditorLoading = useStore((state) => state.loaderStore.modules[moduleId].isEditorLoading, shallow);
|
||||
const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
|
||||
const isModuleEditor = appType === 'module';
|
||||
|
||||
const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode);
|
||||
const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow);
|
||||
|
||||
const changeToDarkMode = (newMode) => {
|
||||
updateIsTJDarkMode(newMode);
|
||||
|
|
@ -45,19 +47,19 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
|
|||
return (
|
||||
<div className={cx('wrapper', { editor: currentMode === 'edit' })}>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditorHeader darkMode={darkMode} />
|
||||
<LeftSidebar switchDarkMode={changeToDarkMode} darkMode={darkMode} />
|
||||
</Suspense>
|
||||
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ModuleProvider moduleId={moduleId}>
|
||||
<AppCanvas moduleId={moduleId} appId={appId} />
|
||||
<ModuleProvider moduleId={moduleId} appType={appType} isModuleMode={false} isModuleEditor={isModuleEditor}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<EditorHeader darkMode={darkMode} />
|
||||
<LeftSidebar switchDarkMode={changeToDarkMode} darkMode={darkMode} />
|
||||
</Suspense>
|
||||
{window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && <RealtimeCursors />}
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<AppCanvas appId={appId} />
|
||||
<QueryPanel darkMode={darkMode} />
|
||||
<RightSideBar darkMode={darkMode} />
|
||||
</ModuleProvider>
|
||||
</DndProvider>
|
||||
<Popups darkMode={darkMode} />
|
||||
</DndProvider>
|
||||
<Popups darkMode={darkMode} />
|
||||
</ModuleProvider>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Container } from './Container';
|
||||
import Grid from './Grid';
|
||||
import { EditorSelecto } from './Selecto';
|
||||
import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { HotkeyProvider } from './HotkeyProvider';
|
||||
import './appCanvas.scss';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
|
@ -18,15 +18,18 @@ import useAppCanvasMaxWidth from './useAppCanvasMaxWidth';
|
|||
import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation';
|
||||
import useSidebarMargin from './useSidebarMargin';
|
||||
|
||||
export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
||||
export const AppCanvas = ({ appId, isViewerSidebarPinned, isViewer = false }) => {
|
||||
const { moduleId, isModuleMode, appType } = useModuleContext();
|
||||
const canvasContainerRef = useRef();
|
||||
const handleCanvasContainerMouseUp = useStore((state) => state.handleCanvasContainerMouseUp, shallow);
|
||||
const canvasHeight = useStore((state) => state.canvasHeight);
|
||||
const creationMode = useStore((state) => state.app.creationMode);
|
||||
const environmentLoadingState = useStore((state) => state.environmentLoadingState || state.isEditorLoading);
|
||||
const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth());
|
||||
const canvasHeight = useStore((state) => state.appStore.modules[moduleId].canvasHeight);
|
||||
const creationMode = useStore((state) => state.appStore.modules[moduleId].app.creationMode);
|
||||
const environmentLoadingState = useStore(
|
||||
(state) => state.environmentLoadingState || state.loaderStore.modules[moduleId].isEditorLoading
|
||||
);
|
||||
const [canvasWidth, setCanvasWidth] = useState(getCanvasWidth(moduleId));
|
||||
const gridWidth = canvasWidth / NO_OF_GRIDS;
|
||||
const currentMode = useStore((state) => state.currentMode, shallow);
|
||||
const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
|
||||
const pageSidebarStyle = useStore((state) => state?.pageSettings?.definition?.properties?.style, shallow);
|
||||
const currentLayout = useStore((state) => state.currentLayout, shallow);
|
||||
const queryPanelHeight = useStore((state) => state?.queryPanel?.queryPanelHeight || 0);
|
||||
|
|
@ -42,23 +45,79 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow);
|
||||
const getPageId = useStore((state) => state.getCurrentPageId, shallow);
|
||||
|
||||
const hideSidebar = isModuleMode || isPagesSidebarHidden || appType === 'module';
|
||||
|
||||
useEffect(() => {
|
||||
// Need to remove this if we shift setExposedVariable Logic outside of components
|
||||
// Currently present to run onLoadQueries after the component is mounted
|
||||
setIsComponentLayoutReady(true);
|
||||
return () => setIsComponentLayoutReady(false);
|
||||
setIsComponentLayoutReady(true, moduleId);
|
||||
return () => setIsComponentLayoutReady(false, moduleId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
const _canvasWidth = document.getElementById('real-canvas')?.getBoundingClientRect()?.width;
|
||||
const _canvasWidth =
|
||||
moduleId === 'canvas'
|
||||
? document.getElementById('real-canvas')?.getBoundingClientRect()?.width
|
||||
: document.getElementById(moduleId)?.getBoundingClientRect()?.width;
|
||||
if (_canvasWidth !== 0) setCanvasWidth(_canvasWidth);
|
||||
}
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
if (moduleId === 'canvas') {
|
||||
window.addEventListener('resize', handleResize);
|
||||
} else {
|
||||
const elem = document.getElementById(moduleId);
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
if (elem) resizeObserver.observe(elem);
|
||||
|
||||
return () => {
|
||||
if (elem) resizeObserver.unobserve(elem);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}
|
||||
handleResize();
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [currentLayout, canvasMaxWidth, isViewerSidebarPinned]);
|
||||
}, [currentLayout, canvasMaxWidth, isViewerSidebarPinned, moduleId]);
|
||||
|
||||
const styles = useMemo(() => {
|
||||
const canvasBgColor =
|
||||
currentMode === 'view'
|
||||
? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor)
|
||||
: !isAppDarkMode
|
||||
? '#EBEBEF'
|
||||
: '#2F3C4C';
|
||||
|
||||
if (isModuleMode) {
|
||||
return {
|
||||
borderLeft: 'none',
|
||||
height: '100%',
|
||||
background: canvasBgColor,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid',
|
||||
height: currentMode === 'edit' ? canvasContainerHeight : '100%',
|
||||
background: canvasBgColor,
|
||||
marginLeft:
|
||||
isViewerSidebarPinned && !hideSidebar && currentLayout !== 'mobile' && currentMode !== 'edit'
|
||||
? pageSidebarStyle === 'icon'
|
||||
? '65px'
|
||||
: '210px'
|
||||
: 'auto',
|
||||
};
|
||||
}, [
|
||||
currentMode,
|
||||
isAppDarkMode,
|
||||
isModuleMode,
|
||||
editorMarginLeft,
|
||||
canvasContainerHeight,
|
||||
isViewerSidebarPinned,
|
||||
hideSidebar,
|
||||
currentLayout,
|
||||
pageSidebarStyle,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -73,29 +132,14 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
className={cx(
|
||||
'canvas-container align-items-center page-container',
|
||||
{ 'dark-theme theme-dark': isAppDarkMode, close: !isViewerSidebarPinned },
|
||||
{ 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' }
|
||||
{ 'overflow-x-auto': (currentMode === 'edit' && isSidebarOpen) || currentMode === 'view' },
|
||||
{ 'overflow-x-hidden': moduleId !== 'canvas' } // Disbling horizontal scroll for modules in view mode
|
||||
)}
|
||||
style={{
|
||||
// transform: `scale(1)`,
|
||||
borderLeft: currentMode === 'edit' && editorMarginLeft + 'px solid',
|
||||
height: currentMode === 'edit' ? canvasContainerHeight : '100%',
|
||||
background:
|
||||
currentMode === 'view'
|
||||
? computeViewerBackgroundColor(isAppDarkMode, canvasBgColor)
|
||||
: !isAppDarkMode
|
||||
? '#EBEBEF'
|
||||
: '#2F3C4C',
|
||||
marginLeft:
|
||||
isViewerSidebarPinned && !isPagesSidebarHidden && currentLayout !== 'mobile' && currentMode !== 'edit'
|
||||
? pageSidebarStyle === 'icon'
|
||||
? '65px'
|
||||
: '210px'
|
||||
: 'auto',
|
||||
}}
|
||||
style={styles}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
minWidth: `calc((100vw - 300px) - 48px)`,
|
||||
minWidth: isModuleMode ? '100%' : `calc((100vw - 300px) - 48px)`,
|
||||
}}
|
||||
className={`app-${appId} _tooljet-page-${getPageId()}`}
|
||||
>
|
||||
|
|
@ -107,7 +151,7 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
{environmentLoadingState !== 'loading' && (
|
||||
<div>
|
||||
<Container
|
||||
id="canvas"
|
||||
id={moduleId}
|
||||
gridWidth={gridWidth}
|
||||
canvasWidth={canvasWidth}
|
||||
canvasHeight={canvasHeight}
|
||||
|
|
@ -115,8 +159,9 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => {
|
|||
canvasMaxWidth={canvasMaxWidth}
|
||||
isViewerSidebarPinned={isViewerSidebarPinned}
|
||||
pageSidebarStyle={pageSidebarStyle}
|
||||
appType={appType}
|
||||
/>
|
||||
<div id="component-portal" />
|
||||
{appType !== 'module' && <div id="component-portal" />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import './configHandle.scss';
|
|||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { findHighestLevelofSelection } from '../Grid/gridUtils';
|
||||
import SolidIcon from '@/_ui/Icon/solidIcons/index';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { DROPPABLE_PARENTS } from '../appCanvasConstants';
|
||||
|
||||
const CONFIG_HANDLE_HEIGHT = 20;
|
||||
const BUFFER_HEIGHT = 1;
|
||||
|
|
@ -18,10 +20,12 @@ export const ConfigHandle = ({
|
|||
showHandle,
|
||||
componentType,
|
||||
visibility,
|
||||
isModuleContainer,
|
||||
subContainerIndex,
|
||||
}) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
const componentName = useStore((state) => state.getComponentDefinition(id)?.component?.name || '', shallow);
|
||||
const componentName = useStore((state) => state.getComponentDefinition(id, moduleId)?.component?.name || '', shallow);
|
||||
const isMultipleComponentsSelected = useStore(
|
||||
(state) => (findHighestLevelofSelection(state?.selectedComponents)?.length > 1 ? true : false),
|
||||
shallow
|
||||
|
|
@ -43,6 +47,7 @@ export const ConfigHandle = ({
|
|||
return (
|
||||
(subContainerIndex === 0 || subContainerIndex === null) &&
|
||||
(isWidgetHovered ||
|
||||
isModuleContainer ||
|
||||
(showHandle && (!isMultipleComponentsSelected || (isModal && isModalOpen)) && !anyComponentHovered))
|
||||
);
|
||||
}, shallow);
|
||||
|
|
@ -67,7 +72,9 @@ export const ConfigHandle = ({
|
|||
if (componentType === 'Tabs') {
|
||||
setFocusedParentId(`${id}-${currentTab}`);
|
||||
} else {
|
||||
setFocusedParentId(id);
|
||||
if (DROPPABLE_PARENTS.has(componentType)) {
|
||||
setFocusedParentId(id);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
@ -125,20 +132,22 @@ export const ConfigHandle = ({
|
|||
data-cy={`${componentName.toLowerCase()}-inspect-button`}
|
||||
className="config-handle-inspect"
|
||||
/>
|
||||
<span
|
||||
style={{ cursor: 'pointer', marginLeft: '5px' }}
|
||||
onClick={() => {
|
||||
deleteComponents([id]);
|
||||
}}
|
||||
data-cy={`${componentName.toLowerCase()}-delete-button`}
|
||||
>
|
||||
<SolidIcon
|
||||
name="trash"
|
||||
width="12"
|
||||
height="12"
|
||||
fill={visibility === false ? 'var(--text-placeholder)' : '#fff'}
|
||||
/>
|
||||
</span>
|
||||
{!isModuleContainer && (
|
||||
<span
|
||||
style={{ cursor: 'pointer', marginLeft: '5px' }}
|
||||
onClick={() => {
|
||||
deleteComponents([id]);
|
||||
}}
|
||||
data-cy={`${componentName.toLowerCase()}-delete-button`}
|
||||
>
|
||||
<SolidIcon
|
||||
name="trash"
|
||||
width="12"
|
||||
height="12"
|
||||
fill={visibility === false ? 'var(--text-placeholder)' : '#fff'}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer';
|
|||
import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants';
|
||||
import { isPDFSupported } from '@/_helpers/appUtils';
|
||||
import toast from 'react-hot-toast';
|
||||
import { ModuleContainerBlank } from '@/modules/Modules/components';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import useSortedComponents from '../_hooks/useSortedComponents';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
//TODO: Revisit the logic of height (dropRef)
|
||||
|
||||
|
|
@ -50,9 +53,12 @@ export const Container = React.memo(
|
|||
isViewerSidebarPinned,
|
||||
pageSidebarStyle,
|
||||
componentType,
|
||||
appType,
|
||||
}) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const realCanvasRef = useRef(null);
|
||||
const components = useStore((state) => state.getContainerChildrenMapping(id), shallow);
|
||||
const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow);
|
||||
|
||||
const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow);
|
||||
const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow);
|
||||
const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow);
|
||||
|
|
@ -61,12 +67,14 @@ export const Container = React.memo(
|
|||
shallow
|
||||
);
|
||||
const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow);
|
||||
const currentMode = useStore((state) => state.currentMode, shallow);
|
||||
const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
|
||||
const currentLayout = useStore((state) => state.currentLayout, shallow);
|
||||
const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow);
|
||||
const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop;
|
||||
|
||||
const isContainerReadOnly = useMemo(() => {
|
||||
return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view';
|
||||
}, [componentType, index, currentMode]);
|
||||
}, [index, componentType, currentMode]);
|
||||
|
||||
const [{ isOverCurrent }, drop] = useDrop({
|
||||
accept: 'box',
|
||||
|
|
@ -75,7 +83,9 @@ export const Container = React.memo(
|
|||
item.canvasId = id;
|
||||
item.canvasWidth = getContainerCanvasWidth();
|
||||
},
|
||||
drop: async ({ componentType }, monitor) => {
|
||||
drop: async ({ componentType, component }, monitor) => {
|
||||
setShowModuleBorder(false); // Hide the module border when dropping
|
||||
if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return;
|
||||
const didDrop = monitor.didDrop();
|
||||
if (didDrop) return;
|
||||
if (componentType === 'PDF' && !isPDFSupported()) {
|
||||
|
|
@ -84,15 +94,41 @@ export const Container = React.memo(
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// IMPORTANT: This logic needs to be changed when we implement the module versioning
|
||||
const moduleInfo = component?.moduleId
|
||||
? {
|
||||
moduleId: component.moduleId,
|
||||
versionId: component.versionId,
|
||||
environmentId: component.environmentId,
|
||||
moduleName: component.displayName,
|
||||
moduleContainer: component.moduleContainer,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) {
|
||||
const parentComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id);
|
||||
const parentComponent = addNewWidgetToTheEditor(
|
||||
componentType,
|
||||
monitor,
|
||||
currentLayout,
|
||||
realCanvasRef,
|
||||
id,
|
||||
moduleInfo
|
||||
);
|
||||
const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout);
|
||||
const newComponents = [parentComponent, ...childComponents];
|
||||
await addComponentToCurrentPage(newComponents);
|
||||
// setSelectedComponents([parentComponent?.id]);
|
||||
setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION);
|
||||
} else {
|
||||
const newComponent = addNewWidgetToTheEditor(componentType, monitor, currentLayout, realCanvasRef, id);
|
||||
const newComponent = addNewWidgetToTheEditor(
|
||||
componentType,
|
||||
monitor,
|
||||
currentLayout,
|
||||
realCanvasRef,
|
||||
id,
|
||||
moduleInfo
|
||||
);
|
||||
await addComponentToCurrentPage([newComponent]);
|
||||
// setSelectedComponents([newComponent?.id]);
|
||||
setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION);
|
||||
|
|
@ -103,7 +139,11 @@ export const Container = React.memo(
|
|||
}),
|
||||
});
|
||||
|
||||
const showEmptyContainer = currentMode === 'edit' && id === 'canvas' && components.length === 0 && !isOverCurrent;
|
||||
const showEmptyContainer =
|
||||
currentMode === 'edit' &&
|
||||
(id === 'canvas' || componentType === 'ModuleContainer') &&
|
||||
components.length === 0 &&
|
||||
!isOverCurrent;
|
||||
|
||||
function getContainerCanvasWidth() {
|
||||
if (canvasWidth !== undefined) {
|
||||
|
|
@ -115,7 +155,7 @@ export const Container = React.memo(
|
|||
}
|
||||
const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS;
|
||||
useEffect(() => {
|
||||
useGridStore.getState().actions.setSubContainerWidths(id, getContainerCanvasWidth() / NO_OF_GRIDS);
|
||||
useGridStore.getState().actions.setSubContainerWidths(id, gridWidth);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [canvasWidth, listViewMode, columns]);
|
||||
|
||||
|
|
@ -125,7 +165,8 @@ export const Container = React.memo(
|
|||
!isPagesSidebarHidden &&
|
||||
isViewerSidebarPinned &&
|
||||
currentLayout !== 'mobile' &&
|
||||
currentMode !== 'edit'
|
||||
currentMode !== 'edit' &&
|
||||
appType !== 'module'
|
||||
) {
|
||||
return `calc(100% - ${pageSidebarStyle === 'icon' ? '65px' : '210px'})`;
|
||||
}
|
||||
|
|
@ -147,6 +188,23 @@ export const Container = React.memo(
|
|||
[setLastCanvasClickPosition]
|
||||
);
|
||||
|
||||
/* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag.
|
||||
Hence display is set to none on dragover and removed only when the component is added */
|
||||
|
||||
const renderEmptyContainer = () => {
|
||||
if (components && components?.length !== 0) return;
|
||||
|
||||
const styles = {
|
||||
display: showEmptyContainer ? 'block' : 'none',
|
||||
...(componentType === 'ModuleContainer' ? { height: '100%', width: '100%' } : {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles}>
|
||||
{componentType === 'ModuleContainer' ? <ModuleContainerBlank /> : <NoComponentCanvasContainer />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const sortedComponents = useSortedComponents(components, currentLayout, id);
|
||||
|
||||
return (
|
||||
|
|
@ -182,7 +240,7 @@ export const Container = React.memo(
|
|||
}}
|
||||
className={cx('real-canvas', {
|
||||
'sub-canvas': id !== 'canvas',
|
||||
'show-grid': isOverCurrent && (index === 0 || index === null),
|
||||
'show-grid': isOverCurrent && (index === 0 || index === null) && currentMode === 'edit',
|
||||
})}
|
||||
id={id === 'canvas' ? 'real-canvas' : `canvas-${id}`}
|
||||
data-cy="real-canvas"
|
||||
|
|
@ -216,14 +274,7 @@ export const Container = React.memo(
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Due to some reason react-dnd does not identify the dragover element if this element is dynamically removed on drag.
|
||||
Hence display is set to none on dragover and removed only when the component is added */}
|
||||
{(!components || components?.length === 0) && (
|
||||
<div style={{ display: showEmptyContainer ? 'block' : 'none' }}>
|
||||
<NoComponentCanvasContainer />
|
||||
</div>
|
||||
)}
|
||||
{renderEmptyContainer()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import {
|
|||
import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import './Grid.css';
|
||||
import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants';
|
||||
|
||||
import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' };
|
||||
const RESIZABLE_CONFIG = {
|
||||
edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'],
|
||||
|
|
@ -38,17 +38,19 @@ const RESIZABLE_CONFIG = {
|
|||
export const GRID_HEIGHT = 10;
|
||||
|
||||
export default function Grid({ gridWidth, currentLayout }) {
|
||||
const { moduleId, isModuleEditor } = useModuleContext();
|
||||
const lastDraggedEventsRef = useRef(null);
|
||||
const updateCanvasBottomHeight = useStore((state) => state.updateCanvasBottomHeight, shallow);
|
||||
const setComponentLayout = useStore((state) => state.setComponentLayout, shallow);
|
||||
const mode = useStore((state) => state.currentMode, shallow);
|
||||
const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
|
||||
const [boxList, setBoxList] = useState([]);
|
||||
const currentPageComponents = useStore((state) => state.getCurrentPageComponents(), shallow);
|
||||
const currentPageComponents = useStore((state) => state.getCurrentPageComponents(moduleId), shallow);
|
||||
const selectedComponents = useStore((state) => state.selectedComponents, shallow);
|
||||
const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow);
|
||||
const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow);
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const isGroupHandleHoverd = useIsGroupHandleHoverd();
|
||||
|
||||
const openModalWidgetId = useOpenModalWidgetId();
|
||||
const moveableRef = useRef(null);
|
||||
const triggerCanvasUpdater = useStore((state) => state.triggerCanvasUpdater, shallow);
|
||||
|
|
@ -132,7 +134,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
|
||||
const noOfBoxs = Object.values(boxList || []).length;
|
||||
useEffect(() => {
|
||||
updateCanvasBottomHeight(boxList);
|
||||
updateCanvasBottomHeight(boxList, moduleId);
|
||||
noOfBoxs != 0;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [noOfBoxs, triggerCanvasUpdater]);
|
||||
|
|
@ -333,8 +335,8 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
};
|
||||
|
||||
const isComponentVisible = (id) => {
|
||||
const component = getResolvedComponent(id);
|
||||
const componentExposedVisibility = getExposedValueOfComponent(id)?.isVisible;
|
||||
const component = getResolvedComponent(id, null, moduleId);
|
||||
const componentExposedVisibility = getExposedValueOfComponent(id, moduleId)?.isVisible;
|
||||
if (componentExposedVisibility === false) return false;
|
||||
let visibility;
|
||||
if (isArray(component)) {
|
||||
|
|
@ -422,6 +424,10 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
const moveableBox = document.querySelector(`.moveable-control-box`);
|
||||
const showConfigHandle = (e) => {
|
||||
const targetId = e.target.offsetParent.getAttribute('target-id');
|
||||
const componentType = getComponentTypeFromId(targetId);
|
||||
if (componentType === 'ModuleContainer') {
|
||||
return;
|
||||
}
|
||||
useStore.getState().setHoveredComponentBoundaryId(targetId);
|
||||
};
|
||||
const hideConfigHandle = () => {
|
||||
|
|
@ -461,9 +467,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
widgetId = widgetId.split('-').slice(0, -1).join('-');
|
||||
widgetType = boxList.find(({ id }) => id === widgetId)?.component?.component;
|
||||
}
|
||||
if (
|
||||
!['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'Listview', 'Container', 'Table'].includes(widgetType)
|
||||
) {
|
||||
if (!DROPPABLE_PARENTS.has(widgetType)) {
|
||||
isDroppable = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -477,10 +481,15 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
.map(({ component }) => component.component);
|
||||
const parentId = draggedOverElemId?.length > 36 ? draggedOverElemId.slice(0, 36) : draggedOverElemId;
|
||||
const parentWidgetType = getComponentTypeFromId(parentId);
|
||||
const restrictedWidgetsTobeDropped =
|
||||
let restrictedWidgetsTobeDropped =
|
||||
RESTRICTED_WIDGETS_CONFIG?.[parentWidgetType]?.filter((widgetType) =>
|
||||
widgetsTypeToBeDropped.includes(widgetType)
|
||||
) || [];
|
||||
|
||||
if (isModuleEditor && parentId === undefined) {
|
||||
restrictedWidgetsTobeDropped = widgetsTypeToBeDropped;
|
||||
// useGridStore.getState().actions.setIsGroupHandleHoverd(false);
|
||||
}
|
||||
const isParentChangeAllowed = isEmpty(restrictedWidgetsTobeDropped);
|
||||
|
||||
if (!isParentChangeAllowed) {
|
||||
|
|
@ -505,7 +514,12 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
});
|
||||
|
||||
// Show error message
|
||||
toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`);
|
||||
if (isModuleEditor) {
|
||||
// Added this to hide configHandle when multiple components were dragged using the configHandle and placed outside the module
|
||||
setSelectedComponents([]);
|
||||
} else {
|
||||
toast.error(`${restrictedWidgetsTobeDropped} is not compatible as a child component of ${parentWidgetType}`);
|
||||
}
|
||||
}
|
||||
|
||||
const parentElm = draggedOverElem || document.getElementById('real-canvas');
|
||||
|
|
@ -588,11 +602,11 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
keepRatio={false}
|
||||
individualGroupableProps={individualGroupableProps}
|
||||
onResize={(e) => {
|
||||
if(resizingComponentId !== e.target.id) {
|
||||
if (resizingComponentId !== e.target.id) {
|
||||
useGridStore.getState().actions.setResizingComponentId(e.target.id);
|
||||
showGridLines();
|
||||
}
|
||||
|
||||
|
||||
const currentWidget = boxList.find(({ id }) => id === e.target.id);
|
||||
let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth;
|
||||
if (currentWidget.component?.parent) {
|
||||
|
|
@ -874,7 +888,7 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
if (!e.lastEvent) return;
|
||||
|
||||
// Build the drag context from the event
|
||||
const dragContext = dragContextBuilder({ event: e, widgets: boxList });
|
||||
const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor });
|
||||
const { target, source, dragged } = dragContext;
|
||||
|
||||
const targetSlotId = target?.slotId;
|
||||
|
|
@ -967,6 +981,19 @@ export default function Grid({ gridWidth, currentLayout }) {
|
|||
left: modalRect.left - mainRect.left,
|
||||
};
|
||||
setCanvasBounds({ ...relativePosition });
|
||||
} else if (isModuleEditor) {
|
||||
const moduleContainer = e.target.closest('.module-container-canvas');
|
||||
const mainCanvas = document.getElementById('real-canvas');
|
||||
|
||||
const mainRect = mainCanvas.getBoundingClientRect();
|
||||
const modalRect = moduleContainer.getBoundingClientRect();
|
||||
const relativePosition = {
|
||||
top: modalRect.top - mainRect.top,
|
||||
right: mainRect.right - modalRect.right + moduleContainer.offsetWidth,
|
||||
bottom: modalRect.height + (modalRect.top - mainRect.top),
|
||||
left: modalRect.left - mainRect.left,
|
||||
};
|
||||
setCanvasBounds({ ...relativePosition });
|
||||
}
|
||||
|
||||
// This block is to show grid lines on the canvas when the dragged element is over a new canvas
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import {
|
|||
RESTRICTED_WIDGETS_CONFIG,
|
||||
RESTRICTED_WIDGET_SLOTS_CONFIG,
|
||||
} from '@/AppBuilder/WidgetManager/configs/restrictedWidgetsConfig';
|
||||
import { DROPPABLE_PARENTS } from '../../appCanvasConstants';
|
||||
|
||||
const CANVAS_ID = 'canvas';
|
||||
const REAL_CANVAS_ID = 'real-canvas';
|
||||
|
|
@ -84,8 +85,6 @@ export class DragEntity {
|
|||
* This class helps determine if a slot is valid and handles various properties like modals.
|
||||
*/
|
||||
export class DropAreaEntity {
|
||||
static dropAreaWidgets = ['Calendar', 'Kanban', 'Form', 'Tabs', 'Modal', 'ModalV2', 'Listview', 'Container', 'Table'];
|
||||
|
||||
constructor(widget, slotId) {
|
||||
this.widget = widget; // The widget that owns this slot
|
||||
this.id = widget?.id || CANVAS_ID; // ID of the widget
|
||||
|
|
@ -119,7 +118,7 @@ export class DropAreaEntity {
|
|||
|
||||
// Determines if the slot is a valid drop target
|
||||
get isDroppable() {
|
||||
return DropAreaEntity.dropAreaWidgets.includes(this.widgetType);
|
||||
return DROPPABLE_PARENTS.has(this.widgetType);
|
||||
}
|
||||
|
||||
// Returns the type of slot (header, footer, body, etc.)
|
||||
|
|
@ -143,7 +142,7 @@ export class DropAreaEntity {
|
|||
* - Any restrictions based on parent-child relationships
|
||||
*/
|
||||
export class DragContext {
|
||||
constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets }) {
|
||||
constructor({ sourceSlotId, targetSlotId, draggedWidgetId, widgets, isModuleEditor = false }) {
|
||||
const sourceWidgetId = sourceSlotId?.slice(0, 36);
|
||||
const sourceWidget = getWidgetById(widgets, sourceWidgetId);
|
||||
|
||||
|
|
@ -156,6 +155,7 @@ export class DragContext {
|
|||
this.target = new DropAreaEntity(targetWidget, targetSlotId);
|
||||
this.dragged = new DragEntity(draggedWidget);
|
||||
this.widgets = widgets;
|
||||
this.isModuleEditor = isModuleEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -168,7 +168,13 @@ export class DragContext {
|
|||
}
|
||||
|
||||
get isDroppable() {
|
||||
const { dragged, target } = this;
|
||||
const { dragged, target, isModuleEditor } = this;
|
||||
|
||||
// If the target is the canvas and we are in module editor,
|
||||
// then we don't want to drop the widget outside the module
|
||||
if (isModuleEditor && target.id === 'canvas') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const restrictedWidgetsOnTarget = RESTRICTED_WIDGETS_CONFIG?.[target.widgetType] || [];
|
||||
const restrictedWidgetsOnTargetSlot = RESTRICTED_WIDGET_SLOTS_CONFIG?.[target.slotType] || [];
|
||||
|
|
@ -181,13 +187,19 @@ export class DragContext {
|
|||
/**
|
||||
* Constructs the **dragging context** by gathering all relevant details from the event.
|
||||
*/
|
||||
export function dragContextBuilder({ event, widgets }) {
|
||||
export function dragContextBuilder({ event, widgets, isModuleEditor = false }) {
|
||||
const draggedWidgetId = event.target.id;
|
||||
const draggedWidget = getWidgetById(widgets, draggedWidgetId);
|
||||
const sourceSlotId = draggedWidget.parent;
|
||||
|
||||
// Initialize drag context
|
||||
const context = new DragContext({ widgets, draggedWidgetId, sourceSlotId, targetSlotId: sourceSlotId });
|
||||
const context = new DragContext({
|
||||
widgets,
|
||||
draggedWidgetId,
|
||||
sourceSlotId,
|
||||
targetSlotId: sourceSlotId,
|
||||
isModuleEditor,
|
||||
});
|
||||
|
||||
// Determine the potential drop target
|
||||
const targetSlotId = getDroppableSlotIdOnScreen(event, widgets);
|
||||
|
|
@ -209,7 +221,7 @@ export const getDroppableSlotIdOnScreen = (event, widgets) => {
|
|||
.map((ele) => extractSlotId(ele))
|
||||
.filter((slotId) => {
|
||||
const widgetType = getWidgetById(widgets, slotId.slice(0, 36))?.component?.component || CANVAS_ID;
|
||||
return DropAreaEntity.dropAreaWidgets.includes(widgetType);
|
||||
return DROPPABLE_PARENTS.has(widgetType);
|
||||
});
|
||||
|
||||
return slotId;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { pasteComponents, copyComponents } from './appCanvasUtils';
|
||||
import useKeyHooks from '@/_hooks/useKeyHooks';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }) => {
|
||||
const { isModuleEditor } = useModuleContext();
|
||||
const canvasRef = useRef(null);
|
||||
const focusedParentId = useStore((state) => state.focusedParentId, shallow);
|
||||
const handleUndo = useStore((state) => state.handleUndo);
|
||||
|
|
@ -18,10 +20,13 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }
|
|||
const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow);
|
||||
const setSelectedComponents = useStore((state) => state.setSelectedComponents, shallow);
|
||||
const containerChildrenMapping = useStore((state) => state.containerChildrenMapping, shallow);
|
||||
const getComponentTypeFromId = useStore((state) => state.getComponentTypeFromId, shallow);
|
||||
|
||||
useHotkeys('meta+z, control+z', handleUndo, { enabled: mode === 'edit' });
|
||||
useHotkeys('meta+shift+z, control+shift+z', handleRedo, { enabled: mode === 'edit' });
|
||||
|
||||
const paste = async () => {
|
||||
if (isModuleEditor && !focusedParentId) return;
|
||||
if (navigator.clipboard && typeof navigator.clipboard.readText === 'function') {
|
||||
try {
|
||||
const cliptext = await navigator.clipboard.readText();
|
||||
|
|
@ -61,6 +66,24 @@ export const HotkeyProvider = ({ children, mode, currentLayout, canvasMaxWidth }
|
|||
enableReleasedVersionPopupState();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable cut, copy, paste, delete shortcuts in module editor
|
||||
// or when a ModuleContainer is selected
|
||||
if (isModuleEditor) {
|
||||
const selectedComponents = getSelectedComponents();
|
||||
if (
|
||||
selectedComponents.length > 0 &&
|
||||
selectedComponents.some((id) => {
|
||||
const componentType = getComponentTypeFromId(id, 'canvas');
|
||||
return componentType === 'ModuleContainer';
|
||||
})
|
||||
) {
|
||||
if (['KeyC', 'KeyX', 'KeyV', 'KeyD', 'Backspace'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'Escape':
|
||||
handleEscapeKeyPress(); // clears the selected components
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { renderTooltip } from '@/_helpers/appUtils';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import ErrorBoundary from '@/_ui/ErrorBoundary';
|
||||
import { BOX_PADDING } from './appCanvasConstants';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY = [
|
||||
'Table',
|
||||
|
|
@ -47,23 +48,27 @@ const RenderWidget = ({
|
|||
inCanvas = false,
|
||||
darkMode,
|
||||
}) => {
|
||||
const componentDefinition = useStore((state) => state.getComponentDefinition(id), shallow);
|
||||
const { moduleId } = useModuleContext();
|
||||
const componentDefinition = useStore((state) => state.getComponentDefinition(id, moduleId), shallow);
|
||||
const getDefaultStyles = useStore((state) => state.debugger.getDefaultStyles, shallow);
|
||||
const component = componentDefinition?.component;
|
||||
const componentName = component?.name;
|
||||
const [key, setKey] = useState(Math.random());
|
||||
const resolvedProperties = useStore(
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex)?.properties,
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.properties,
|
||||
shallow
|
||||
);
|
||||
const resolvedStyles = useStore(
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.styles,
|
||||
shallow
|
||||
);
|
||||
const resolvedStyles = useStore((state) => state.getResolvedComponent(id, subContainerIndex)?.styles, shallow);
|
||||
const fireEvent = useStore((state) => state.eventsSlice.fireEvent, shallow);
|
||||
const resolvedGeneralProperties = useStore(
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex)?.general,
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.general,
|
||||
shallow
|
||||
);
|
||||
const resolvedGeneralStyles = useStore(
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex)?.generalStyles,
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.generalStyles,
|
||||
shallow
|
||||
);
|
||||
const unResolvedValidation = componentDefinition?.component?.definition?.validation || {};
|
||||
|
|
@ -73,10 +78,13 @@ const RenderWidget = ({
|
|||
const setExposedValue = useStore((state) => state.setExposedValue, shallow);
|
||||
const setExposedValues = useStore((state) => state.setExposedValues, shallow);
|
||||
const setDefaultExposedValues = useStore((state) => state.setDefaultExposedValues, shallow);
|
||||
const resolvedValidation = useStore((state) => state.getResolvedComponent(id)?.validation, shallow);
|
||||
const resolvedValidation = useStore(
|
||||
(state) => state.getResolvedComponent(id, subContainerIndex, moduleId)?.validation,
|
||||
shallow
|
||||
);
|
||||
const parentId = component?.parent;
|
||||
const customResolvables = useStore(
|
||||
(state) => state.resolvedStore.modules.canvas?.customResolvables?.[parentId],
|
||||
(state) => state.resolvedStore.modules[moduleId]?.customResolvables?.[parentId],
|
||||
shallow
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -110,31 +118,31 @@ const RenderWidget = ({
|
|||
(key, value) => {
|
||||
// Check if the component is inside the subcontainer and it has its own onOptionChange(setExposedValue) function
|
||||
if (onOptionChange === null) {
|
||||
setExposedValue(id, key, value);
|
||||
setExposedValue(id, key, value, moduleId);
|
||||
// Trigger an update when the child components is directly linked to any component
|
||||
updateDependencyValues(`components.${id}.${key}`);
|
||||
updateDependencyValues(`components.${id}.${key}`, moduleId);
|
||||
} else {
|
||||
onOptionChange(key, value, id, subContainerIndex);
|
||||
}
|
||||
},
|
||||
[id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange]
|
||||
[id, setExposedValue, updateDependencyValues, subContainerIndex, onOptionChange, moduleId]
|
||||
);
|
||||
const setExposedVariables = useCallback(
|
||||
(exposedValues) => {
|
||||
if (onOptionsChange === null) {
|
||||
setExposedValues(id, 'components', exposedValues);
|
||||
setExposedValues(id, 'components', exposedValues, moduleId);
|
||||
} else {
|
||||
onOptionsChange(exposedValues, id, subContainerIndex);
|
||||
}
|
||||
},
|
||||
[id, setExposedValues, onOptionsChange]
|
||||
[id, setExposedValues, onOptionsChange, moduleId]
|
||||
);
|
||||
const fireEventWrapper = useCallback(
|
||||
(eventName, options) => {
|
||||
fireEvent(eventName, id, 'canvas', customResolvables?.[subContainerIndex] ?? {}, options);
|
||||
fireEvent(eventName, id, moduleId, customResolvables?.[subContainerIndex] ?? {}, options);
|
||||
return Promise.resolve();
|
||||
},
|
||||
[fireEvent, id, customResolvables, subContainerIndex]
|
||||
[fireEvent, id, customResolvables, subContainerIndex, moduleId]
|
||||
);
|
||||
|
||||
const onComponentClick = useStore((state) => state.eventsSlice.onComponentClickEvent);
|
||||
|
|
@ -155,17 +163,18 @@ const RenderWidget = ({
|
|||
? null
|
||||
: ['hover', 'focus']
|
||||
: !resolvedGeneralProperties?.tooltip?.toString().trim()
|
||||
? null
|
||||
: ['hover', 'focus']
|
||||
? null
|
||||
: ['hover', 'focus']
|
||||
}
|
||||
overlay={(props) =>
|
||||
renderTooltip({
|
||||
props,
|
||||
text: inCanvas
|
||||
? `${SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component)
|
||||
? resolvedProperties?.tooltip
|
||||
: resolvedGeneralProperties?.tooltip
|
||||
}`
|
||||
? `${
|
||||
SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component)
|
||||
? resolvedProperties?.tooltip
|
||||
: resolvedGeneralProperties?.tooltip
|
||||
}`
|
||||
: `${t(`widget.${component?.name}.description`, component?.description)}`,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import './selecto.scss';
|
|||
import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { findHighestLevelofSelection } from './Grid/gridUtils';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
export const EditorSelecto = () => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab);
|
||||
const setSelectedComponents = useStore((state) => state.setSelectedComponents);
|
||||
const getSelectedComponents = useStore((state) => state.getSelectedComponents, shallow);
|
||||
|
|
@ -16,7 +18,7 @@ export const EditorSelecto = () => {
|
|||
const filterSelectedComponentsByHighestLevel = (selectedIds) => {
|
||||
const highestLevelComponents = findHighestLevelofSelection(
|
||||
selectedIds.map((id) => {
|
||||
const component = getComponentDefinition(id);
|
||||
const component = getComponentDefinition(id, moduleId);
|
||||
return {
|
||||
...component,
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { ConfigHandle } from './ConfigHandle/ConfigHandle';
|
|||
import { useGridStore } from '@/_stores/gridStore';
|
||||
import cx from 'classnames';
|
||||
import RenderWidget from './RenderWidget';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { NO_OF_GRIDS } from './appCanvasConstants';
|
||||
|
||||
const WidgetWrapper = memo(
|
||||
({
|
||||
|
|
@ -20,24 +22,31 @@ const WidgetWrapper = memo(
|
|||
mode,
|
||||
darkMode,
|
||||
}) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const calculateMoveableBoxHeightWithId = useStore((state) => state.calculateMoveableBoxHeightWithId, shallow);
|
||||
const stylesDefinition = useStore(
|
||||
(state) => state.getComponentDefinition(id)?.component?.definition?.styles,
|
||||
(state) => state.getComponentDefinition(id, moduleId)?.component?.definition?.styles,
|
||||
shallow
|
||||
);
|
||||
const layoutData = useStore(
|
||||
(state) => state.getComponentDefinition(id, moduleId)?.layouts?.[currentLayout],
|
||||
shallow
|
||||
);
|
||||
const layoutData = useStore((state) => state.getComponentDefinition(id)?.layouts?.[currentLayout], shallow);
|
||||
const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow);
|
||||
const isDragging = useStore((state) => state.draggingComponentId === id);
|
||||
const isResizing = useGridStore((state) => state.resizingComponentId === id);
|
||||
const componentType = useStore((state) => state.getComponentDefinition(id)?.component?.component, shallow);
|
||||
const componentType = useStore(
|
||||
(state) => state.getComponentDefinition(id, moduleId)?.component?.component,
|
||||
shallow
|
||||
);
|
||||
const setHoveredComponentForGrid = useStore((state) => state.setHoveredComponentForGrid, shallow);
|
||||
const canShowInCurrentLayout = useStore((state) => {
|
||||
const others = state.getResolvedComponent(id, subContainerIndex)?.others;
|
||||
const others = state.getResolvedComponent(id, subContainerIndex, moduleId)?.others;
|
||||
return others?.[currentLayout === 'mobile' ? 'showOnMobile' : 'showOnDesktop'];
|
||||
});
|
||||
const visibility = useStore((state) => {
|
||||
const component = state.getResolvedComponent(id, subContainerIndex);
|
||||
const componentExposedVisibility = state.getExposedValueOfComponent(id)?.isVisible;
|
||||
const component = state.getResolvedComponent(id, subContainerIndex, moduleId);
|
||||
const componentExposedVisibility = state.getExposedValueOfComponent(id, moduleId)?.isVisible;
|
||||
if (componentExposedVisibility === false) return false;
|
||||
if (component?.properties?.visibility === false || component?.styles?.visibility === false) return false;
|
||||
return true;
|
||||
|
|
@ -47,16 +56,24 @@ const WidgetWrapper = memo(
|
|||
return null;
|
||||
}
|
||||
|
||||
const width = gridWidth * layoutData?.width;
|
||||
let newLayoutData = layoutData;
|
||||
|
||||
if (componentType === 'ModuleContainer' && mode === 'view') {
|
||||
newLayoutData = { ...layoutData, top: 0, left: 0, width: NO_OF_GRIDS };
|
||||
}
|
||||
|
||||
const width = gridWidth * newLayoutData?.width;
|
||||
const height = calculateMoveableBoxHeightWithId(id, currentLayout, stylesDefinition);
|
||||
const styles = {
|
||||
width: width + 'px',
|
||||
height: visibility === false ? '10px' : `${height}px`,
|
||||
transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`,
|
||||
transform: `translate(${newLayoutData.left * gridWidth}px, ${newLayoutData.top}px)`,
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
border: visibility === false && mode === 'edit' ? `1px solid var(--border-default)` : 'none',
|
||||
};
|
||||
|
||||
const isModuleContainer = componentType === 'ModuleContainer';
|
||||
|
||||
if (!componentType) return null;
|
||||
return (
|
||||
<>
|
||||
|
|
@ -67,6 +84,7 @@ const WidgetWrapper = memo(
|
|||
'position-absolute': readOnly,
|
||||
'active-target': isWidgetActive,
|
||||
'opacity-0': isDragging || isResizing,
|
||||
'module-container': isModuleContainer,
|
||||
})}
|
||||
data-id={`${id}`}
|
||||
id={id}
|
||||
|
|
@ -76,30 +94,32 @@ const WidgetWrapper = memo(
|
|||
// zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null,
|
||||
...styles,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (isDragging) return;
|
||||
onMouseEnter={() => {
|
||||
if (isDragging || isModuleContainer) return;
|
||||
setHoveredComponentForGrid(id);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (isDragging) return;
|
||||
if (isDragging || isModuleContainer) return;
|
||||
setHoveredComponentForGrid('');
|
||||
}}
|
||||
>
|
||||
{mode == 'edit' && (
|
||||
<ConfigHandle
|
||||
id={id}
|
||||
widgetTop={layoutData.top}
|
||||
widgetHeight={layoutData.height}
|
||||
widgetTop={newLayoutData.top}
|
||||
widgetHeight={newLayoutData.height}
|
||||
showHandle={isWidgetActive}
|
||||
componentType={componentType}
|
||||
visibility={visibility}
|
||||
customClassName={isModuleContainer ? 'module-container' : ''}
|
||||
isModuleContainer={isModuleContainer}
|
||||
subContainerIndex={subContainerIndex}
|
||||
/>
|
||||
)}
|
||||
<RenderWidget
|
||||
id={id}
|
||||
componentType={componentType}
|
||||
widgetHeight={layoutData.height}
|
||||
widgetHeight={newLayoutData.height}
|
||||
widgetWidth={width}
|
||||
inCanvas={inCanvas}
|
||||
subContainerIndex={subContainerIndex}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,18 @@ export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1;
|
|||
|
||||
export const BOX_PADDING = 2;
|
||||
|
||||
export const DROPPABLE_PARENTS = new Set([
|
||||
'Calendar',
|
||||
'Kanban',
|
||||
'Form',
|
||||
'Tabs',
|
||||
'Modal',
|
||||
'ModalV2',
|
||||
'Listview',
|
||||
'Container',
|
||||
'Table',
|
||||
'ModuleContainer',
|
||||
]);
|
||||
export const TAB_CANVAS_PADDING = 7.5;
|
||||
|
||||
export const MODAL_CANVAS_PADDING = 5;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,14 @@ export function snapToGrid(canvasWidth, x, y) {
|
|||
}
|
||||
|
||||
//TODO: componentTypes should be a key value pair and get the definition directly by passing the componentType
|
||||
export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, currentLayout, realCanvasRef, parentId) => {
|
||||
export const addNewWidgetToTheEditor = (
|
||||
componentType,
|
||||
eventMonitorObject,
|
||||
currentLayout,
|
||||
realCanvasRef,
|
||||
parentId,
|
||||
moduleInfo = undefined
|
||||
) => {
|
||||
const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect();
|
||||
const componentMeta = componentTypes.find((component) => component.component === componentType);
|
||||
const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents());
|
||||
|
|
@ -51,6 +58,24 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre
|
|||
const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas'];
|
||||
let width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth);
|
||||
|
||||
let customLayouts = undefined;
|
||||
|
||||
if (moduleInfo) {
|
||||
componentData.definition.properties.moduleAppId = { value: moduleInfo.moduleId };
|
||||
componentData.definition.properties.moduleVersionId = { value: moduleInfo.versionId };
|
||||
componentData.definition.properties.moduleEnvironmentId = { value: moduleInfo.environmentId };
|
||||
componentData.definition.properties.visibility = { value: true };
|
||||
customLayouts = moduleInfo.moduleContainer.layouts;
|
||||
|
||||
const inputItems = Object.values(
|
||||
moduleInfo.moduleContainer.component.definition.properties?.input_items?.value ?? {}
|
||||
);
|
||||
|
||||
for (const { name, default_value } of inputItems) {
|
||||
componentData.definition.properties[name] = { value: default_value };
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure minimum width
|
||||
width = Math.max(width, 1);
|
||||
|
||||
|
|
@ -76,14 +101,14 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre
|
|||
[currentLayout]: {
|
||||
top: top,
|
||||
left: left,
|
||||
width,
|
||||
height: defaultHeight,
|
||||
width: customLayouts ? customLayouts[currentLayout].width : width,
|
||||
height: customLayouts ? customLayouts[currentLayout].height : defaultHeight,
|
||||
},
|
||||
[nonActiveLayout]: {
|
||||
top: top,
|
||||
left: left,
|
||||
width,
|
||||
height: defaultHeight,
|
||||
width: customLayouts ? customLayouts[nonActiveLayout].width : width,
|
||||
height: customLayouts ? customLayouts[nonActiveLayout].height : defaultHeight,
|
||||
},
|
||||
},
|
||||
withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component),
|
||||
|
|
@ -170,6 +195,7 @@ export function addChildrenWidgetsToParent(componentType, parentId, currentLayou
|
|||
component: {
|
||||
...componentData,
|
||||
parent: _parent,
|
||||
name: widgetName,
|
||||
},
|
||||
layouts: {
|
||||
[currentLayout]: {
|
||||
|
|
@ -682,10 +708,14 @@ export function pasteComponents(targetParentId, copiedComponentObj) {
|
|||
toast.success(`Component${filteredComponentsCount > 1 ? 's' : ''} pasted successfully`);
|
||||
}
|
||||
|
||||
export const getCanvasWidth = (currentLayout) => {
|
||||
if (currentLayout === 'mobile') {
|
||||
return CANVAS_WIDTHS.deviceWindowWidth;
|
||||
export const getCanvasWidth = (moduleId = 'canvas') => {
|
||||
if (moduleId !== 'canvas') {
|
||||
return '100%';
|
||||
}
|
||||
|
||||
// if (currentLayout === 'mobile') {
|
||||
// return CANVAS_WIDTHS.deviceWindowWidth;
|
||||
// }
|
||||
const windowWidth = window.innerWidth;
|
||||
const widthInPx = windowWidth - (CANVAS_WIDTHS.leftSideBarWidth + CANVAS_WIDTHS.rightSideBarWidth);
|
||||
const canvasMaxWidth = useStore.getState().globalSettings.canvasMaxWidth;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
.active-target {
|
||||
outline: 1px solid #4af !important;
|
||||
}
|
||||
}
|
||||
|
||||
.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover {
|
||||
.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover {
|
||||
outline: 1px solid #4af;
|
||||
z-index: 4 !important;
|
||||
}
|
||||
|
||||
.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover {
|
||||
.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover {
|
||||
// outline: 1px solid #4af;
|
||||
z-index: 4 !important;
|
||||
}
|
||||
|
||||
.main-editor-canvas .widget-target.module-container {
|
||||
outline: dotted 2px #CCD1D5 !important;
|
||||
}
|
||||
|
||||
// .main-editor-canvas .widget-target:hover {
|
||||
// outline: 1px solid #4af;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// }
|
||||
|
|
@ -3,11 +3,13 @@ import { isEmpty } from 'lodash';
|
|||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { LEFT_SIDEBAR_WIDTH } from './appCanvasConstants';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const useSidebarMargin = (canvasContainerRef) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [editorMarginLeft, setEditorMarginLeft] = useState(0);
|
||||
const isSidebarOpen = useStore((state) => state.isSidebarOpen, shallow);
|
||||
const mode = useStore((state) => state.currentMode, shallow);
|
||||
const mode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== 'view') setEditorMarginLeft(isSidebarOpen ? LEFT_SIDEBAR_WIDTH : 0);
|
||||
|
|
|
|||
100
frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx
Normal file
100
frontend/src/AppBuilder/CodeBuilder/Elements/Query.jsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import SelectComponent from '@/_ui/Select';
|
||||
import { components } from 'react-select';
|
||||
import Check from '@/_ui/Icon/solidIcons/Check';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
|
||||
const Option = (props) => {
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
<div className="d-flex justify-content-between">
|
||||
<span>{props.label}</span>
|
||||
{props.isSelected && (
|
||||
<span>
|
||||
<Check width={'20'} fill={'#3E63DD'} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const selectCustomStyles = {
|
||||
control: (base, state) => {
|
||||
return {
|
||||
...base,
|
||||
border: state.isFocused ? '1px solid #3E63DD' : '1px solid #cccccc',
|
||||
boxShadow: state.isFocused ? '0px 0px 6px #3E63DD' : 'none',
|
||||
backgroundColor: state.isFocused ? 'var(--indigo2)' : 'var(--base)',
|
||||
'&:hover': {
|
||||
border: '1px solid #3E63DD !important',
|
||||
boxShadow: '0px 0px 6px #3E63DD',
|
||||
},
|
||||
borderRadius: '6px',
|
||||
width: '144px',
|
||||
minHeight: '32px',
|
||||
};
|
||||
},
|
||||
|
||||
dropdownIndicator: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
menuList: (base) => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isFocused ? '#F0F4FF !important' : 'white',
|
||||
color: '#11181C',
|
||||
borderRadius: '6px',
|
||||
}),
|
||||
};
|
||||
|
||||
export const Query = ({ value, onChange, meta }) => {
|
||||
const dataQueries = useStore((state) => state.dataQuery.getCurrentModuleQueries('canvas'));
|
||||
const options = dataQueries
|
||||
.filter((query) => !(meta?.skipKinds ?? []).includes(query.kind))
|
||||
.map((query) => ({ name: query.name, value: query.id }));
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(value) => {
|
||||
console.log('value--- ', value, options);
|
||||
onChange(value);
|
||||
},
|
||||
[onChange, options]
|
||||
);
|
||||
|
||||
// const cleanedValue = useMemo(() => {
|
||||
// if (initialValue) {
|
||||
// return initialValue.replace('{{queries.', '').replace('}}', '');
|
||||
// }
|
||||
// return '';
|
||||
// }, [initialValue]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="row fx-container"
|
||||
data-cy={`dropdown-${meta.displayName ? String(meta.displayName).toLowerCase().replace(/\s+/g, '-') : 'common'}`}
|
||||
>
|
||||
<div className="field" onClick={(e) => e.stopPropagation()}>
|
||||
<SelectComponent
|
||||
options={options}
|
||||
value={value}
|
||||
hasSearch={true}
|
||||
onChange={onValueChange}
|
||||
width={224}
|
||||
height={32}
|
||||
styles={selectCustomStyles}
|
||||
useCustomStyles={true}
|
||||
classNamePrefix="inspector-select"
|
||||
components={{
|
||||
IndicatorSeparator: () => null,
|
||||
Option,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -20,4 +20,5 @@ export const TypeMapping = {
|
|||
visibility: 'Visibility',
|
||||
numberInput: 'NumberInput',
|
||||
tableRowHeightInput: 'TableRowHeightInput',
|
||||
query: 'Query',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput';
|
|||
import { Datepicker } from '../CodeBuilder/Elements/Datepicker';
|
||||
import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput';
|
||||
import { TimePicker } from '../CodeBuilder/Elements/TimePicker';
|
||||
import { Query } from '../CodeBuilder/Elements/Query';
|
||||
import { ColorSwatches } from '@/modules/Appbuilder/components';
|
||||
|
||||
const AllElements = {
|
||||
|
|
@ -40,6 +41,7 @@ const AllElements = {
|
|||
TableRowHeightInput,
|
||||
Datepicker,
|
||||
TimePicker,
|
||||
Query,
|
||||
};
|
||||
|
||||
export const DynamicFxTypeRenderer = ({ paramType, ...restProps }) => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { reservedKeywordReplacer } from '@/_lib/reserved-keyword-replacer';
|
|||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Overlay } from 'react-bootstrap';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const sanitizeLargeDataset = (data, callback) => {
|
||||
const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes
|
||||
|
|
@ -90,11 +91,12 @@ export const PreviewBox = ({
|
|||
isWorkspaceVariable,
|
||||
validationFn,
|
||||
}) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [resolvedValue, setResolvedValue] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
const [coersionData, setCoersionData] = useState(null);
|
||||
const [largeDataset, setLargeDataset] = useState(false);
|
||||
const globals = useStore((state) => state.getAllExposedValues().constants || {}, shallow);
|
||||
const globals = useStore((state) => state.getAllExposedValues(moduleId).constants || {}, shallow);
|
||||
const secrets = useStore((state) => state.getSecrets(), shallow);
|
||||
const globalServerConstantsRegex = /^\{\{.*globals\.server.*\}\}$/;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,13 @@ import CodeHinter from './CodeHinter';
|
|||
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
|
||||
import { createReferencesLookup } from '@/_stores/utils';
|
||||
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
|
||||
|
||||
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const { initialValue, onChange, enablePreview = true, portalProps } = restProps;
|
||||
const { validation = {} } = fieldMeta;
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
|
|
@ -42,7 +44,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
|
|||
const [cursorInsidePreview, setCursorInsidePreview] = useState(false);
|
||||
const [showSuggestions, setShowSuggestions] = useState(true);
|
||||
const validationFn = restProps?.validationFn;
|
||||
const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow);
|
||||
const componentDefinition = useStore((state) => state.getComponentDefinition(componentId, moduleId), shallow);
|
||||
const parentId = componentDefinition?.component?.parent;
|
||||
const customResolvables = useStore((state) => state.resolvedStore.modules.canvas?.customResolvables, shallow);
|
||||
|
||||
|
|
@ -522,6 +524,32 @@ const DynamicEditorBridge = (props) => {
|
|||
setForceCodeBox(fxActive);
|
||||
}, [component, fxActive]);
|
||||
|
||||
const renderFx = () => {
|
||||
if (paramType === 'query' || !(paramLabel !== 'Type' && isFxNotRequired === undefined)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`col-auto pt-0 fx-common fx-button-container ${(isEventManagerParam || codeShow) && 'show-fx-button-container'
|
||||
}`}
|
||||
>
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
onPress={() => {
|
||||
if (codeShow) {
|
||||
setForceCodeBox(false);
|
||||
onFxPress(false);
|
||||
} else {
|
||||
setForceCodeBox(true);
|
||||
onFxPress(true);
|
||||
}
|
||||
}}
|
||||
dataCy={cyLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const fxClass = isEventManagerParam ? 'justify-content-start' : 'justify-content-end';
|
||||
return (
|
||||
<div className={cx({ 'codeShow-active': codeShow }, 'wrapper-div-code-editor')}>
|
||||
|
|
@ -538,26 +566,7 @@ const DynamicEditorBridge = (props) => {
|
|||
)}
|
||||
<div className={`${(paramType ?? 'code') === 'code' ? 'd-none' : ''} flex-grow-1`}>
|
||||
<div style={{ marginBottom: codeShow ? '0.5rem' : '0px' }} className={`d-flex align-items-center ${fxClass}`}>
|
||||
{paramLabel !== 'Type' && isFxNotRequired === undefined && (
|
||||
<div
|
||||
className={`col-auto pt-0 fx-common fx-button-container ${(isEventManagerParam || codeShow) && 'show-fx-button-container'
|
||||
}`}
|
||||
>
|
||||
<FxButton
|
||||
active={codeShow}
|
||||
onPress={() => {
|
||||
if (codeShow) {
|
||||
setForceCodeBox(false);
|
||||
onFxPress(false);
|
||||
} else {
|
||||
setForceCodeBox(true);
|
||||
onFxPress(true);
|
||||
}
|
||||
}}
|
||||
dataCy={cyLabel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{renderFx()}
|
||||
</div>
|
||||
</div>
|
||||
{!codeShow && (
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor
|
|||
'queries',
|
||||
'globals',
|
||||
'page',
|
||||
'input',
|
||||
'constants',
|
||||
'moment',
|
||||
'_',
|
||||
|
|
@ -168,6 +169,7 @@ function resolveCode(code, customObjects = {}, withError = false, reservedKeywor
|
|||
isJsCode ? state?.queries : undefined,
|
||||
isJsCode ? state?.globals : undefined,
|
||||
isJsCode ? state?.page : undefined,
|
||||
isJsCode ? state?.input : undefined,
|
||||
state?.constants, // Passing constants as an argument allows the evaluated code to access and utilize the constants value correctly.
|
||||
moment,
|
||||
_,
|
||||
|
|
@ -365,6 +367,7 @@ export const FxParamTypeMapping = Object.freeze({
|
|||
visibility: 'Visibility',
|
||||
numberInput: 'NumberInput',
|
||||
tableRowHeightInput: 'TableRowHeightInput',
|
||||
query: 'Query',
|
||||
});
|
||||
|
||||
export function computeCoercion(oldValue, newValue) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { shallow } from 'zustand/shallow';
|
|||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const appVersionLoadingStatus = Object.freeze({
|
||||
loading: 'loading',
|
||||
|
|
@ -14,6 +15,7 @@ const appVersionLoadingStatus = Object.freeze({
|
|||
});
|
||||
|
||||
export const AppVersionsManager = function ({ darkMode }) {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [appVersionStatus, setGetAppVersionStatus] = useState(appVersionLoadingStatus.loading);
|
||||
|
||||
const [deleteVersion, setDeleteVersion] = useState({
|
||||
|
|
@ -54,15 +56,15 @@ export const AppVersionsManager = function ({ darkMode }) {
|
|||
deleteVersionAction: state.deleteVersionAction,
|
||||
selectedEnvironment: state.selectedEnvironment,
|
||||
currentLayout: state.currentLayout,
|
||||
appId: state.app.appId,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
releasedVersionId: state.releasedVersionId,
|
||||
editingVersion: state.editingVersion,
|
||||
setCurrentVersionId: state.setCurrentVersionId,
|
||||
currentVersionId: state.currentVersionId,
|
||||
creationMode: state.app.creationMode,
|
||||
isPublic: state.app.isPublic,
|
||||
isViewer: state.isViewer,
|
||||
currentMode: state.currentMode,
|
||||
creationMode: state.appStore.modules[moduleId].app.creationMode,
|
||||
isPublic: state.appStore.modules[moduleId].app.isPublic,
|
||||
isViewer: state.appStore.modules[moduleId].isViewer,
|
||||
currentMode: state.modeStore.modules[moduleId].currentMode,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import Select from '@/_ui/Select';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const CreateVersionModal = ({
|
||||
showCreateAppVersion,
|
||||
|
|
@ -17,6 +18,7 @@ const CreateVersionModal = ({
|
|||
fetchingOrgGit,
|
||||
handleCommitOnVersionCreation = () => {},
|
||||
}) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
|
||||
const [versionName, setVersionName] = useState('');
|
||||
const gitSyncEnabled =
|
||||
|
|
@ -39,7 +41,7 @@ const CreateVersionModal = ({
|
|||
developmentVersions: state.developmentVersions,
|
||||
featureAccess: state.license.featureAccess,
|
||||
editingVersion: state.currentVersionId,
|
||||
appId: state.app.appId,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
currentVersionId: state.currentVersionId,
|
||||
setCurrentVersionId: state.setCurrentVersionId,
|
||||
selectedVersion: state.selectedVersion,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,17 @@ import { InfoOrErrorBox } from './InfoOrErrorBox';
|
|||
import { toast } from 'react-hot-toast';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
function EditAppName() {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [appId, appName, setAppName, appCreationMode] = useStore(
|
||||
(state) => [state.app.appId, state.app.appName, state.setAppName, state.app.creationMode],
|
||||
(state) => [
|
||||
state.appStore.modules[moduleId].app.appId,
|
||||
state.appStore.modules[moduleId].app.appName,
|
||||
state.setAppName,
|
||||
state.appStore.modules[moduleId].app.creationMode,
|
||||
],
|
||||
shallow
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import { toast } from 'react-hot-toast';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [isEditingVersion, setIsEditingVersion] = useState(false);
|
||||
const {
|
||||
updateVersionNameAction,
|
||||
|
|
@ -15,7 +17,7 @@ export const EditVersionModal = ({ setShowEditAppVersion, showEditAppVersion })
|
|||
(state) => ({
|
||||
updateVersionNameAction: state.updateVersionNameAction,
|
||||
selectedVersion: state.selectedVersion,
|
||||
appId: state.app.appId,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ import RightTopHeaderButtons from './RightTopHeaderButtons/RightTopHeaderButtons
|
|||
import BuildSuggestions from './BuildSuggestions';
|
||||
import GitSyncManager from './GitSyncManager';
|
||||
import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer';
|
||||
import { ModuleEditorBanner } from '@/modules/Modules/components';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
export const EditorHeader = ({ darkMode }) => {
|
||||
const { moduleId, isModuleEditor } = useModuleContext();
|
||||
const { isSaving, saveError, isVersionReleased } = useStore(
|
||||
(state) => ({
|
||||
isSaving: state.app.isSaving,
|
||||
saveError: state.app.saveError,
|
||||
isSaving: state.appStore.modules[moduleId].app.isSaving,
|
||||
saveError: state.appStore.modules[moduleId].app.saveError,
|
||||
isVersionReleased: state.isVersionReleased,
|
||||
}),
|
||||
shallow
|
||||
|
|
@ -72,7 +75,10 @@ export const EditorHeader = ({ darkMode }) => {
|
|||
}}
|
||||
>
|
||||
<div className="global-settings-app-wrapper p-0 m-0 ">
|
||||
<EditAppName />
|
||||
<div className="d-flex flex-row">
|
||||
{isModuleEditor && <ModuleEditorBanner />}
|
||||
<EditAppName />
|
||||
</div>
|
||||
</div>
|
||||
<HeaderActions darkMode={darkMode} />
|
||||
<div className="d-flex align-items-center">
|
||||
|
|
@ -96,20 +102,21 @@ export const EditorHeader = ({ darkMode }) => {
|
|||
{shouldEnableMultiplayer && <UpdatePresenceMultiPlayer />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="navbar-seperator"></div>
|
||||
{/* <div className="d-flex align-items-center p-0" style={{ marginRight: '12px' }}></div> */}
|
||||
{!isModuleEditor && <div className="navbar-seperator"></div>}
|
||||
</div>
|
||||
<div className="d-flex align-items-center p-0">
|
||||
<div className="d-flex version-manager-container p-0 mx-2 align-items-center ">
|
||||
{/* {editingVersion && ( */}
|
||||
<AppEnvironments darkMode={darkMode} />
|
||||
{/* )} */}
|
||||
<AppVersionsManager darkMode={darkMode} />
|
||||
<GitSyncManager />
|
||||
{!isModuleEditor && (
|
||||
<>
|
||||
<AppEnvironments darkMode={darkMode} />
|
||||
<AppVersionsManager darkMode={darkMode} />
|
||||
<GitSyncManager />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RightTopHeaderButtons />
|
||||
<RightTopHeaderButtons isModuleEditor={isModuleEditor} />
|
||||
<BuildSuggestions />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import { shallow } from 'zustand/shallow';
|
|||
import '@/_styles/versions.scss';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const ReleaseVersionButton = function DeployVersionButton() {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [isReleasing, setIsReleasing] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
const { isVersionReleased, editingVersion, updateReleasedVersionId, appId, versionToBeReleased, name } = useStore(
|
||||
|
|
@ -19,7 +21,7 @@ const ReleaseVersionButton = function DeployVersionButton() {
|
|||
editingVersion: state.editingVersion,
|
||||
isEditorFreezed: state.isEditorFreezed,
|
||||
updateReleasedVersionId: state.updateReleasedVersionId,
|
||||
appId: state.app.appId,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
versionToBeReleased: state.currentVersionId,
|
||||
// selectedVersionId: state.selectedVersion.id,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -8,19 +8,21 @@ import { isEmpty } from 'lodash';
|
|||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { PromoteReleaseButton } from '@/modules/Appbuilder/components';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const RightTopHeaderButtons = () => {
|
||||
const RightTopHeaderButtons = ({ isModuleEditor }) => {
|
||||
return (
|
||||
<div className="d-flex justify-content-end navbar-right-section" style={{ width: '300px', paddingRight: '12px' }}>
|
||||
<div className=" release-buttons navbar-nav flex-row">
|
||||
<PreviewAndShareIcons />
|
||||
<PromoteReleaseButton />
|
||||
{!isModuleEditor && <PromoteReleaseButton />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PreviewAndShareIcons = () => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const {
|
||||
featureAccess,
|
||||
currentPageHandle,
|
||||
|
|
@ -36,14 +38,14 @@ const PreviewAndShareIcons = () => {
|
|||
} = useStore(
|
||||
(state) => ({
|
||||
featureAccess: state.license?.featureAccess,
|
||||
currentPageHandle: state?.currentPageHandle,
|
||||
currentPageHandle: state?.modules[moduleId].currentPageHandle,
|
||||
selectedEnvironment: state.selectedEnvironment,
|
||||
isVersionReleased: state.releasedVersionId === state.selectedVersion?.id,
|
||||
editingVersion: state.editingVersion,
|
||||
appId: state.app.appId,
|
||||
app: state.app.app,
|
||||
slug: state.app.slug,
|
||||
isPublic: state.app.isPublic,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
app: state.appStore.modules[moduleId].app.app,
|
||||
slug: state.appStore.modules[moduleId].app.slug,
|
||||
isPublic: state.appStore.modules[moduleId].app.isPublic,
|
||||
currentVersionId: state.currentVersionId,
|
||||
selectedVersion: state.selectedVersion,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ function Debugger({ pinned, setPinned }) {
|
|||
shallow
|
||||
);
|
||||
|
||||
const currentPageId = useStore((state) => state.currentPageId);
|
||||
const currentPageId = useStore((state) => state.modules.canvas.currentPageId);
|
||||
|
||||
const logsToBeShown = logs.filter((log) => log.page === currentPageId);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import { Button } from '@/components/ui/Button/Button';
|
|||
import ExportAppModal from '@/HomePage/ExportAppModal';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import cx from 'classnames';
|
||||
|
||||
const AppExport = ({ darkMode }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const { app } = useStore(
|
||||
(state) => ({
|
||||
app: state.app,
|
||||
app: state.appStore.modules[moduleId].app,
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import FxButton from '@/Editor/CodeBuilder/Elements/FxButton';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Confirm } from '@/Editor/Viewer/Confirm';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const CanvasSettings = ({ darkMode }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const { globalSettings, globalSettingsChanged, resolveOthers, getCanvasBackgroundColor } = useStore(
|
||||
(state) => ({
|
||||
globalSettings: state.globalSettings,
|
||||
updateGlobalSettings: state.updateGlobalSettings,
|
||||
isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn,
|
||||
globalSettingsChanged: state.globalSettingsChanged,
|
||||
resolveOthers: state.resolveOthers,
|
||||
getCanvasBackgroundColor: state.getCanvasBackgroundColor,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import SwitchComponent from '@/components/ui/Switch/Index';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { Confirm } from '@/Editor/Viewer/Confirm';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const MaintenanceMode = ({ darkMode }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [showConfirmation, setConfirmationShow] = useState(false);
|
||||
const { isMaintenanceOn, toggleAppMaintenance } = useStore(
|
||||
(state) => ({
|
||||
isMaintenanceOn: state.app.isMaintenanceOn,
|
||||
isMaintenanceOn: state.appStore.modules[moduleId].app.isMaintenanceOn,
|
||||
toggleAppMaintenance: state.toggleAppMaintenance,
|
||||
}),
|
||||
shallow
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import { getHostURL, replaceEditorURL } from '@/_helpers/routes';
|
|||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
// import { useStore } from '@/store';
|
||||
|
||||
const SlugInput = () => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const {
|
||||
slug: oldSlug,
|
||||
appId,
|
||||
|
|
@ -20,11 +22,11 @@ const SlugInput = () => {
|
|||
} = useStore(
|
||||
(state) => ({
|
||||
globalSettings: state.globalSettings,
|
||||
slug: state.app.slug,
|
||||
appId: state.app.appId,
|
||||
app: state.app,
|
||||
slug: state.appStore.modules[moduleId].app.slug,
|
||||
appId: state.appStore.modules[moduleId].app.appId,
|
||||
app: state.appStore.modules[moduleId].app,
|
||||
setApp: state.setApp,
|
||||
currentPage: state.modules.canvas.pages[state.currentPageId],
|
||||
currentPage: state.modules[moduleId].pages[state.modules[moduleId].currentPageIndex],
|
||||
}),
|
||||
shallow
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import LeftSidebarInspector from './LeftSidebarInspector/LeftSidebarInspector';
|
|||
import GlobalSettings from './GlobalSettings';
|
||||
import '../../_styles/left-sidebar.scss';
|
||||
import Debugger from './Debugger/Debugger';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent';
|
||||
|
||||
// TODO: remove passing refs to LeftSidebarItem and use state
|
||||
|
|
@ -23,6 +24,7 @@ export const BaseLeftSidebar = ({
|
|||
renderAISideBarTrigger = () => null,
|
||||
renderAIChat = () => null,
|
||||
}) => {
|
||||
const { moduleId, isModuleEditor, appType } = useModuleContext();
|
||||
const [
|
||||
pinned,
|
||||
selectedSidebarItem,
|
||||
|
|
@ -41,7 +43,7 @@ export const BaseLeftSidebar = ({
|
|||
state.selectedSidebarItem,
|
||||
state.setIsLeftSideBarPinned,
|
||||
state.setSelectedSidebarItem,
|
||||
state.currentMode,
|
||||
state.modeStore.modules[moduleId].currentMode,
|
||||
state.queryPanel.queryPanelHeight,
|
||||
state.debugger.unreadErrorCount,
|
||||
state.debugger.resetUnreadErrorCount,
|
||||
|
|
@ -104,6 +106,8 @@ export const BaseLeftSidebar = ({
|
|||
// popoverContentHeight={popoverContentHeight}
|
||||
setPinned={setPinned}
|
||||
pinned={pinned}
|
||||
moduleId={moduleId}
|
||||
appType={appType}
|
||||
/>
|
||||
);
|
||||
case 'tooljetai':
|
||||
|
|
@ -148,6 +152,7 @@ export const BaseLeftSidebar = ({
|
|||
// globalSettingsChanged={globalSettingsChanged}
|
||||
// globalSettings={appDefinition.globalSettings}
|
||||
darkMode={darkMode}
|
||||
isModuleEditor={isModuleEditor}
|
||||
// toggleAppMaintenance={toggleAppMaintenance}
|
||||
// isMaintenanceOn={isMaintenanceOn}
|
||||
// app={app}
|
||||
|
|
@ -162,72 +167,79 @@ export const BaseLeftSidebar = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const renderCommonItems = () => {
|
||||
return (
|
||||
<>
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('inspect')}
|
||||
darkMode={darkMode}
|
||||
icon="inspect"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`}
|
||||
tip="Inspector"
|
||||
ref={setSideBarBtnRefs('inspect')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
icon="debugger"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('debugger')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
count={unreadErrorCount}
|
||||
tip="Debugger"
|
||||
ref={setSideBarBtnRefs('debugger')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLeftSidebarItems = () => {
|
||||
if (isModuleEditor) {
|
||||
return renderCommonItems();
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{renderAISideBarTrigger({
|
||||
selectedSidebarItem: selectedSidebarItem,
|
||||
onClick: () => handleSelectedSidebarItem('tooljetai'),
|
||||
darkMode: darkMode,
|
||||
icon: 'tooljetai',
|
||||
className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`,
|
||||
tip: 'Build with AI',
|
||||
ref: setSideBarBtnRefs('tooljetai'),
|
||||
})}
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('page')}
|
||||
darkMode={darkMode}
|
||||
icon="page"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`}
|
||||
tip="Pages"
|
||||
ref={setSideBarBtnRefs('page')}
|
||||
/>
|
||||
{renderCommonItems()}
|
||||
<SidebarItem
|
||||
icon="settings"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('settings')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
tip="Settings"
|
||||
ref={setSideBarBtnRefs('settings')}
|
||||
isModuleEditor={isModuleEditor}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cx('left-sidebar', { 'dark-theme theme-dark': darkMode })} data-cy="left-sidebar-inspector">
|
||||
{renderAISideBarTrigger({
|
||||
selectedSidebarItem: selectedSidebarItem,
|
||||
onClick: () => handleSelectedSidebarItem('tooljetai'),
|
||||
darkMode: darkMode,
|
||||
icon: 'tooljetai',
|
||||
className: `left-sidebar-item left-sidebar-layout left-sidebar-page-selector`,
|
||||
tip: 'Build with AI',
|
||||
ref: setSideBarBtnRefs('tooljetai'),
|
||||
})}
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('page')}
|
||||
darkMode={darkMode}
|
||||
icon="page"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-page-selector`}
|
||||
tip="Pages"
|
||||
ref={setSideBarBtnRefs('page')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('inspect')}
|
||||
darkMode={darkMode}
|
||||
icon="inspect"
|
||||
className={`left-sidebar-item left-sidebar-layout left-sidebar-inspector`}
|
||||
tip="Inspector"
|
||||
ref={setSideBarBtnRefs('inspect')}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
icon="debugger"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('debugger')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
count={unreadErrorCount}
|
||||
tip="Debugger"
|
||||
ref={setSideBarBtnRefs('debugger')}
|
||||
/>
|
||||
<SidebarItem
|
||||
icon="settings"
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
darkMode={darkMode}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onClick={(e) => handleSelectedSidebarItem('settings')}
|
||||
className={`left-sidebar-item left-sidebar-layout`}
|
||||
badge={true}
|
||||
tip="Settings"
|
||||
ref={setSideBarBtnRefs('settings')}
|
||||
/>
|
||||
|
||||
{/* {dataSources?.length > 0 && (
|
||||
<LeftSidebarItem
|
||||
selectedSidebarItem={selectedSidebarItem}
|
||||
onClick={() => handleSelectedSidebarItem('datasource')}
|
||||
icon="datasource"
|
||||
className={`left-sidebar-item left-sidebar-layout sidebar-datasources`}
|
||||
tip="Sources"
|
||||
ref={setSideBarBtnRefs('datasource')}
|
||||
/>
|
||||
)} */}
|
||||
|
||||
{renderLeftSidebarItems()}
|
||||
<Popover
|
||||
onInteractOutside={(e) => {
|
||||
// if tooljetai is open don't close
|
||||
|
|
@ -238,7 +250,7 @@ export const BaseLeftSidebar = ({
|
|||
toggleLeftSidebar(false);
|
||||
}}
|
||||
open={isSidebarOpen}
|
||||
popoverContentClassName={`p-0 sidebar-h-100-popover ${selectedSidebarItem}`}
|
||||
popoverContentClassName={`p-0 left-sidebar-scrollbar sidebar-h-100-popover ${selectedSidebarItem}`}
|
||||
side="right"
|
||||
popoverContent={renderPopoverContent()}
|
||||
popoverContentHeight={popoverContentHeight}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const ArrayNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#1F99ED' }}>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>{`[${value.length}]`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArrayNode;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const BooleanNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#9467BD' }}>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
{value.toString()}
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BooleanNode;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const FunctionNode = () => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#4368E3' }}>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
function
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FunctionNode;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const NullNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#ca3973' }}>
|
||||
<OverflowTooltip style={{ width: '100%' }}>{value === null ? 'null' : 'undefined'}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NullNode;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const NumberNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>
|
||||
{value}
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NumberNode;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
const ObjectNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#FF7F0E' }}>
|
||||
<OverflowTooltip maxLetters={32} style={{ width: '100%' }}>{`{${Object.keys(value).length}}`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ObjectNode;
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
import React, { useState } from 'react';
|
||||
import StringNode from './StringNode';
|
||||
import FunctionNode from './FunctionNode';
|
||||
import NumberNode from './NumberNode';
|
||||
import BooleanNode from './BooleanNode';
|
||||
import NullNode from './NullNode';
|
||||
import ArrayNode from './ArrayNode';
|
||||
import ObjectNode from './ObjectNode';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { DefaultCopyIcon } from '../../DefaultCopyIcon';
|
||||
import { copyToClipboard, extractComponentName } from '../../utils';
|
||||
import WidgetIcon from '@/../assets/images/icons/widgets';
|
||||
import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers';
|
||||
|
||||
const renderNodeIcons = (node, iconsList, darkMode) => {
|
||||
const icon = iconsList.filter((icon) => icon?.iconName === node)[0];
|
||||
|
||||
if (icon && icon.jsx) {
|
||||
if (icon?.tooltipMessage) {
|
||||
return (
|
||||
<ToolTip message={icon?.tooltipMessage}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>{icon.jsx({ height: 14, width: 14 })}</div>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
return icon.jsx({ height: 14, width: 14 });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const Row = ({ label, value, level = 1, absolutePath, iconsList, darkMode }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const Node = () => {
|
||||
if (typeof value === 'string') {
|
||||
return <StringNode value={value} />;
|
||||
} else if (typeof value === 'undefined' || value === null) {
|
||||
return <NullNode value={value} />;
|
||||
} else if (typeof value === 'number') {
|
||||
return <NumberNode value={value} />;
|
||||
} else if (typeof value === 'boolean') {
|
||||
return <BooleanNode value={value} />;
|
||||
} else if (Array.isArray(value)) {
|
||||
return <ArrayNode value={value} />;
|
||||
} else if (typeof value === 'object') {
|
||||
return <ObjectNode value={value} />;
|
||||
} else if (typeof value === 'function') {
|
||||
return <FunctionNode />;
|
||||
}
|
||||
};
|
||||
|
||||
const isObject = typeof value === 'object' && !Array.isArray(value) && value !== null;
|
||||
const isArray = Array.isArray(value);
|
||||
|
||||
return (
|
||||
<div style={{ marginLeft: `${level === 1 ? '0px' : '22px'}` }}>
|
||||
<div className="json-viewer-row-container">
|
||||
<div className="json-viewer-row" onClick={() => setIsExpanded((prev) => !prev)}>
|
||||
<div className="json-viewer-expand-icon">
|
||||
{(isArray || isObject) &&
|
||||
(isExpanded ? (
|
||||
<SolidIcon
|
||||
name="TriangleUpCenter"
|
||||
size={14}
|
||||
color="#1F99ED"
|
||||
style={{ marginRight: '4px' }}
|
||||
className="json-viewer-expand-icon"
|
||||
/>
|
||||
) : (
|
||||
<SolidIcon
|
||||
name="rightarrrow"
|
||||
size={12}
|
||||
color="#1F99ED"
|
||||
style={{ marginRight: '4px' }}
|
||||
className="json-viewer-expand-icon"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="json-viewer-label-container"
|
||||
data-cy={`inspector-${generateCypressDataCy(label || '')}-label`}
|
||||
>
|
||||
<OverflowTooltip style={{ width: '100%', display: 'flex', alignItems: 'center' }}>
|
||||
{renderNodeIcons(label, iconsList, darkMode)}
|
||||
{label}
|
||||
</OverflowTooltip>
|
||||
</div>
|
||||
<div
|
||||
className="json-viewer-value-container"
|
||||
data-cy={`inspector-${generateCypressDataCy(label || '')}-value`}
|
||||
>
|
||||
<Node />
|
||||
</div>
|
||||
<div className="json-viewer-actions-container">
|
||||
<ToolTip message={'Copy path'}>
|
||||
<span
|
||||
onClick={() => {
|
||||
copyToClipboard(`{{${absolutePath}}}`, false);
|
||||
}}
|
||||
className="copy-to-clipboard json-viewer-action-icon"
|
||||
>
|
||||
<DefaultCopyIcon height={12} width={12} />
|
||||
</span>
|
||||
</ToolTip>
|
||||
<ToolTip message={'Copy value'}>
|
||||
<span
|
||||
onClick={() => {
|
||||
copyToClipboard(value);
|
||||
}}
|
||||
className="json-viewer-action-icon"
|
||||
>
|
||||
<SolidIcon width="12" height="12" name="copy" fill="#6A727C" />
|
||||
</span>
|
||||
</ToolTip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded && isObject && (
|
||||
<div className="json-viewer-children" style={{ marginLeft: '5px', borderLeft: '1px solid var(--border-weak)' }}>
|
||||
{Object.entries(value).map(([key, val]) => (
|
||||
<Row
|
||||
key={key}
|
||||
label={key}
|
||||
value={val}
|
||||
level={level + 1}
|
||||
absolutePath={`${absolutePath}.${key}`}
|
||||
iconsList={iconsList}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{isExpanded && isArray && (
|
||||
<div className="json-viewer-children" style={{ marginLeft: '5px', borderLeft: '1px solid var(--border-weak)' }}>
|
||||
{value.map((item, index) => {
|
||||
return (
|
||||
<Row
|
||||
key={index}
|
||||
label={`${index}`}
|
||||
value={item}
|
||||
level={level + 1}
|
||||
absolutePath={`${absolutePath}.${index}`}
|
||||
iconsList={iconsList}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Row;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
|
||||
const StringNode = ({ value }) => {
|
||||
return (
|
||||
<div className="json-viewer-node-value" style={{ color: '#2CA02C' }}>
|
||||
<OverflowTooltip maxLetters={32}>{`"${value}"`}</OverflowTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StringNode;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import Row from './Components/Row';
|
||||
import './styles.scss';
|
||||
|
||||
const CustomJSONViewer = ({ data, absolutePath, iconsList }) => {
|
||||
let modifiedData = data;
|
||||
if (typeof data !== 'object') modifiedData = { '': data };
|
||||
return (
|
||||
<div className="custom-json-viewer">
|
||||
{Object.entries(modifiedData).map(([key, value], index) => {
|
||||
return (
|
||||
<Row key={index} label={key} value={value} absolutePath={`${absolutePath}.${key}`} iconsList={iconsList} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomJSONViewer;
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
.custom-json-viewer {
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
font-family: "IBM Plex Sans";
|
||||
font-size: 12px;
|
||||
color: var(--text-default, #1B1F24);
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
// Hide scrollbar for IE, Edge and Firefox
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
// Hide scrollbar for Chrome, Safari and Opera
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.json-viewer-row-container {
|
||||
min-width: max-content;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: var(--interactive-overlays-fill-hover);
|
||||
width: 100%;
|
||||
|
||||
.json-viewer-actions-container {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.json-viewer-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
.json-viewer-expand-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.json-viewer-label-container {
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0; /* don't shrink */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.json-viewer-value-container {
|
||||
flex: 1; /* take available space */
|
||||
min-width: 0; /* allow shrinkage */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 70px; /* Add padding to prevent text from going under actions */
|
||||
}
|
||||
|
||||
.json-viewer-actions-container {
|
||||
display: none;
|
||||
margin-left: auto;
|
||||
height: 12px;
|
||||
width: 40px;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
right: 16px; /* Align with the parent container's margin */
|
||||
background: var(--bg-default); /* Add background to ensure text doesn't show through */
|
||||
z-index: 1; /* Ensure actions stay on top */
|
||||
|
||||
.json-viewer-action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--button-outline-hover);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
|
||||
export const DefaultCopyIcon = ({ height = 12, width = 12, fill = '#6A727C' }) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill={fill}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-cy="copy-path-to-clipboard"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M19.3113 4.68871C18.5834 3.9609 17.4034 3.9609 16.6757 4.68871L15.8421 5.5223C15.4237 5.94071 14.7453 5.94071 14.3269 5.5223C13.9084 5.10389 13.9084 4.42549 14.3269 4.00707L15.1605 3.17348C16.7251 1.60884 19.2619 1.60884 20.8266 3.17348C22.3911 4.73811 22.3911 7.2749 20.8266 8.83954L19.9929 9.67313C19.5746 10.0916 18.8961 10.0916 18.4777 9.67313C18.0593 9.25471 18.0593 8.57633 18.4777 8.1579L19.3113 7.32431C20.0391 6.59651 20.0391 5.41651 19.3113 4.68871ZM17.406 6.59394C17.8244 7.01236 17.8244 7.69074 17.406 8.10917L15.1982 10.317C14.7798 10.7354 14.1014 10.7354 13.683 10.317C13.2645 9.89856 13.2645 9.22017 13.683 8.80174L15.8908 6.59394C16.3091 6.17551 16.9876 6.17551 17.406 6.59394ZM12.6651 7.184C13.0835 7.60241 13.0835 8.28081 12.6651 8.69923L11.8315 9.53283C11.1037 10.2606 11.1037 11.4406 11.8315 12.1684C12.5593 12.8962 13.7393 12.8962 14.4671 12.1684L15.3007 11.3348C15.7191 10.9164 16.3974 10.9164 16.8159 11.3348C17.2343 11.7533 17.2343 12.4316 16.8159 12.8501L15.9823 13.6837C14.4177 15.2483 11.8809 15.2483 10.3162 13.6837C8.75161 12.119 8.75161 9.58223 10.3162 8.0176L11.1498 7.184C11.5683 6.76559 12.2467 6.76559 12.6651 7.184ZM17.245 14.9463C14.983 17.2083 11.3156 17.2083 9.05356 14.9463C6.79156 12.6843 6.79156 9.01691 9.05356 6.7549L9.52276 6.28571H4.14286C2.95939 6.28571 2 7.2451 2 8.42857V19.8571C2 21.0406 2.95939 22 4.14286 22H15.5714C16.7549 22 17.7143 21.0406 17.7143 19.8571V14.4771L17.245 14.9463Z"
|
||||
fill={'fill'}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import { OverlayTrigger, Popover } from 'react-bootstrap';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import cx from 'classnames';
|
||||
import { DefaultCopyIcon } from './DefaultCopyIcon';
|
||||
import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers';
|
||||
|
||||
export const HiddenOptions = (props) => {
|
||||
const { nodeSpecificFilteredActions, generalActionsFiltered, darkMode, setActionClicked, data } = props;
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const closeMenu = () => {
|
||||
setShowMenu(false);
|
||||
};
|
||||
|
||||
const copyPath = () => {
|
||||
generalActionsFiltered[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false);
|
||||
};
|
||||
|
||||
const copyValue = () => {
|
||||
const value = getResolvedValue(`{{${data?.selectedNodePath}}}`);
|
||||
generalActionsFiltered[0].dispatchAction(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (event.target.closest('.copy-menu-options') === null) {
|
||||
closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// This is to ensure that the actionClicked state is updated when the menu is shown or deleted on the next render to avoid misplacing the Popover
|
||||
useEffect(() => {
|
||||
setTimeout(() => setActionClicked(showMenu), 0);
|
||||
}, [showMenu]);
|
||||
|
||||
const renderOptions = () => {
|
||||
return nodeSpecificFilteredActions?.map((actionOption, index) => {
|
||||
const { name, icon, src, iconName, dispatchAction, width = 12, height = 12 } = actionOption;
|
||||
if (icon) {
|
||||
return (
|
||||
<div
|
||||
className="node-action-icon"
|
||||
key={`${name}-${index}`}
|
||||
data-cy={`inspector-${generateCypressDataCy(name || '')}-action`}
|
||||
>
|
||||
<ToolTip message={`${name}`}>
|
||||
{/* ${name === 'Go to component' ? '' : currentNode} */}
|
||||
<span
|
||||
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
dispatchAction(data);
|
||||
}}
|
||||
>
|
||||
<SolidIcon name={iconName} fill="var(--icon-strong)" width={width} height={height} />
|
||||
</span>
|
||||
</ToolTip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
className={cx('d-flex position-absolute', { 'show-menu': showMenu })}
|
||||
>
|
||||
{renderOptions()}
|
||||
<OverlayTrigger
|
||||
trigger={'click'}
|
||||
placement={'bottom-end'}
|
||||
rootClose={false}
|
||||
show={showMenu}
|
||||
overlay={
|
||||
<Popover className={cx('copy-menu-options', { 'dark-theme': darkMode })} onClick={(e) => e.stopPropagation()}>
|
||||
<Popover.Body bsPrefix="popover-body">
|
||||
<div className="menu-options mb-0">
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
copyPath();
|
||||
closeMenu();
|
||||
}}
|
||||
className="option"
|
||||
data-cy="inspector-copy-path"
|
||||
>
|
||||
<SolidIcon width="16" height="16" name="copy" fill="var(--icon-weak)" />
|
||||
<span> Copy path</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
copyValue();
|
||||
closeMenu();
|
||||
}}
|
||||
className="option"
|
||||
data-cy="inspector-copy-value"
|
||||
>
|
||||
<DefaultCopyIcon height={16} width={16} fill="var(--icon-weak)" />
|
||||
<span> Copy value</span>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setShowMenu((prev) => !prev);
|
||||
}}
|
||||
className="node-action-icon"
|
||||
style={{
|
||||
outline: 'none',
|
||||
...(showMenu && { backgroundColor: 'var(--button-outline-pressed, rgba(136, 144, 153, 0.18)' }),
|
||||
}}
|
||||
>
|
||||
<SolidIcon fill="var(--icon-strong)" width="12" height="12" name="copy" />
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import TreeView, { flattenTree } from 'react-accessible-treeview';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import Fuse from 'fuse.js';
|
||||
import JSONViewer from './JSONViewer';
|
||||
import { Node } from './Node';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import InputComponent from '@/components/ui/Input/Index';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
const ensureUniqueIds = (node, parentId = '') => {
|
||||
if (!node) return node;
|
||||
|
||||
const seenIds = new Set();
|
||||
const processNode = (currentNode, currentParentId = '') => {
|
||||
if (!currentNode) return currentNode;
|
||||
|
||||
const newChildren = currentNode.children?.map((child, index) => {
|
||||
const baseId = child.id;
|
||||
let uniqueId = baseId;
|
||||
let counter = 1;
|
||||
|
||||
while (seenIds.has(uniqueId)) {
|
||||
uniqueId = `${baseId}_${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
seenIds.add(uniqueId);
|
||||
return processNode({ ...child, id: uniqueId }, uniqueId);
|
||||
});
|
||||
|
||||
return {
|
||||
...currentNode,
|
||||
children: newChildren,
|
||||
};
|
||||
};
|
||||
|
||||
return processNode(node, parentId);
|
||||
};
|
||||
|
||||
const JSONTreeViewerV2 = ({ data = {}, iconsList = [], darkMode, searchablePaths = new Set() }) => {
|
||||
const searchValue = useStore((state) => state.inspectorSearchValue, shallow);
|
||||
const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow);
|
||||
const getComponentDefinition = useStore((state) => state.getComponentDefinition, shallow);
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const setSearchValue = useStore((state) => state.setInspectorSearchValue, shallow);
|
||||
const selectedNodePath = useStore((state) => state.selectedNodePath, shallow);
|
||||
const setSelectedNodePath = useStore((state) => state.setSelectedNodePath, shallow);
|
||||
|
||||
const selectedNodes = useStore((state) => state.selectedNodes, shallow);
|
||||
|
||||
function fuzzySearch(query, searchablePaths) {
|
||||
const list = Array.from(searchablePaths);
|
||||
const fuse = new Fuse(list, {
|
||||
threshold: 0.2,
|
||||
minMatchCharLength: 2,
|
||||
includeScore: true,
|
||||
distance: 1000,
|
||||
tokenize: true,
|
||||
matchAllTokens: true,
|
||||
});
|
||||
return fuse.search(query).map((result) => result.item);
|
||||
}
|
||||
|
||||
const [searchedSet, pathSet] = useMemo(() => {
|
||||
const result = fuzzySearch(searchValue, searchablePaths);
|
||||
const expandedIdSet = new Set();
|
||||
result.forEach((id) => {
|
||||
const pathArray = id.split('.');
|
||||
for (let i = pathArray.length - 1; i > 0; i--) {
|
||||
const parentPath = pathArray.slice(0, i).join('.');
|
||||
if (!expandedIdSet.has(parentPath)) {
|
||||
expandedIdSet.add(parentPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
return [new Set(result), expandedIdSet];
|
||||
}, [searchValue, JSON.stringify(searchablePaths)]);
|
||||
|
||||
// Do not remove this code, once we have the data in the correct format, we can use this function to filter the data
|
||||
// const recursiveFn = (obj) => {
|
||||
// if (!obj || typeof obj !== 'object') return [];
|
||||
// let isCompletelyExposed = false;
|
||||
// obj?.children?.forEach((child) => {
|
||||
// const { id } = child;
|
||||
// if (searchedSet.has(id)) {
|
||||
// isCompletelyExposed = true;
|
||||
// }
|
||||
// });
|
||||
// const newChildren =
|
||||
// obj?.children
|
||||
// ?.filter((child) => {
|
||||
// return isCompletelyExposed || pathSet.has(child.id);
|
||||
// })
|
||||
// ?.map((child) => {
|
||||
// return recursiveFn(child);
|
||||
// }) || [];
|
||||
|
||||
// return {
|
||||
// ...obj,
|
||||
// children: newChildren,
|
||||
// };
|
||||
// };
|
||||
|
||||
// const formattedData = useMemo(() => {
|
||||
// return searchValue ? recursiveFn(data) : data;
|
||||
// }, [data, searchValue]);
|
||||
|
||||
const key = useMemo(() => {
|
||||
return uuidv4();
|
||||
}, [JSON.stringify(data), selectedNodePath]);
|
||||
|
||||
const processedData = useMemo(() => ensureUniqueIds(data), [data]);
|
||||
const flattendedData = flattenTree(processedData);
|
||||
|
||||
const backFn = () => {
|
||||
setSelectedNodePath(null);
|
||||
};
|
||||
|
||||
const selectedData = (() => {
|
||||
if (selectedNodePath?.startsWith('components.')) {
|
||||
// Split the selectedNode path using . and grab the second element if it exists
|
||||
const pathArray = selectedNodePath.split('.');
|
||||
const componentName = pathArray?.[1];
|
||||
const componentId = getComponentIdFromName(componentName);
|
||||
const component = getComponentDefinition(componentId);
|
||||
const parent = component?.component?.parent;
|
||||
if (parent) {
|
||||
const parentComponent = getComponentDefinition(parent);
|
||||
const parentType = parentComponent?.component?.component;
|
||||
if (parentType === 'Form') {
|
||||
return {
|
||||
id: componentId,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return selectedNodePath ? getResolvedValue(`{{${selectedNodePath}}}`) : {};
|
||||
})();
|
||||
|
||||
const expandedIds = [...Array.from(pathSet), ...selectedNodes];
|
||||
|
||||
const filteredIds = useMemo(() => {
|
||||
const expandedIdsSet = new Set(expandedIds);
|
||||
const filtered = flattendedData.filter((item) => {
|
||||
const { metadata } = item || {};
|
||||
const { actualPath, path } = metadata || {};
|
||||
return expandedIdsSet.has(actualPath || path);
|
||||
});
|
||||
|
||||
return filtered
|
||||
.map((item) => item.id)
|
||||
.filter((path) => {
|
||||
const pathArray = path.split('.');
|
||||
// One by one combine and check if the path is in expandedIds or not
|
||||
for (let i = pathArray.length - 1; i > 0; i--) {
|
||||
const parentPath = pathArray.slice(0, i).join('.');
|
||||
if (!expandedIdsSet.has(parentPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [flattendedData, expandedIds]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!selectedNodePath || (typeof selectedData == 'object' && isEmpty(selectedData)) ? (
|
||||
<div>
|
||||
<div style={{ margin: '8px 16px 12px 16px' }}>
|
||||
{/* <SearchBox
|
||||
dataCy={`inspector-search`}
|
||||
initialValue={searchValue}
|
||||
callBack={(e) => setSearchValue(e.target.value)}
|
||||
onClearCallback={() => setSearchValue('')}
|
||||
placeholder={`Search`}
|
||||
customClass={`tj-inspector-search-input tj-text-xsm`}
|
||||
showClearButton={false}
|
||||
width={300}
|
||||
/> */}
|
||||
|
||||
<InputComponent
|
||||
leadingIcon="search01"
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
onClear={() => setSearchValue('')}
|
||||
size="medium"
|
||||
placeholder="Search"
|
||||
value={searchValue}
|
||||
{...(searchValue && { trailingAction: 'clear' })}
|
||||
data-cy="inspector-search-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="json-tree-view">
|
||||
<TreeView
|
||||
data={flattendedData}
|
||||
className="basic"
|
||||
aria-label="basic example tree"
|
||||
defaultExpandedIds={selectedNodes}
|
||||
expandedIds={filteredIds}
|
||||
key={key}
|
||||
nodeRenderer={(props) => {
|
||||
const { element } = props;
|
||||
const { metadata } = element || {};
|
||||
const { path } = metadata || {};
|
||||
const data = {
|
||||
nodeName: element.name,
|
||||
selectedNodePath: path,
|
||||
};
|
||||
|
||||
return (
|
||||
<Node
|
||||
{...props}
|
||||
darkMode={darkMode}
|
||||
setSelectedNodePath={setSelectedNodePath}
|
||||
searchValue={searchValue}
|
||||
iconsList={iconsList}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<JSONViewer
|
||||
data={selectedData}
|
||||
iconsList={iconsList}
|
||||
darkMode={darkMode}
|
||||
path={selectedNodePath}
|
||||
backFn={backFn}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default JSONTreeViewerV2;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import { TreeViewHeader } from './TreeViewHeader';
|
||||
import useCallbackActions from './useCallbackActions';
|
||||
import CustomJSONViewer from './CustomJSONViewer/CustomJSONViewer';
|
||||
|
||||
export const JSONViewer = (props) => {
|
||||
const { data, path, darkMode, backFn, iconsList } = props;
|
||||
|
||||
const callbackActions = useCallbackActions() || [];
|
||||
const type = path.startsWith('components') ? 'components' : path.startsWith('queries') ? 'queries' : 'actions';
|
||||
const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for))?.[0]?.actions;
|
||||
const optionsData = {
|
||||
nodeName: path?.split('.')?.slice(-1)?.[0] || '',
|
||||
selectedNodePath: path,
|
||||
};
|
||||
|
||||
const generalActions = callbackActions.filter((action) => action.for === 'all')?.[0]?.actions || [];
|
||||
|
||||
return (
|
||||
<div className="json-viewer">
|
||||
<TreeViewHeader
|
||||
path={path}
|
||||
backFn={backFn}
|
||||
darkMode={darkMode}
|
||||
nodeSpecificActions={nodeSpecificActions}
|
||||
generalActions={generalActions}
|
||||
data={optionsData}
|
||||
type={type}
|
||||
/>
|
||||
<CustomJSONViewer absolutePath={path} data={data} darkMode={darkMode} iconsList={iconsList} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JSONViewer;
|
||||
|
|
@ -1,108 +1,153 @@
|
|||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { HeaderSection } from '@/_ui/LeftSidebar';
|
||||
import JSONTreeViewer from '@/_ui/JSONTreeViewer';
|
||||
import JSONTreeViewerV2 from './JSONTreeViewerV2';
|
||||
import _ from 'lodash';
|
||||
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
||||
import useIconList from './useIconList';
|
||||
import useCallbackActions from './useCallbackActions';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
|
||||
import { formatInspectorDataMisc, formatInspectorQueryData } from './utils';
|
||||
import ErrorBoundary from '@/_ui/ErrorBoundary';
|
||||
|
||||
const sortAndReduce = (obj) => {
|
||||
return Object.entries(obj)
|
||||
.sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' }))
|
||||
.reduce((acc, [name, value]) => {
|
||||
acc[name] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
import './styles.scss';
|
||||
|
||||
const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
|
||||
const LeftSidebarInspector = ({ darkMode, pinned, setPinned, moduleId, appType }) => {
|
||||
const exposedComponentsVariables = useStore((state) => state.getAllExposedValues().components, shallow);
|
||||
const exposedQueries = useStore((state) => state.getAllExposedValues().queries || {}, shallow);
|
||||
const exposedVariables = useStore((state) => state.getAllExposedValues().variables || {}, shallow);
|
||||
const exposedConstants = useStore((state) => state.getAllExposedValues().constants || {}, shallow);
|
||||
const exposedPageVariables = useStore((state) => state.getAllExposedValues().page || {}, shallow);
|
||||
const exposedGlobalVariables = useStore((state) => state.getAllExposedValues().globals || {}, shallow);
|
||||
const exposedModuleInputs = useStore((state) => state.getAllExposedValues(moduleId).input || {}, shallow);
|
||||
const componentIdNameMapping = useStore((state) => state.getComponentIdNameMapping(), shallow);
|
||||
const formatInspectorComponentData = useStore((state) => state.formatInspectorComponentData, shallow);
|
||||
const queryNameIdMapping = useStore((state) => state.getQueryNameIdMapping(), shallow);
|
||||
const pathToBeInspected = useStore((state) => state.pathToBeInspected);
|
||||
const searchablePaths = useRef(new Set(['queries', 'components', 'globals', 'variables', 'page', 'constants']));
|
||||
|
||||
const iconsList = useIconList({
|
||||
exposedComponentsVariables,
|
||||
componentIdNameMapping,
|
||||
exposedQueries,
|
||||
});
|
||||
const callbackActions = useCallbackActions();
|
||||
|
||||
const sortedComponents = useMemo(() => {
|
||||
return Object.entries(componentIdNameMapping)
|
||||
.map(([key, name]) => ({
|
||||
key,
|
||||
name: name || key,
|
||||
value: exposedComponentsVariables[key] ?? { id: key },
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }))
|
||||
.reduce((acc, { key, name, value }) => {
|
||||
acc[name] = { ...value, id: key };
|
||||
return acc;
|
||||
}, {});
|
||||
return formatInspectorComponentData(componentIdNameMapping, exposedComponentsVariables, searchablePaths.current);
|
||||
}, [exposedComponentsVariables, componentIdNameMapping]);
|
||||
|
||||
const sortedQueries = useMemo(() => {
|
||||
// Create a reverse mapping for faster lookups
|
||||
const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name]));
|
||||
|
||||
const _sortedQueries = Object.entries(exposedQueries)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
name: reverseMapping[key] || key,
|
||||
value,
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }))
|
||||
.reduce((acc, { name, value }) => {
|
||||
acc[name] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
return _sortedQueries;
|
||||
return formatInspectorQueryData(queryNameIdMapping, exposedQueries, searchablePaths.current);
|
||||
}, [exposedQueries, queryNameIdMapping]);
|
||||
|
||||
const sortedVariables = useMemo(() => sortAndReduce(exposedVariables), [exposedVariables]);
|
||||
const sortedVariables = useMemo(
|
||||
() => formatInspectorDataMisc(exposedVariables, 'variables', searchablePaths.current),
|
||||
[exposedVariables]
|
||||
);
|
||||
|
||||
const sortedConstants = useMemo(() => sortAndReduce(exposedConstants), [exposedConstants]);
|
||||
const sortedConstants = useMemo(
|
||||
() => formatInspectorDataMisc(exposedConstants, 'constants', searchablePaths.current),
|
||||
[exposedConstants]
|
||||
);
|
||||
|
||||
const sortedPageVariables = useMemo(() => sortAndReduce(exposedPageVariables), [exposedPageVariables]);
|
||||
const sortedPageVariables = useMemo(
|
||||
() => formatInspectorDataMisc(exposedPageVariables, 'page', searchablePaths.current),
|
||||
[exposedPageVariables]
|
||||
);
|
||||
|
||||
const sortedGlobalVariables = useMemo(() => sortAndReduce(exposedGlobalVariables), [exposedGlobalVariables]);
|
||||
const sortedGlobalVariables = useMemo(
|
||||
() => formatInspectorDataMisc(exposedGlobalVariables, 'globals', searchablePaths.current),
|
||||
[exposedGlobalVariables]
|
||||
);
|
||||
|
||||
const sortedModuleInputs = useMemo(
|
||||
() => formatInspectorDataMisc(exposedModuleInputs, 'input', searchablePaths.current),
|
||||
[exposedModuleInputs]
|
||||
);
|
||||
|
||||
const memoizedJSONData = React.useMemo(() => {
|
||||
const jsontreeData = {};
|
||||
const jsontreeData = {
|
||||
name: '',
|
||||
children: [
|
||||
{
|
||||
id: 'queries',
|
||||
name: 'Queries',
|
||||
|
||||
jsontreeData['queries'] = sortedQueries;
|
||||
jsontreeData['components'] = sortedComponents;
|
||||
jsontreeData['globals'] = sortedGlobalVariables;
|
||||
jsontreeData['variables'] = sortedVariables;
|
||||
jsontreeData['page'] = sortedPageVariables;
|
||||
jsontreeData['constants'] = sortedConstants;
|
||||
children: sortedQueries,
|
||||
metadata: { type: 'queries', path: 'queries' },
|
||||
},
|
||||
{
|
||||
id: 'components',
|
||||
name: 'Components',
|
||||
|
||||
children: sortedComponents,
|
||||
metadata: { type: 'components', path: 'components' },
|
||||
},
|
||||
{
|
||||
id: 'globals',
|
||||
name: 'Globals',
|
||||
|
||||
children: sortedGlobalVariables,
|
||||
metadata: { type: 'globals', path: 'globals' },
|
||||
},
|
||||
{
|
||||
id: 'variables',
|
||||
name: 'Variables',
|
||||
|
||||
children: sortedVariables,
|
||||
metadata: { type: 'variables', path: 'variables' },
|
||||
},
|
||||
{
|
||||
id: 'page',
|
||||
name: 'Page',
|
||||
|
||||
children: sortedPageVariables,
|
||||
metadata: { type: 'page', path: 'page' },
|
||||
},
|
||||
{
|
||||
id: 'constants',
|
||||
name: 'Constants',
|
||||
children: sortedConstants,
|
||||
metadata: { type: 'constants', path: 'constants' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (appType === 'module') {
|
||||
jsontreeData.children.push({
|
||||
id: 'input',
|
||||
name: 'Input',
|
||||
children: sortedModuleInputs,
|
||||
metadata: { path: 'input' },
|
||||
});
|
||||
}
|
||||
|
||||
const addNoDataChild = (data) => {
|
||||
const types = data.children;
|
||||
types.forEach((type) => {
|
||||
if (type.children.length === 0) {
|
||||
type.children.push({
|
||||
id: `empty-${type.metadata.type}`,
|
||||
name: `No ${type.metadata.type} found`,
|
||||
children: [],
|
||||
metadata: { noData: true },
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
addNoDataChild(jsontreeData);
|
||||
|
||||
return jsontreeData;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sortedComponents, sortedQueries, sortedVariables, sortedConstants, sortedPageVariables, sortedGlobalVariables]);
|
||||
|
||||
const handleNodeExpansion = (path, data, currentNode) => {
|
||||
if (pathToBeInspected && path?.length > 0) {
|
||||
const shouldExpand = pathToBeInspected.includes(path[path.length - 1]);
|
||||
|
||||
// Scroll to the component in the inspector
|
||||
if (path?.length === 2 && path?.[0] === 'components' && shouldExpand) {
|
||||
const target = document.getElementById(`inspector-node-${String(currentNode).toLowerCase()}`);
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
|
||||
return shouldExpand;
|
||||
} else return false;
|
||||
};
|
||||
}, [
|
||||
sortedComponents,
|
||||
sortedQueries,
|
||||
sortedVariables,
|
||||
sortedConstants,
|
||||
sortedPageVariables,
|
||||
sortedGlobalVariables,
|
||||
sortedModuleInputs,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -110,39 +155,30 @@ const LeftSidebarInspector = ({ darkMode, pinned, setPinned }) => {
|
|||
style={{ resize: 'horizontal', minWidth: 288 }}
|
||||
>
|
||||
<HeaderSection darkMode={darkMode}>
|
||||
<HeaderSection.PanelHeader title="Inspector">
|
||||
<HeaderSection.PanelHeader title="State inspector">
|
||||
<div className="d-flex justify-content-end">
|
||||
<ButtonSolid
|
||||
title={`${pinned ? 'Unpin' : 'Pin'}`}
|
||||
<ButtonComponent
|
||||
iconOnly
|
||||
leadingIcon={pinned ? 'unpin' : 'pin'}
|
||||
onClick={() => setPinned(!pinned)}
|
||||
darkMode={darkMode}
|
||||
styles={{ width: '28px', padding: 0 }}
|
||||
data-cy={`left-sidebar-inspector`}
|
||||
variant="tertiary"
|
||||
className="left-sidebar-header-btn"
|
||||
leftIcon={pinned ? 'unpin' : 'pin'}
|
||||
iconWidth="14"
|
||||
fill={`var(--slate12)`}
|
||||
></ButtonSolid>
|
||||
variant="ghost"
|
||||
fill="var(--icon-strong,#6A727C)"
|
||||
size="medium"
|
||||
data-cy="left-sidebar-pin-button"
|
||||
/>
|
||||
</div>
|
||||
</HeaderSection.PanelHeader>
|
||||
</HeaderSection>
|
||||
|
||||
<div className="card-body p-1 pb-5">
|
||||
<JSONTreeViewer
|
||||
data={memoizedJSONData}
|
||||
useIcons={true}
|
||||
iconsList={iconsList}
|
||||
useIndentedBlock={true}
|
||||
enableCopyToClipboard={true}
|
||||
useActions={true}
|
||||
actionsList={callbackActions}
|
||||
actionIdentifier="id"
|
||||
expandWithLabels={true}
|
||||
shouldExpandNode={handleNodeExpansion}
|
||||
// selectedComponent={selectedComponent}
|
||||
treeType="inspector"
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<JSONTreeViewerV2
|
||||
data={memoizedJSONData}
|
||||
iconsList={iconsList}
|
||||
darkMode={darkMode}
|
||||
searchablePaths={searchablePaths.current}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
import React, { useState } from 'react';
|
||||
import WidgetIcon from '@/../assets/images/icons/widgets';
|
||||
import { extractComponentName } from './utils';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import Highlighter from 'react-highlight-words';
|
||||
import cx from 'classnames';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
import { HiddenOptions } from './HiddenOptions';
|
||||
import useCallbackActions from './useCallbackActions';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
const renderNodeIcons = (node, iconsList, darkMode) => {
|
||||
const icon = iconsList.filter((icon) => icon?.iconName === node && !icon?.isInfoIcon)[0];
|
||||
if (icon && icon?.iconPath) {
|
||||
return (
|
||||
<WidgetIcon
|
||||
name={extractComponentName(icon?.iconPath)}
|
||||
fill={darkMode ? '#3A3F42' : '#D7DBDF'}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (icon && icon.jsx) {
|
||||
if (icon?.tooltipMessage) {
|
||||
return (
|
||||
<ToolTip message={icon?.tooltipMessage}>
|
||||
<div>{icon.jsx({ height: 14, width: 14 })}</div>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
return icon.jsx({ height: 14, width: 14 });
|
||||
}
|
||||
};
|
||||
|
||||
export const Node = (props) => {
|
||||
const {
|
||||
element,
|
||||
getNodeProps,
|
||||
level,
|
||||
handleSelect,
|
||||
handleExpand,
|
||||
isExpanded,
|
||||
isDisabled,
|
||||
isBranch,
|
||||
darkMode,
|
||||
setSelectedNodePath,
|
||||
searchValue,
|
||||
iconsList,
|
||||
data,
|
||||
} = props;
|
||||
|
||||
const [actionClicked, setActionClicked] = useState(false);
|
||||
const setSelectedNodes = useStore((state) => state.setSelectedNodes, shallow);
|
||||
const callbackActions = useCallbackActions() || [];
|
||||
const nodeIcon = renderNodeIcons(element.name, iconsList, darkMode);
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const metadata = element.metadata || {};
|
||||
const { type, path } = metadata;
|
||||
const nodeSpecificActions = callbackActions.filter((action) => [type].includes(action.for));
|
||||
const onExpand = (node) => {
|
||||
const { element } = node || {};
|
||||
const { metadata } = element || {};
|
||||
const { path, actualPath } = metadata || {};
|
||||
setSelectedNodes(actualPath || path);
|
||||
};
|
||||
|
||||
const onSelect = (node) => {
|
||||
const { isBranch, element } = node || {};
|
||||
const { metadata } = element || {};
|
||||
const { path, type } = metadata || {};
|
||||
if (type && level !== 1) {
|
||||
setSelectedNodePath(path);
|
||||
}
|
||||
};
|
||||
|
||||
const nodeSpecificFilteredActions =
|
||||
nodeSpecificActions?.[0]?.actions?.filter((action) => {
|
||||
return action.enableInspectorTreeView;
|
||||
}) || [];
|
||||
|
||||
const generalActions = callbackActions.filter((action) => action.for === 'all');
|
||||
const generalActionsFiltered = generalActions?.[0]?.actions?.filter((action) => {
|
||||
return action.enableInspectorTreeView;
|
||||
});
|
||||
|
||||
return (
|
||||
// <div {...getNodeProps({ onClick: handleExpand })}>
|
||||
<div
|
||||
style={{
|
||||
// marginLeft: level > 1 ? 12 : 0,
|
||||
// paddingLeft: '16px',
|
||||
opacity: isDisabled ? 0.5 : 1,
|
||||
height: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: level === 1 ? 'var(--text-placeholder, #6A727C)' : 'var(--text-default, #1B1F24)',
|
||||
fontWeight: level === 1 ? 500 : 400,
|
||||
marginTop: level === 1 ? 4 : 0,
|
||||
marginBottom: level === 1 ? 4 : 0,
|
||||
// borderLeft: level > 1 ? '1px solid var(--slate6, #D7DBDF)' : 'none',
|
||||
}}
|
||||
>
|
||||
{/* {!['queries', 'globals', 'variables'].includes(type) && ( */}
|
||||
<div className="node-expansion-icon">
|
||||
{(isBranch || level === 1 || path === 'page.variables') && (
|
||||
<ButtonComponent
|
||||
iconOnly
|
||||
leadingIcon={isExpanded ? 'TriangleDownCenter' : 'rightarrrow'}
|
||||
onClick={() => onExpand(props)}
|
||||
variant="ghost"
|
||||
fill="var(--icon-default,#ACB2B9)"
|
||||
size="small"
|
||||
data-cy={`inspector-${type}-expand-button`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* )} */}
|
||||
|
||||
<div
|
||||
onClick={() => onSelect(props)}
|
||||
className={cx('node-content', {
|
||||
'node-content-hoverable': level !== 1 && !metadata.noData,
|
||||
'node-content-active': actionClicked,
|
||||
})}
|
||||
>
|
||||
{nodeIcon && <div className="node-icon">{nodeIcon}</div>}
|
||||
<div className="node-label" data-cy={`inspector-${type}-node`}>
|
||||
{metadata.noData && (
|
||||
<div className="node-label-no-data">
|
||||
<SolidIcon name="removefolder" fill="var(--icon-weak)" width="14" height="14" />
|
||||
{element.name}
|
||||
</div>
|
||||
)}
|
||||
{!metadata.noData && (
|
||||
<OverflowTooltip whiteSpace="normal" placement="top" style={{ height: '100%', width: '80%' }}>
|
||||
<Highlighter
|
||||
highlightClassName="node-highlight"
|
||||
searchWords={[searchValue]}
|
||||
autoEscape={true}
|
||||
textToHighlight={element.name}
|
||||
/>
|
||||
</OverflowTooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="node-actions" style={{ marginRight: 10 + nodeSpecificFilteredActions.length * 10 }}>
|
||||
<HiddenOptions
|
||||
nodeSpecificFilteredActions={nodeSpecificFilteredActions}
|
||||
generalActionsFiltered={generalActionsFiltered}
|
||||
setActionClicked={setActionClicked}
|
||||
data={data}
|
||||
darkMode={darkMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import ArrowLeft from '@/_ui/Icon/bulkIcons/Arrowleft';
|
||||
import CheveronRight from '@/_ui/Icon/bulkIcons/CheveronRight';
|
||||
import { OverlayTrigger, Popover } from 'react-bootstrap';
|
||||
import cx from 'classnames';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { DefaultCopyIcon } from './DefaultCopyIcon';
|
||||
import { generateCypressDataCy } from '@/modules/common/helpers/cypressHelpers';
|
||||
|
||||
export const TreeViewHeader = (props) => {
|
||||
const { path, backFn, darkMode, data, nodeSpecificActions, type, generalActions } = props;
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const pathArray = path.split('.');
|
||||
const parentNode = pathArray[0];
|
||||
|
||||
const closeMenu = () => {
|
||||
setShowMenu(false);
|
||||
};
|
||||
|
||||
const copyPath = () => {
|
||||
generalActions[0].dispatchAction(`{{${data?.selectedNodePath}}}`, false);
|
||||
};
|
||||
|
||||
const copyValue = () => {
|
||||
const value = getResolvedValue(`{{${data?.selectedNodePath}}}`);
|
||||
generalActions[0].dispatchAction(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (event.target.closest('.copy-menu-options') === null) {
|
||||
closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const renderOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="menu-options mb-0">
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
copyPath();
|
||||
closeMenu();
|
||||
}}
|
||||
className="option"
|
||||
data-cy="inspector-copy-path"
|
||||
>
|
||||
<SolidIcon width="16" height="16" name="copy" fill="var(--icon-weak)" />
|
||||
<span> Copy path</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
copyValue();
|
||||
closeMenu();
|
||||
}}
|
||||
className="option"
|
||||
data-cy="inspector-copy-value"
|
||||
>
|
||||
<DefaultCopyIcon height={16} width={16} fill="var(--icon-weak)" />
|
||||
<span> Copy value</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{nodeSpecificActions?.map((actionOption, index) => {
|
||||
const { name, icon, src, iconName, dispatchAction, width = 16, height = 16 } = actionOption;
|
||||
if (icon) {
|
||||
return (
|
||||
<div
|
||||
className="menu-options mb-0"
|
||||
key={`${name}-${index}`}
|
||||
data-cy={`inspector-${generateCypressDataCy(name || '')}-action`}
|
||||
>
|
||||
<span
|
||||
style={{ display: 'flex', alignItems: 'center' }}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
dispatchAction(data);
|
||||
setShowMenu(false);
|
||||
}}
|
||||
className="option"
|
||||
>
|
||||
<SolidIcon name={iconName} fill="var(--icon-weak)" width={width} height={height} />
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="json-viewer-header">
|
||||
{/* <div className="json-viewer-back-btn" onClick={backFn}>
|
||||
<ArrowLeft tailOpacity="1" fill={'var(--slate12)'} width={'18'} />
|
||||
</div> */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={backFn}>
|
||||
<span style={{ color: 'var(--slate11)' }}>{parentNode.charAt(0).toUpperCase() + parentNode.slice(1)}</span>
|
||||
|
||||
{pathArray.length > 1 &&
|
||||
pathArray.slice(1).map((item, index) => (
|
||||
<>
|
||||
<CheveronRight fill={'var(--slate12)'} width={'18'} />
|
||||
<span key={index} style={{ color: 'var(--slate12)' }}>
|
||||
{item.charAt(0).toUpperCase() + item.slice(1)}
|
||||
</span>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<OverlayTrigger
|
||||
trigger={'click'}
|
||||
placement={'bottom-start'}
|
||||
rootClose={false}
|
||||
show={showMenu}
|
||||
overlay={
|
||||
<Popover
|
||||
style={{ width: type === 'components' ? '180px' : '144px' }}
|
||||
className={cx('copy-menu-options', { 'dark-theme': darkMode })}
|
||||
>
|
||||
<Popover.Body bsPrefix="popover-body">{renderOptions()}</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setShowMenu((prev) => !prev);
|
||||
}}
|
||||
className="copy-menu-options-icon json-viewer-options-btn"
|
||||
style={{
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
}}
|
||||
data-cy="inspector-menu-icon"
|
||||
>
|
||||
<SolidIcon data-cy={'menu-icon'} name="morevertical" width="18" fill={'var(--icon-strong)'} />
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
.json-tree-view {
|
||||
// ul {
|
||||
// margin-left:16px !important;
|
||||
// border-left: 1px solid var(--slate6, #D7DBDF);
|
||||
// }
|
||||
|
||||
ul[role="tree"] {
|
||||
margin-left: 16px !important;
|
||||
border-left: none !important;
|
||||
|
||||
ul {
|
||||
margin-left: 16px !important;
|
||||
border-left: none !important;
|
||||
|
||||
ul {
|
||||
margin-left: 10px !important;
|
||||
padding-left: 16px !important;
|
||||
border-left: 1px solid var(--slate6, #D7DBDF) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.basic.tree {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0px;
|
||||
padding-right: 20px;
|
||||
padding-top: 0px;
|
||||
|
||||
}
|
||||
|
||||
.basic .tree-node,
|
||||
.basic .tree-node-group {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.basic .tree-branch-wrapper,
|
||||
.basic .tree-node__leaf {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// .basic .tree-node--focused {
|
||||
// outline-color: rgb(77, 144, 254);
|
||||
// outline-style: auto;
|
||||
// outline-width: 2px;
|
||||
// display: block;
|
||||
// }
|
||||
|
||||
.basic .tree-node__branch {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.basic .tree-node {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node-expansion-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
margin-right: 6px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
|
||||
.tj-inspector-search-input {
|
||||
width: 300px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--base) !important;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
}
|
||||
|
||||
.node-content-active {
|
||||
.copy-menu-options-icon {
|
||||
background-color: var(--interactive-overlays-fill-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.node-content-hoverable:hover,
|
||||
.node-content-active {
|
||||
background-color: var(--interactive-overlays-fill-hover);
|
||||
cursor: pointer;
|
||||
|
||||
.node-actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.json-viewer {
|
||||
font-size: 11px;
|
||||
|
||||
ul {
|
||||
background-color: transparent !important;
|
||||
padding-left: 6px !important;
|
||||
padding-right: 10px !important;
|
||||
// li{
|
||||
// text-indent: 0px !important;
|
||||
// padding-top: 0px !important;
|
||||
// height:18.46px;
|
||||
// }
|
||||
// ul > li:hover {
|
||||
// background-color: var(--interactive-overlays-fill-hover); // or any color you prefer
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.json-viewer-node-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.json-viewer-header {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: 16px;
|
||||
margin-bottom: 12px;
|
||||
margin-right: 18px;
|
||||
margin-top: 8px;
|
||||
height: 28px;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.json-viewer-back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--interactive-overlays-fill-hover);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// .json-viewer-options-btn {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// margin-left: auto;
|
||||
|
||||
// &:hover {
|
||||
// cursor: pointer;
|
||||
// background-color:var(--interactive-overlays-fill-hover);
|
||||
// border-radius: 4px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.json-viewer-options-btn {
|
||||
margin-left: auto;
|
||||
|
||||
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.node-highlight {
|
||||
background-color: #FFD43B;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.node-action-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-outline-hover);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.copy-menu-options-icon {
|
||||
border: 1px solid var(--border-weak, #E4E7EB);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--elevation-100-box-shadow);
|
||||
background-color: var(--base);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-outline-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.copy-menu-options {
|
||||
width: 144px;
|
||||
border: none;
|
||||
background-color: var(--background-surface-layer-01);
|
||||
border-radius: 10px;
|
||||
top: -5px !important;
|
||||
|
||||
&.dark-theme {
|
||||
.popover-body {
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.9), 0px 8px 16px 0px #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--background-surface-layer-01);
|
||||
box-shadow: 0px 0px 1px 0px rgba(48, 50, 51, 0.05), 0px 8px 16px 0px rgba(48, 50, 51, 0.1);
|
||||
|
||||
.menu-options {
|
||||
.option {
|
||||
display: flex;
|
||||
padding: 6px 8px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 30px;
|
||||
align-self: stretch;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(136, 144, 153, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-label-no-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--text-placeholder, #858C94);
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { toast } from 'react-hot-toast';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
// import { runQuery } from '@/AppBuilder/_utils/queryPanel';
|
||||
import { copyToClipboard } from './utils';
|
||||
|
||||
const useCallbackActions = () => {
|
||||
const deleteComponents = useStore((state) => state.deleteComponents, shallow);
|
||||
|
|
@ -10,29 +10,39 @@ const useCallbackActions = () => {
|
|||
const shouldFreeze = useStore((state) => state.getShouldFreeze());
|
||||
const runQuery = useStore((state) => state.queryPanel.runQuery);
|
||||
const getComponentIdToAutoScroll = useStore((state) => state.getComponentIdToAutoScroll);
|
||||
const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery, shallow);
|
||||
const getComponentIdFromName = useStore((state) => state.getComponentIdFromName, shallow);
|
||||
const getQueryIdFromName = useStore((state) => state.getQueryIdFromName, shallow);
|
||||
|
||||
const handleRemoveComponent = (component) => {
|
||||
deleteComponents([component.id]);
|
||||
const { nodeName } = component;
|
||||
const componentId = getComponentIdFromName(nodeName);
|
||||
deleteComponents([componentId]);
|
||||
};
|
||||
|
||||
const handleSelectComponentOnEditor = (component) => {
|
||||
if (currentPageComponents?.[component.id]) {
|
||||
setSelectedComponents([component.id]);
|
||||
const { nodeName } = component;
|
||||
const componentId = getComponentIdFromName(nodeName);
|
||||
if (currentPageComponents?.[componentId]) {
|
||||
setSelectedComponents([componentId]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRunQuery = (query, currentNode) => {
|
||||
runQuery(query.id, currentNode, undefined, 'edit', {}, true);
|
||||
const handleRunQuery = (data) => {
|
||||
const { nodeName } = data;
|
||||
const queryId = getQueryIdFromName(nodeName);
|
||||
runQuery(queryId, nodeName, undefined, 'edit', {}, true);
|
||||
};
|
||||
|
||||
const copyToClipboard = (data) => {
|
||||
const stringified = JSON.stringify(data, null, 2).replace(/\\/g, '');
|
||||
navigator.clipboard.writeText(stringified);
|
||||
return toast.success('Copied to the clipboard', { position: 'top-center' });
|
||||
const selectQuery = (data) => {
|
||||
const { nodeName } = data;
|
||||
const id = getQueryIdFromName(nodeName);
|
||||
setSelectedQuery(id);
|
||||
};
|
||||
|
||||
const handleAutoScrollToComponent = (data) => {
|
||||
const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(data.id);
|
||||
const componentId = getComponentIdFromName(data.nodeName);
|
||||
const { isAccessible, computedComponentId, isOnCanvas } = getComponentIdToAutoScroll(componentId);
|
||||
if (!isAccessible) {
|
||||
if (isOnCanvas) {
|
||||
toast.success(
|
||||
|
|
@ -57,9 +67,19 @@ const useCallbackActions = () => {
|
|||
name: 'Run Query',
|
||||
dispatchAction: handleRunQuery,
|
||||
icon: true,
|
||||
src: 'assets/images/icons/editor/play.svg',
|
||||
iconName: 'play01',
|
||||
width: 8,
|
||||
height: 8,
|
||||
enableInspectorTreeView: false,
|
||||
},
|
||||
{
|
||||
name: 'View query',
|
||||
dispatchAction: selectQuery,
|
||||
icon: true,
|
||||
iconName: 'file-code',
|
||||
width: 14,
|
||||
height: 14,
|
||||
enableInspectorTreeView: true,
|
||||
},
|
||||
],
|
||||
enableForAllChildren: false,
|
||||
|
|
@ -68,10 +88,30 @@ const useCallbackActions = () => {
|
|||
{
|
||||
for: 'components',
|
||||
actions: [
|
||||
{ name: 'Select Widget', dispatchAction: handleSelectComponentOnEditor, icon: false, onSelect: true },
|
||||
{ name: 'Go to component', dispatchAction: handleAutoScrollToComponent, icon: true, iconName: 'select' },
|
||||
{
|
||||
name: 'Select Widget',
|
||||
dispatchAction: handleSelectComponentOnEditor,
|
||||
icon: false,
|
||||
onSelect: true,
|
||||
enableInspectorTreeView: false,
|
||||
},
|
||||
{
|
||||
name: 'Go to component',
|
||||
dispatchAction: handleAutoScrollToComponent,
|
||||
icon: true,
|
||||
iconName: 'corners',
|
||||
enableInspectorTreeView: true,
|
||||
},
|
||||
...(!shouldFreeze
|
||||
? [{ name: 'Delete Component', dispatchAction: handleRemoveComponent, icon: true, iconName: 'trash' }]
|
||||
? [
|
||||
{
|
||||
name: 'Delete Component',
|
||||
dispatchAction: handleRemoveComponent,
|
||||
icon: true,
|
||||
iconName: 'trash',
|
||||
enableInspectorTreeView: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
enableForAllChildren: false,
|
||||
|
|
@ -79,7 +119,10 @@ const useCallbackActions = () => {
|
|||
},
|
||||
{
|
||||
for: 'all',
|
||||
actions: [{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false }],
|
||||
actions: [
|
||||
{ name: 'Copy value', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true },
|
||||
{ name: 'Copy path', dispatchAction: copyToClipboard, icon: false, enableInspectorTreeView: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
return callbackActions;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
const queryIcons = Object.keys(exposedQueries).map((queryId) => {
|
||||
const query = dataQueries.find((dataQuery) => dataQuery.id === queryId);
|
||||
if (!isEmpty(query)) {
|
||||
return { iconName: query?.name, jsx: () => <DataSourceIcon source={query} height={16} /> };
|
||||
return {
|
||||
iconName: query?.name,
|
||||
jsx: ({ height = 16, width = 16 }) => <DataSourceIcon source={query} height={height} width={width} />,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -37,7 +40,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.disable) {
|
||||
icons.push({
|
||||
iconName: 'disable',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -47,7 +52,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.visibility) {
|
||||
icons.push({
|
||||
iconName: 'visibility',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -62,7 +69,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.setChecked) {
|
||||
icons.push({
|
||||
iconName: 'setChecked',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setValue as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -78,7 +87,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.disable) {
|
||||
icons.push({
|
||||
iconName: 'disable',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setDisable as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -88,7 +99,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.visibility) {
|
||||
icons.push({
|
||||
iconName: 'visibility',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -98,7 +111,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
if (componentExposedVariables.loading) {
|
||||
icons.push({
|
||||
iconName: 'loading',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setLoading as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -112,7 +127,9 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
return [
|
||||
{
|
||||
iconName: 'visibility',
|
||||
jsx: () => <Icon name={'warning'} height={16} width={16} fill="#DB4324" />,
|
||||
jsx: ({ height = 16, width = 16 }) => (
|
||||
<Icon name={'warning'} height={height} width={width} fill="#DB4324" />
|
||||
),
|
||||
className: 'component-icon',
|
||||
tooltipMessage: 'This function will be deprecated soon, You can use setVisibility as an alternative',
|
||||
isInfoIcon: true,
|
||||
|
|
@ -124,7 +141,6 @@ const useIconList = ({ exposedComponentsVariables, componentIdNameMapping, expos
|
|||
})
|
||||
.flat()
|
||||
.filter((value) => value !== undefined); // Remove undefined values
|
||||
|
||||
const iconsList = useMemo(
|
||||
() => [...queryIcons, ...componentIcons, ...deprecatedIcons],
|
||||
[queryIcons, componentIcons, deprecatedIcons]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
import { toast } from 'react-hot-toast';
|
||||
|
||||
export const formatInspectorDataMisc = (obj, type, searchablePaths = new Set()) => {
|
||||
if (typeof obj !== 'object' || obj === null) return [];
|
||||
const data = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0], undefined, { sensitivity: 'base' }));
|
||||
const reduceData = (obj, path, level = 1) => {
|
||||
let data = obj;
|
||||
if (!obj || typeof obj !== 'object' || (path === 'page.variables' ? level > 2 : level > 1)) return [];
|
||||
else if (!Array.isArray(obj)) {
|
||||
data = Object.entries(obj);
|
||||
}
|
||||
return data.reduce((acc, [name, value]) => {
|
||||
const currentPath = path + `.${name}`;
|
||||
searchablePaths.add(currentPath);
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
id: currentPath,
|
||||
name,
|
||||
children: reduceData(value, currentPath, level + 1),
|
||||
metadata: {
|
||||
type: type,
|
||||
path: currentPath,
|
||||
...((path === 'page.variables' ? level === 2 : level === 1) && {
|
||||
data: typeof value === 'object' ? JSON.stringify(value) : value,
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
return reduceData(data, type);
|
||||
};
|
||||
|
||||
export const formatInspectorQueryData = (queryNameIdMapping, exposedQueries, searchablePaths = new Set()) => {
|
||||
const reverseMapping = Object.fromEntries(Object.entries(queryNameIdMapping).map(([name, id]) => [id, name]));
|
||||
const _sortedQueries = Object.entries(exposedQueries)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
name: reverseMapping[key] || key,
|
||||
value,
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
||||
|
||||
const reduceData = (obj, path = 'queries', level = 1) => {
|
||||
let data = obj;
|
||||
if (!obj || typeof obj !== 'object' || level > 1) return [];
|
||||
else if (!Array.isArray(obj)) {
|
||||
data = Object.entries(obj);
|
||||
}
|
||||
return data
|
||||
.filter((item) => item.name)
|
||||
.reduce((acc, { id, name, value }) => {
|
||||
const currentPath = path + `.${name}`;
|
||||
searchablePaths.add(currentPath);
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
id: currentPath,
|
||||
name,
|
||||
children: reduceData(value, currentPath, level + 1),
|
||||
metadata: {
|
||||
type: 'queries',
|
||||
path: currentPath,
|
||||
...(level === 1 && { data: typeof value === 'object' ? JSON.stringify(value) : value }),
|
||||
},
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
return reduceData(_sortedQueries);
|
||||
};
|
||||
|
||||
export const extractComponentName = (path) => {
|
||||
// Match the last part of the URL before ".svg" using a regular expression
|
||||
const match = path.match(/\/([^/]+)\.svg$/);
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1]; // Return the matched component name
|
||||
} else {
|
||||
return null; // Return null if the pattern doesn't match
|
||||
}
|
||||
};
|
||||
|
||||
export const copyToClipboard = (data, includeQuotes = true) => {
|
||||
const stringified = JSON.stringify(data, null, 2).replace(/\\/g, '');
|
||||
const finalText = includeQuotes ? stringified : stringified.slice(1, -1);
|
||||
navigator.clipboard.writeText(finalText);
|
||||
return toast.success('Copied to the clipboard', { position: 'top-center' });
|
||||
};
|
||||
|
|
@ -2,11 +2,13 @@ import React from 'react';
|
|||
import { Overlay, Popover } from 'react-bootstrap';
|
||||
import { Button } from '@/_ui/LeftSidebar';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
|
||||
export const PageHandlerMenu = ({ darkMode }) => {
|
||||
const { moduleId } = useModuleContext();
|
||||
const setShowEditingPopover = useStore((state) => state.setShowEditingPopover);
|
||||
const setShowPageEventsModal = useStore((state) => state.setShowPageEventsModal);
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ export const PageHandlerMenu = ({ darkMode }) => {
|
|||
closePageEditPopover();
|
||||
};
|
||||
|
||||
const homePageId = useStore((state) => state.app.homePageId);
|
||||
const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId);
|
||||
const page = editingPage;
|
||||
const isHomePage = page?.id === homePageId;
|
||||
const showMenu = showEditingPopover;
|
||||
|
|
@ -105,7 +107,7 @@ export const PageHandlerMenu = ({ darkMode }) => {
|
|||
text="Mark home"
|
||||
iconSrc={'assets/images/icons/home.svg'}
|
||||
closeMenu={() => {}}
|
||||
callback={() => markAsHomePage(editingPage.id)}
|
||||
callback={() => markAsHomePage(editingPage.id, moduleId)}
|
||||
/>
|
||||
)}
|
||||
{!isDisabled && (
|
||||
|
|
@ -164,7 +166,7 @@ export const PageHandlerMenu = ({ darkMode }) => {
|
|||
>
|
||||
<div className="d-flex align-items-center">
|
||||
<div>Page permission</div>
|
||||
{!licenseValid && <SolidIcon name="enterprisesmall" />}
|
||||
{!licenseValid && <SolidIcon name="enterprisecrown" />}
|
||||
</div>
|
||||
</ToolTip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,13 +16,16 @@ import { RenameInput } from './RenameInput';
|
|||
import IconSelector from './IconSelector';
|
||||
import { withRouter } from '@/_hoc/withRouter';
|
||||
import OverflowTooltip from '@/_components/OverflowTooltip';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
|
||||
export const PageMenuItem = withRouter(
|
||||
memo(({ darkMode, page, navigate }) => {
|
||||
const homePageId = useStore((state) => state.app.homePageId);
|
||||
const { moduleId } = useModuleContext();
|
||||
const homePageId = useStore((state) => state.appStore.modules[moduleId].app.homePageId);
|
||||
const isHomePage = page.id === homePageId;
|
||||
const currentPageId = useStore((state) => state.currentPageId);
|
||||
const currentPageId = useStore((state) => state.modules[moduleId].currentPageId);
|
||||
const isSelected = page.id === currentPageId;
|
||||
const isHidden = page?.hidden ?? false;
|
||||
const isDisabled = page?.disabled ?? false;
|
||||
|
|
@ -139,9 +142,9 @@ export const PageMenuItem = withRouter(
|
|||
if (currentPageId === page.id) {
|
||||
return;
|
||||
}
|
||||
switchPage(page.id, page.handle);
|
||||
setCurrentPageHandle(page.handle);
|
||||
}, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle]);
|
||||
switchPage(page.id, page.handle, [], moduleId);
|
||||
setCurrentPageHandle(page.handle, moduleId);
|
||||
}, [currentPageId, page.id, page.handle, switchPage, setCurrentPageHandle, moduleId]);
|
||||
|
||||
const handlePageMenuSettings = useCallback(
|
||||
(event) => {
|
||||
|
|
@ -151,6 +154,36 @@ export const PageMenuItem = withRouter(
|
|||
[popoverRef.current, page]
|
||||
);
|
||||
|
||||
function getTooltip() {
|
||||
const permission = page?.permissions?.length ? page?.permissions[0] : null;
|
||||
if (!permission) return '';
|
||||
const users = permission.users || [];
|
||||
const isSingle = permission.type === 'SINGLE';
|
||||
const isGroup = permission.type === 'GROUP';
|
||||
|
||||
if (users.length === 0) return null;
|
||||
|
||||
if (isSingle) {
|
||||
if (users.length === 1) {
|
||||
const email = users[0].user.email;
|
||||
return `Access restricted to ${email}`;
|
||||
} else {
|
||||
return `Access restricted to ${users.length} users`;
|
||||
}
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
if (users.length === 1) {
|
||||
const groupName = users[0].permissionGroup?.name ?? 'Group';
|
||||
return `Access restricted to ${groupName} group`;
|
||||
} else {
|
||||
return `Access restricted to ${users.length} groups`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
|
|
@ -200,7 +233,13 @@ export const PageMenuItem = withRouter(
|
|||
</span>
|
||||
</div>
|
||||
<div style={{ marginLeft: '8px', marginRight: 'auto' }}>
|
||||
{licenseValid && restricted && <SolidIcon width="16" name="lock" fill="var(--icon-strong)" />}
|
||||
{licenseValid && restricted && (
|
||||
<ToolTip message={getTooltip()}>
|
||||
<div>
|
||||
<SolidIcon width="16" name="lock" fill="var(--icon-strong)" />
|
||||
</div>
|
||||
</ToolTip>
|
||||
)}
|
||||
</div>
|
||||
<div className={cx('right', { 'handler-menu-open': showEditingPopover })}>
|
||||
{!shouldFreeze && (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { appPermissionService } from '@/_services';
|
|||
import { ConfirmDialog } from '@/_components';
|
||||
import toast from 'react-hot-toast';
|
||||
import Spinner from '@/_ui/Spinner';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
const PERMISSION_TYPES = {
|
||||
single: 'SINGLE',
|
||||
|
|
@ -16,10 +17,11 @@ const PERMISSION_TYPES = {
|
|||
};
|
||||
|
||||
export default function PagePermission({ darkMode }) {
|
||||
const { moduleId } = useModuleContext();
|
||||
const showPagePermissionModal = useStore((state) => state.showPagePermissionModal);
|
||||
const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal);
|
||||
const editingPage = useStore((state) => state.editingPage);
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
|
||||
const selectedUserGroups = useStore((state) => state.selectedUserGroups);
|
||||
const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
|
||||
const selectedUsers = useStore((state) => state.selectedUsers);
|
||||
|
|
@ -34,7 +36,6 @@ export default function PagePermission({ darkMode }) {
|
|||
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPermissionsLoading, setPermissionsLoading] = useState(true);
|
||||
const [pageToDelete, setPageToDelete] = useState(null);
|
||||
const [initialSelectedGroups, setInitialSelectedGroups] = useState([]);
|
||||
const [initialSelectedUsers, setInitialSelectedUsers] = useState([]);
|
||||
const [initalPagePermissionType, setInitialPagePermissionType] = useState('all');
|
||||
|
|
@ -42,7 +43,7 @@ export default function PagePermission({ darkMode }) {
|
|||
useEffect(() => {
|
||||
if (!showPagePermissionModal) return;
|
||||
const fetchPagePermission = () => {
|
||||
appPermissionService.getPagePermission(appId, editingPage?.id || pageToDelete).then((data) => {
|
||||
appPermissionService.getPagePermission(appId, editingPage?.id).then((data) => {
|
||||
if (data) {
|
||||
if (data[0] && data[0]?.type === PERMISSION_TYPES.group) {
|
||||
const groups =
|
||||
|
|
@ -55,7 +56,6 @@ export default function PagePermission({ darkMode }) {
|
|||
setInitialPagePermissionType(data[0]?.type?.toLowerCase());
|
||||
setPagePermission(data);
|
||||
toggleUserGroupSelect(true);
|
||||
setPageToDelete(null);
|
||||
setInitialSelectedGroups(groups);
|
||||
data?.length && setSelectedUserGroups(groups);
|
||||
} else if (data[0] && data[0]?.type === PERMISSION_TYPES.single) {
|
||||
|
|
@ -74,7 +74,6 @@ export default function PagePermission({ darkMode }) {
|
|||
setInitialPagePermissionType(data[0]?.type?.toLowerCase());
|
||||
setPagePermission(data);
|
||||
toggleUsersSelect(true);
|
||||
setPageToDelete(null);
|
||||
setInitialSelectedUsers(users);
|
||||
data?.length && setSelectedUsers(users);
|
||||
}
|
||||
|
|
@ -83,7 +82,7 @@ export default function PagePermission({ darkMode }) {
|
|||
});
|
||||
};
|
||||
fetchPagePermission();
|
||||
}, [showPagePermissionModal, pageToDelete]);
|
||||
}, [showPagePermissionModal]);
|
||||
|
||||
const isSelectionUnchanged = useMemo(() => {
|
||||
if (pagePermissionType === 'group') {
|
||||
|
|
@ -237,13 +236,12 @@ export default function PagePermission({ darkMode }) {
|
|||
const deletePagePermission = () => {
|
||||
setIsLoading(true);
|
||||
appPermissionService
|
||||
.deletePagePermission(appId, pageToDelete)
|
||||
.deletePagePermission(appId, editingPage?.id)
|
||||
.then((data) => {
|
||||
toast.success('Permission successfully deleted!', {
|
||||
className: 'text-nowrap w-auto mw-100',
|
||||
});
|
||||
updatePageWithPermissions(pageToDelete, []);
|
||||
setPageToDelete(null);
|
||||
updatePageWithPermissions(editingPage?.id, []);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error('Permission could not be deleted. Please try again!', {
|
||||
|
|
@ -284,25 +282,18 @@ export default function PagePermission({ darkMode }) {
|
|||
isLoading={isLoading}
|
||||
handleClose={handlePagePermissionModalClose}
|
||||
confirmBtnProps={{
|
||||
title: pagePermission ? 'Update' : pagePermissionType === 'all' ? 'Default permission' : 'Create permission',
|
||||
title: pagePermission
|
||||
? 'Save changes'
|
||||
: pagePermissionType === 'all'
|
||||
? 'Default permission'
|
||||
: 'Create permission',
|
||||
disabled: isPermissionsLoading || isSelectionUnchanged,
|
||||
tooltipMessage: '',
|
||||
leftIcon: pagePermission && 'save',
|
||||
className: 'action-btn-page-permission',
|
||||
}}
|
||||
darkMode={darkMode}
|
||||
className="page-permissions-modal"
|
||||
headerAction={() =>
|
||||
pagePermission && (
|
||||
<span
|
||||
onClick={(e) => {
|
||||
setPageToDelete(editingPage?.id);
|
||||
togglePagePermissionModal(false);
|
||||
setShowConfirmDelete(true);
|
||||
}}
|
||||
>
|
||||
<SolidIcon fill="var(--tomato10)" width="20" name="trash" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="page-permission">
|
||||
{isPermissionsLoading ? (
|
||||
|
|
@ -360,7 +351,8 @@ export default function PagePermission({ darkMode }) {
|
|||
}
|
||||
|
||||
const UserGroupSelect = () => {
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const { moduleId } = useModuleContext();
|
||||
const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
|
||||
const selectedUserGroups = useStore((state) => state.selectedUserGroups);
|
||||
const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups);
|
||||
const [userGroups, setUserGroups] = useState([]);
|
||||
|
|
@ -420,7 +412,8 @@ const UserGroupSelect = () => {
|
|||
};
|
||||
|
||||
const UserSelect = () => {
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const { moduleId } = useModuleContext();
|
||||
const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
|
||||
const editingPage = useStore((state) => state.editingPage);
|
||||
const selectedUsers = useStore((state) => state.selectedUsers);
|
||||
const setSelectedUsers = useStore((state) => state.setSelectedUsers);
|
||||
|
|
|
|||
|
|
@ -396,4 +396,8 @@
|
|||
.spinner-center {
|
||||
min-height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-base .modal-footer .action-btn-page-permission svg path {
|
||||
fill: var(--indigo1) !important;
|
||||
}
|
||||
|
|
@ -10,12 +10,12 @@ import { Button } from 'react-bootstrap';
|
|||
import { decodeEntities } from '@/_helpers/utils';
|
||||
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import { Button as ButtonComponent } from '@/components/ui/Button/Button';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => {
|
||||
const moduleId = useModuleId();
|
||||
const { moduleId } = useModuleContext();
|
||||
const updateQuerySuggestions = useStore((state) => state.queryPanel.updateQuerySuggestions);
|
||||
const previewQuery = useStore((state) => state.queryPanel.previewQuery);
|
||||
const renameQuery = useStore((state) => state.dataQuery.renameQuery);
|
||||
|
|
@ -152,8 +152,8 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
|
|||
const hasPermissions =
|
||||
selectedDataSourceScope === 'global'
|
||||
? canUpdateDataSource(selectedQuery?.data_source_id) ||
|
||||
canReadDataSource(selectedQuery?.data_source_id) ||
|
||||
canDeleteDataSource()
|
||||
canReadDataSource(selectedQuery?.data_source_id) ||
|
||||
canDeleteDataSource()
|
||||
: true;
|
||||
const inputRef = useRef();
|
||||
|
||||
|
|
@ -275,8 +275,8 @@ const PreviewButton = ({ buttonLoadingState, onClick }) => {
|
|||
const hasPermissions =
|
||||
selectedDataSource?.scope === 'global' && selectedDataSource?.type !== DATA_SOURCE_TYPE.SAMPLE
|
||||
? canUpdateDataSource(selectedQuery?.data_source_id) ||
|
||||
canReadDataSource(selectedQuery?.data_source_id) ||
|
||||
canDeleteDataSource()
|
||||
canReadDataSource(selectedQuery?.data_source_id) ||
|
||||
canDeleteDataSource()
|
||||
: true;
|
||||
const isPreviewQueryLoading = useStore((state) => state.queryPanel.isPreviewQueryLoading);
|
||||
const { t } = useTranslation();
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@ import CodeHinter from '@/AppBuilder/CodeEditor';
|
|||
import './workflows-query.scss';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
|
||||
|
||||
export function Workflows({ options, optionsChanged, currentState }) {
|
||||
const { moduleId } = useModuleContext();
|
||||
const [workflowOptions, setWorkflowOptions] = useState([]);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined);
|
||||
const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]);
|
||||
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
|
||||
|
||||
usePopoverObserver(
|
||||
document.getElementsByClassName('query-details')[0],
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { Confirm } from '@/Editor/Viewer/Confirm';
|
||||
// TODO: enable delete query confirmation popup
|
||||
import { debounce } from 'lodash';
|
||||
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
|
||||
|
||||
export const QueryCard = ({ dataQuery, darkMode = false, localDs }) => {
|
||||
const appId = useStore((state) => state.app.appId);
|
||||
const { moduleId } = useModuleContext();
|
||||
const appId = useStore((state) => state.appStore.modules[moduleId].app.appId);
|
||||
|
||||
const isQuerySelected = useStore((state) => state.queryPanel.isQuerySelected(dataQuery.id), shallow);
|
||||
const setSelectedQuery = useStore((state) => state.queryPanel.setSelectedQuery);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store';
|
|||
import { RIGHT_SIDE_BAR_TAB } from '@/AppBuilder/RightSideBar/rightSidebarConstants';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
export const ComponentConfigurationTab = ({ darkMode }) => {
|
||||
export const ComponentConfigurationTab = ({ darkMode, isModuleEditor }) => {
|
||||
const selectedComponentId = useStore((state) => state.selectedComponents?.[0], shallow);
|
||||
const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab);
|
||||
if (!selectedComponentId) {
|
||||
|
|
@ -17,6 +17,7 @@ export const ComponentConfigurationTab = ({ darkMode }) => {
|
|||
darkMode={darkMode}
|
||||
selectedComponentId={selectedComponentId}
|
||||
pages={[]}
|
||||
isModuleEditor={isModuleEditor}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import React, { useState } from 'react';
|
||||
import './styles.scss';
|
||||
|
||||
export const ComponentModuleTab = ({ onChangeTab }) => {
|
||||
const [activeTab, setActiveTab] = useState(1);
|
||||
|
||||
const handleChangeTab = (tab) => {
|
||||
setActiveTab(tab);
|
||||
onChangeTab(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tj-tabs-container-outer">
|
||||
<div className="tj-tabs-container">
|
||||
<button
|
||||
className={`tj-drawer-tabs-btn tj-text-xsm ${activeTab == 1 && 'tj-drawer-tabs-btn-active'}`}
|
||||
onClick={() => handleChangeTab(1)}
|
||||
data-cy="button-invite-with-email"
|
||||
>
|
||||
<span>Components</span>
|
||||
</button>
|
||||
<button
|
||||
className={`tj-drawer-tabs-btn tj-text-xsm ${activeTab == 2 && 'tj-drawer-tabs-btn-active'}`}
|
||||
onClick={() => handleChangeTab(2)}
|
||||
data-cy="button-upload-csv-file"
|
||||
>
|
||||
<span>Modules</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { ComponentModuleTab as default } from './ComponentModuleTab';
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue