commit
433461a347
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Thank you for your interest in Appsmith and taking the time to contribute on this project. 🙌
|
Thank you for your interest in Appsmith and taking the time to contribute on this project. 🙌
|
||||||
Appsmith is a project by developers for developers and there are a lot of ways you can contribute.
|
Appsmith is a project by developers for developers and there are a lot of ways you can contribute.
|
||||||
Feel free to propose changes to this document in a pull request.
|
If you don't know where to start contributing, ask us on our [Discord channel](https://discord.gg/WN8b2W8j).
|
||||||
|
|
||||||
## Code of conduct
|
## Code of conduct
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing
|
||||||
|
|
||||||
## How can I contribute?
|
## How can I contribute?
|
||||||
|
|
||||||
There are many ways in which we/one can to contribute to Appsmith. All contributions are highly appreciated.
|
There are many ways in which you can to contribute to Appsmith.
|
||||||
|
|
||||||
#### 🐛 Report a bug
|
#### 🐛 Report a bug
|
||||||
Report all issues through GitHub Issues using the [Report a Bug](https://github.com/appsmithorg/appsmith/issues/new?assignees=Nikhil-Nandagopal&labels=Bug%2C+High&template=---bug-report.md&title=%5BBug%5D) template.
|
Report all issues through GitHub Issues using the [Report a Bug](https://github.com/appsmithorg/appsmith/issues/new?assignees=Nikhil-Nandagopal&labels=Bug%2C+High&template=---bug-report.md&title=%5BBug%5D) template.
|
||||||
|
|
@ -24,4 +24,4 @@ File your feature request through GitHub Issues using the [Feature Request](http
|
||||||
In the process of shipping features quickly, we often forget to keep our docs up to date. You can help by suggesting improvements to our documentation or dive right in to our [Contribution Guide](contributions/docs/CONTRIBUTING.md)!
|
In the process of shipping features quickly, we often forget to keep our docs up to date. You can help by suggesting improvements to our documentation or dive right in to our [Contribution Guide](contributions/docs/CONTRIBUTING.md)!
|
||||||
|
|
||||||
#### ⚙️ Close a Bug / Feature issue
|
#### ⚙️ Close a Bug / Feature issue
|
||||||
We welcome contributions that help make appsmith bug free & improve the experience of our users. Check out our [Code Contribution Guide](contributions/CodeContributionsGuidelines.md) to begin.
|
We welcome contributions that help make appsmith bug free & improve the experience of our users. You can also find issues tagged [Good First Issues](https://github.com/appsmithorg/appsmith/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22+bug). Check out our [Code Contribution Guide](contributions/CodeContributionsGuidelines.md) to begin.
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,77 @@
|
||||||
{
|
{
|
||||||
"dsl": {
|
"dsl": {
|
||||||
"widgetName": "MainContainer",
|
"widgetName": "MainContainer",
|
||||||
"backgroundColor": "none",
|
"backgroundColor": "none",
|
||||||
"rightColumn": 1224,
|
"rightColumn": 1224,
|
||||||
"snapColumns": 16,
|
"snapColumns": 16,
|
||||||
"detachFromLayout": true,
|
"detachFromLayout": true,
|
||||||
"widgetId": "0",
|
"widgetId": "0",
|
||||||
"topRow": 0,
|
"topRow": 0,
|
||||||
"bottomRow": 1280,
|
"bottomRow": 1280,
|
||||||
"containerStyle": "none",
|
"containerStyle": "none",
|
||||||
"snapRows": 33,
|
"snapRows": 33,
|
||||||
"parentRowSpace": 1,
|
"parentRowSpace": 1,
|
||||||
"type": "CANVAS_WIDGET",
|
"type": "CANVAS_WIDGET",
|
||||||
"canExtend": true,
|
"canExtend": true,
|
||||||
"dynamicBindingPathList": [],
|
"version": 8,
|
||||||
"version": 6,
|
"minHeight": 1292,
|
||||||
"minHeight": 1292,
|
"parentColumnSpace": 1,
|
||||||
"parentColumnSpace": 1,
|
"dynamicBindingPathList": [],
|
||||||
|
"leftColumn": 0,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"isVisible": true,
|
||||||
|
"inputType": "TEXT",
|
||||||
|
"label": "",
|
||||||
|
"widgetName": "Input1",
|
||||||
|
"type": "INPUT_WIDGET",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 74,
|
||||||
|
"parentRowSpace": 40,
|
||||||
|
"leftColumn": 6,
|
||||||
|
"rightColumn": 11,
|
||||||
|
"topRow": 13,
|
||||||
|
"bottomRow": 14,
|
||||||
|
"parentId": "0",
|
||||||
|
"widgetId": "1bek8n8byg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"isVisible": true,
|
||||||
|
"label": "",
|
||||||
|
"selectionType": "SINGLE_SELECT",
|
||||||
|
"options": "",
|
||||||
|
"widgetName": "Dropdown1",
|
||||||
|
"defaultOptionValue": "VEG",
|
||||||
|
"type": "DROP_DOWN_WIDGET",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 74,
|
||||||
|
"parentRowSpace": 40,
|
||||||
"leftColumn": 0,
|
"leftColumn": 0,
|
||||||
"children": [
|
"rightColumn": 5,
|
||||||
{
|
"topRow": 15,
|
||||||
"isVisible": true,
|
"bottomRow": 16,
|
||||||
"inputType": "TEXT",
|
"parentId": "0",
|
||||||
"label": "",
|
"widgetId": "zd6jycngj7",
|
||||||
"widgetName": "Input1",
|
"dynamicBindingPathList": []
|
||||||
"type": "INPUT_WIDGET",
|
},
|
||||||
"isLoading": false,
|
{
|
||||||
"parentColumnSpace": 74,
|
"isVisible": true,
|
||||||
"parentRowSpace": 40,
|
"label": "Data",
|
||||||
"leftColumn": 4,
|
"widgetName": "Table1",
|
||||||
"rightColumn": 9,
|
"searchKey": "",
|
||||||
"topRow": 0,
|
"tableData": "[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]",
|
||||||
"bottomRow": 1,
|
"type": "TABLE_WIDGET",
|
||||||
"parentId": "0",
|
"isLoading": false,
|
||||||
"widgetId": "orkla1pg88"
|
"parentColumnSpace": 74,
|
||||||
},
|
"parentRowSpace": 40,
|
||||||
{
|
"leftColumn": 4,
|
||||||
"isVisible": true,
|
"rightColumn": 12,
|
||||||
"label": "",
|
"topRow": 19,
|
||||||
"selectionType": "SINGLE_SELECT",
|
"bottomRow": 26,
|
||||||
"options": "[\n {\n \"label\": \"Vegetarian\",\n \"value\": \"VEG\"\n },\n {\n \"label\": \"Non-Vegetarian\",\n \"value\": \"NON_VEG\"\n },\n {\n \"label\": \"Vegan\",\n \"value\": \"VEGAN\"\n }\n]",
|
"parentId": "0",
|
||||||
"widgetName": "Dropdown1",
|
"widgetId": "ei38nqop3s",
|
||||||
"defaultOptionValue": "VEG",
|
"dynamicBindingPathList": []
|
||||||
"type": "DROP_DOWN_WIDGET",
|
}
|
||||||
"isLoading": false,
|
]
|
||||||
"parentColumnSpace": 74,
|
}
|
||||||
"parentRowSpace": 40,
|
}
|
||||||
"leftColumn": 4,
|
|
||||||
"rightColumn": 9,
|
|
||||||
"topRow": 2,
|
|
||||||
"bottomRow": 3,
|
|
||||||
"parentId": "0",
|
|
||||||
"widgetId": "9iofg44qjm",
|
|
||||||
"dynamicBindingPathList": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"isVisible": true,
|
|
||||||
"defaultText": "This is the initial <b>content</b> of the editor",
|
|
||||||
"isDisabled": false,
|
|
||||||
"widgetName": "RichTextEditor1",
|
|
||||||
"isDefaultClickDisabled": true,
|
|
||||||
"type": "RICH_TEXT_EDITOR_WIDGET",
|
|
||||||
"isLoading": false,
|
|
||||||
"parentColumnSpace": 74,
|
|
||||||
"parentRowSpace": 40,
|
|
||||||
"leftColumn": 2,
|
|
||||||
"rightColumn": 10,
|
|
||||||
"topRow": 4,
|
|
||||||
"bottomRow": 9,
|
|
||||||
"parentId": "0",
|
|
||||||
"widgetId": "p4uowm5ds3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"isVisible": true,
|
|
||||||
"label": "Data",
|
|
||||||
"widgetName": "Table1",
|
|
||||||
"searchKey": "",
|
|
||||||
"tableData": "[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]",
|
|
||||||
"type": "TABLE_WIDGET",
|
|
||||||
"isLoading": false,
|
|
||||||
"parentColumnSpace": 74,
|
|
||||||
"parentRowSpace": 40,
|
|
||||||
"leftColumn": 3,
|
|
||||||
"rightColumn": 11,
|
|
||||||
"topRow": 12,
|
|
||||||
"bottomRow": 19,
|
|
||||||
"parentId": "0",
|
|
||||||
"widgetId": "iu9vqkj1rd",
|
|
||||||
"dynamicBindingPathList": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
"mongo-username": "cypress-test",
|
"mongo-username": "cypress-test",
|
||||||
"mongo-password": "RaopEmky505xYV4p",
|
"mongo-password": "RaopEmky505xYV4p",
|
||||||
"mongo-authenticationAuthtype": "SCRAM-SHA-1",
|
"mongo-authenticationAuthtype": "SCRAM-SHA-1",
|
||||||
"mongo-sslAuthtype": "No SSL",
|
|
||||||
"postgres-host": "postgres-test-db.cz8diybf9wdj.ap-south-1.rds.amazonaws.com",
|
"postgres-host": "postgres-test-db.cz8diybf9wdj.ap-south-1.rds.amazonaws.com",
|
||||||
"postgres-port": 5432,
|
"postgres-port": 5432,
|
||||||
"postgres-databaseName": "fakeapi",
|
"postgres-databaseName": "fakeapi",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
"value": "VEG"
|
"value": "VEG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "{{Table1.tableData[1].email}}",
|
"label": "{{Table1.tableData[2].email}}",
|
||||||
"value": "NONVEG"
|
"value": "NONVEG"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ describe("Binding the multiple Widgets and validating NavigateTo Page", function
|
||||||
it("Input widget test with default value from table widget", function() {
|
it("Input widget test with default value from table widget", function() {
|
||||||
cy.openPropertyPane("inputwidget");
|
cy.openPropertyPane("inputwidget");
|
||||||
cy.get(widgetsPage.defaultInput).type(testdata.defaultInputWidget);
|
cy.get(widgetsPage.defaultInput).type(testdata.defaultInputWidget);
|
||||||
cy.get(widgetsPage.actionSelect).click();
|
cy.get(widgetsPage.actionSelect)
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
cy.get(commonlocators.chooseAction)
|
cy.get(commonlocators.chooseAction)
|
||||||
.children()
|
.children()
|
||||||
.contains("Navigate To")
|
.contains("Navigate To")
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,88 @@
|
||||||
// const commonlocators = require("../../../locators/commonlocators.json");
|
const commonlocators = require("../../../locators/commonlocators.json");
|
||||||
// const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||||
// const dsl = require("../../../fixtures/rundsl.json");
|
const dsl = require("../../../fixtures/rundsl.json");
|
||||||
// const pages = require("../../../locators/Pages.json");
|
const pages = require("../../../locators/Pages.json");
|
||||||
// const widgetsPage = require("../../../locators/Widgets.json");
|
const widgetsPage = require("../../../locators/Widgets.json");
|
||||||
// const publish = require("../../../locators/publishWidgetspage.json");
|
const publish = require("../../../locators/publishWidgetspage.json");
|
||||||
// const queryLocators = require("../../../locators/QueryEditor.json");
|
const queryLocators = require("../../../locators/QueryEditor.json");
|
||||||
// const datasource = require("../../../locators/DatasourcesEditor.json");
|
const datasource = require("../../../locators/DatasourcesEditor.json");
|
||||||
// const apiwidget = require("../../../locators/apiWidgetslocator.json");
|
const apiwidget = require("../../../locators/apiWidgetslocator.json");
|
||||||
// const testdata = require("../../../fixtures/testdata.json");
|
const testdata = require("../../../fixtures/testdata.json");
|
||||||
|
|
||||||
// const pageid = "MyPage";
|
const pageid = "MyPage";
|
||||||
// let updatedName;
|
let updatedName;
|
||||||
// let datasourceName;
|
let datasourceName;
|
||||||
|
|
||||||
// describe("Binding the multiple widgets and validating default data", function() {
|
describe("Binding the multiple widgets and validating default data", function() {
|
||||||
// before(() => {
|
before(() => {
|
||||||
// cy.addDsl(dsl);
|
cy.addDsl(dsl);
|
||||||
// });
|
});
|
||||||
// it("Create a postgres datasource", function() {
|
it("Create a postgres datasource", function() {
|
||||||
// cy.NavigateToDatasourceEditor();
|
cy.NavigateToDatasourceEditor();
|
||||||
// cy.get(datasource.PostgreSQL).click();
|
cy.get(datasource.PostgreSQL).click();
|
||||||
|
|
||||||
// cy.getPluginFormsAndCreateDatasource();
|
cy.getPluginFormsAndCreateDatasource();
|
||||||
|
|
||||||
// cy.fillPostgresDatasourceForm();
|
cy.fillPostgresDatasourceForm();
|
||||||
|
|
||||||
// cy.testSaveDatasource();
|
cy.testSaveDatasource();
|
||||||
|
|
||||||
// cy.get("@createDatasource").then(httpResponse => {
|
cy.get("@createDatasource").then((httpResponse) => {
|
||||||
// datasourceName = httpResponse.response.body.data.name;
|
datasourceName = httpResponse.response.body.data.name;
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// it("Create and runs query", () => {
|
it("Create and runs query", () => {
|
||||||
// cy.NavigateToQueryEditor();
|
cy.NavigateToQueryEditor();
|
||||||
// cy.contains(".t--datasource-name", datasourceName)
|
cy.contains(".t--datasource-name", datasourceName)
|
||||||
// .find(queryLocators.createQuery)
|
.find(queryLocators.createQuery)
|
||||||
// .click();
|
.click();
|
||||||
|
|
||||||
// cy.get(queryLocators.templateMenu).click();
|
cy.get(queryLocators.templateMenu).click();
|
||||||
// cy.get(".CodeMirror textarea")
|
cy.get(".CodeMirror textarea")
|
||||||
// .first()
|
.first()
|
||||||
// .focus()
|
.focus()
|
||||||
// .type("select * from users limit 10");
|
.type("select * from users limit 10");
|
||||||
|
|
||||||
// cy.EvaluateCurrentValue("select * from users limit 10");
|
cy.EvaluateCurrentValue("select * from users limit 10");
|
||||||
// cy.runQuery();
|
cy.runQuery();
|
||||||
// });
|
});
|
||||||
|
|
||||||
// it("Button widget test with on action query run", function() {
|
it("Button widget test with on action query run", function() {
|
||||||
// cy.SearchEntityandOpen("Button1");
|
cy.SearchEntityandOpen("Button1");
|
||||||
// cy.executeDbQuery("Query1");
|
cy.executeDbQuery("Query1");
|
||||||
// cy.get(commonlocators.editPropCrossButton).click();
|
cy.get(commonlocators.editPropCrossButton).click();
|
||||||
// cy.wait("@updateLayout").should(
|
cy.wait("@updateLayout").should(
|
||||||
// "have.nested.property",
|
"have.nested.property",
|
||||||
// "response.body.responseMeta.status",
|
"response.body.responseMeta.status",
|
||||||
// 200,
|
200,
|
||||||
// );
|
);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// it("Input widget test with default value update with query data", function() {
|
it("Input widget test with default value update with query data", function() {
|
||||||
// cy.SearchEntityandOpen("Input1");
|
cy.SearchEntityandOpen("Input1");
|
||||||
// cy.get(widgetsPage.defaultInput).type(testdata.defaultInputQuery);
|
cy.get(widgetsPage.defaultInput).type(testdata.defaultInputQuery);
|
||||||
// cy.get(commonlocators.editPropCrossButton).click();
|
cy.get(commonlocators.editPropCrossButton).click();
|
||||||
// cy.wait("@updateLayout").should(
|
cy.wait("@updateLayout").should(
|
||||||
// "have.nested.property",
|
"have.nested.property",
|
||||||
// "response.body.responseMeta.status",
|
"response.body.responseMeta.status",
|
||||||
// 200,
|
200,
|
||||||
// );
|
);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// it("Publish App and validate loading functionalty", function() {
|
it("Publish App and validate loading functionalty", function() {
|
||||||
// cy.PublishtheApp();
|
cy.PublishtheApp();
|
||||||
// cy.wait(2000);
|
cy.wait(2000);
|
||||||
// cy.get(widgetsPage.widgetBtn)
|
cy.get(widgetsPage.widgetBtn)
|
||||||
// .first()
|
.first()
|
||||||
// .click({ force: true });
|
.click({ force: true });
|
||||||
// cy.wait("@postExecute").should(
|
cy.wait("@postExecute").should(
|
||||||
// "have.nested.property",
|
"have.nested.property",
|
||||||
// "response.body.responseMeta.status",
|
"response.body.responseMeta.status",
|
||||||
// 200,
|
200,
|
||||||
// );
|
);
|
||||||
// cy.get(publish.inputWidget + " " + "input")
|
cy.get(publish.inputWidget + " " + "input")
|
||||||
// .first()
|
.first()
|
||||||
// .invoke("attr", "value")
|
.invoke("attr", "value")
|
||||||
// .should("contain", "7");
|
.should("contain", "7");
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ describe("Binding the multiple widgets and validating default data", function()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
//To be enabled once the single select multi select issues are resolved
|
||||||
To be enabled once the single select multi select issues are resolved
|
|
||||||
it("Dropdown widget test with default value from table widget", function() {
|
it("Dropdown widget test with default value from table widget", function() {
|
||||||
cy.openPropertyPane("dropdownwidget");
|
cy.openPropertyPane("dropdownwidget");
|
||||||
cy.testJsontext("options", JSON.stringify(testdata.deafultDropDownWidget));
|
cy.testJsontext("options", JSON.stringify(testdata.deafultDropDownWidget));
|
||||||
|
|
@ -34,11 +33,10 @@ describe("Binding the multiple widgets and validating default data", function()
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
it("validation of default data displayed in all widgets based on row selected", function() {
|
it("validation of default data displayed in all widgets based on row selected", function() {
|
||||||
cy.isSelectRow(1);
|
cy.isSelectRow(1);
|
||||||
cy.readTabledataPublish("1", "0").then(tabData => {
|
cy.readTabledataPublish("1", "0").then((tabData) => {
|
||||||
const tabValue = tabData;
|
const tabValue = tabData;
|
||||||
expect(tabValue).to.be.equal("2736212");
|
expect(tabValue).to.be.equal("2736212");
|
||||||
cy.log("the value is" + tabValue);
|
cy.log("the value is" + tabValue);
|
||||||
|
|
@ -48,18 +46,17 @@ describe("Binding the multiple widgets and validating default data", function()
|
||||||
.invoke("attr", "value")
|
.invoke("attr", "value")
|
||||||
.should("contain", tabValue);
|
.should("contain", tabValue);
|
||||||
});
|
});
|
||||||
/*
|
|
||||||
cy.readTabledataPublish("1", "1").then(tabData => {
|
cy.readTabledataPublish("1", "1").then((tabData) => {
|
||||||
const tabValue = tabData;
|
const tabValue = tabData;
|
||||||
expect(tabValue).to.be.equal("lindsay.ferguson@reqres.in");
|
expect(tabValue).to.be.equal("lindsay.ferguson@reqres.in");
|
||||||
cy.log("the value is" + tabValue);
|
cy.log("the value is" + tabValue);
|
||||||
cy.get(widgetsPage.defaultSingleSelectValue)
|
cy.get(widgetsPage.defaultSingleSelectValue)
|
||||||
.invoke("text")
|
.invoke("text")
|
||||||
.then(text => {
|
.then((text) => {
|
||||||
const someText = text;
|
const someText = text;
|
||||||
expect(someText).to.equal(tabValue);
|
expect(someText).to.equal(tabValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,37 @@ describe("FilePicker Widget Functionality", function() {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.addDsl(dsl);
|
cy.addDsl(dsl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Create API to be used in Filepicker", function() {
|
||||||
|
cy.log("Login Successful");
|
||||||
|
cy.NavigateToAPI_Panel();
|
||||||
|
cy.log("Navigation to API Panel screen successful");
|
||||||
|
cy.CreateAPI("FirstAPI");
|
||||||
|
cy.log("Creation of FirstAPI Action successful");
|
||||||
|
cy.enterDatasourceAndPath(
|
||||||
|
this.data.paginationUrl,
|
||||||
|
this.data.paginationParam,
|
||||||
|
);
|
||||||
|
cy.SaveAndRunAPI();
|
||||||
|
});
|
||||||
|
|
||||||
it("FilePicker Widget Functionality", function() {
|
it("FilePicker Widget Functionality", function() {
|
||||||
cy.openPropertyPane("filepickerwidget");
|
cy.SearchEntityandOpen("FilePicker1");
|
||||||
|
cy.wait(1000);
|
||||||
//Checking the edit props for FilePicker and also the properties of FilePicker widget
|
//Checking the edit props for FilePicker and also the properties of FilePicker widget
|
||||||
|
|
||||||
cy.testCodeMirror("Upload Files");
|
cy.testCodeMirror("Upload Files");
|
||||||
cy.get(commonlocators.editPropCrossButton).click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("It checks the loading state of filepicker on call the action", function() {
|
it("It checks the loading state of filepicker on call the action", function() {
|
||||||
cy.openPropertyPane("filepickerwidget");
|
cy.SearchEntityandOpen("FilePicker1");
|
||||||
const fixturePath = "testFile.mov";
|
const fixturePath = "testFile.mov";
|
||||||
cy.getAlert(commonlocators.filePickerOnFilesSelected);
|
cy.addAPIFromLightningMenu("FirstAPI");
|
||||||
cy.get(commonlocators.filePickerButton).click();
|
cy.get(commonlocators.filePickerButton).click();
|
||||||
cy.get(commonlocators.filePickerInput)
|
cy.get(commonlocators.filePickerInput)
|
||||||
.first()
|
.first()
|
||||||
.attachFile(fixturePath);
|
.attachFile(fixturePath);
|
||||||
cy.get(commonlocators.filePickerUploadButton).click();
|
cy.get(commonlocators.filePickerUploadButton).click();
|
||||||
cy.get(".bp3-spinner").should("have.length", 1);
|
cy.get(".bp3-spinner").should("have.length", 1);
|
||||||
cy.wait("@updateLayout").should(
|
|
||||||
"have.nested.property",
|
|
||||||
"response.body.responseMeta.status",
|
|
||||||
200,
|
|
||||||
);
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get("button").contains("1 files selected");
|
cy.get("button").contains("1 files selected");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/// <reference types="Cypress" />
|
||||||
|
|
||||||
|
const homePage = require("../../../locators/HomePage.json");
|
||||||
|
|
||||||
|
describe("Org name validation spec", function() {
|
||||||
|
it("create org with leading space validation", function() {
|
||||||
|
cy.NavigateToHome();
|
||||||
|
cy.get(homePage.createOrg)
|
||||||
|
.should("be.visible")
|
||||||
|
.first()
|
||||||
|
.click({ force: true });
|
||||||
|
cy.xpath(homePage.inputOrgName)
|
||||||
|
.should("be.visible")
|
||||||
|
.type(" ");
|
||||||
|
cy.get(homePage.submit).should("be.disabled");
|
||||||
|
cy.xpath(homePage.cancelBtn).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("create org with special characters validation", function() {
|
||||||
|
cy.get(homePage.createOrg)
|
||||||
|
.should("be.visible")
|
||||||
|
.first()
|
||||||
|
.click({ force: true });
|
||||||
|
cy.xpath(homePage.inputOrgName)
|
||||||
|
.should("be.visible")
|
||||||
|
.type("Test & Org");
|
||||||
|
cy.get(homePage.submit).should("be.enabled");
|
||||||
|
cy.xpath(homePage.cancelBtn).click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
const commonlocators = require("../../../locators/commonlocators.json");
|
||||||
|
|
||||||
|
describe("Check for product updates button and modal", function() {
|
||||||
|
it("Check if we should show the product updates button and it opens the updates modal", function() {
|
||||||
|
cy.get(commonlocators.homeIcon).click({ force: true });
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
cy.window()
|
||||||
|
.its("store")
|
||||||
|
.invoke("getState")
|
||||||
|
.then((state) => {
|
||||||
|
const { releaseItems, newReleasesCount } = state.ui.releases;
|
||||||
|
if (Array.isArray(releaseItems) && releaseItems.length > 0) {
|
||||||
|
cy.get("[data-cy=t--product-updates-btn]")
|
||||||
|
.contains(newReleasesCount)
|
||||||
|
.click({ force: true });
|
||||||
|
cy.wait(500); // modal transition
|
||||||
|
cy.get(".bp3-dialog-container").contains("Product Updates");
|
||||||
|
cy.get("[data-cy=t--product-updates-close-btn]").click({
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
cy.wait(500); // modal transition
|
||||||
|
cy.get(".bp3-dialog-container").should("not.exist");
|
||||||
|
} else {
|
||||||
|
cy.get("[data-cy=t--product-updates-btn]").should("not.exist");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -6,13 +6,11 @@
|
||||||
"username": "input[name='datasourceConfiguration.authentication.username']",
|
"username": "input[name='datasourceConfiguration.authentication.username']",
|
||||||
"password": "input[name='datasourceConfiguration.authentication.password']",
|
"password": "input[name='datasourceConfiguration.authentication.password']",
|
||||||
"authenticationAuthtype": "[data-cy=datasourceConfiguration\\.authentication\\.authType]",
|
"authenticationAuthtype": "[data-cy=datasourceConfiguration\\.authentication\\.authType]",
|
||||||
"sslAuthtype": "[data-cy=datasourceConfiguration\\.connection\\.ssl\\.authType]",
|
|
||||||
"url": "input[name='datasourceConfiguration.url']",
|
"url": "input[name='datasourceConfiguration.url']",
|
||||||
"MongoDB": ".t--plugin-name:contains('MongoDB')",
|
"MongoDB": ".t--plugin-name:contains('MongoDB')",
|
||||||
"RESTAPI": ".t--plugin-name:contains('REST API')",
|
"RESTAPI": ".t--plugin-name:contains('REST API')",
|
||||||
"PostgreSQL": ".t--plugin-name:contains('PostgreSQL')",
|
"PostgreSQL": ".t--plugin-name:contains('PostgreSQL')",
|
||||||
"sectionAuthentication": "[data-cy=section-Authentication]",
|
"sectionAuthentication": "[data-cy=section-Authentication]",
|
||||||
"sectionSSL": "[data-cy=section-SSL\\ \\(optional\\)]",
|
|
||||||
"PostgresEntity": ".t--entity-name:contains(PostgreSQL)",
|
"PostgresEntity": ".t--entity-name:contains(PostgreSQL)",
|
||||||
"createQuerty": ".t--create-query",
|
"createQuerty": ".t--create-query",
|
||||||
"editDatasource": ".t--edit-datasource",
|
"editDatasource": ".t--edit-datasource",
|
||||||
|
|
|
||||||
|
|
@ -67,5 +67,7 @@
|
||||||
"uploadLogo": "//div/form/input",
|
"uploadLogo": "//div/form/input",
|
||||||
"removeLogo": ".remove-button a span",
|
"removeLogo": ".remove-button a span",
|
||||||
"generalTab": "//li//span[text()='General']",
|
"generalTab": "//li//span[text()='General']",
|
||||||
"membersTab": "//li//span[text()='Members']"
|
"membersTab": "//li//span[text()='Members']",
|
||||||
|
"cancelBtn": "//span[text()='Cancel']",
|
||||||
|
"submit": "button:contains('Submit')"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
app/client/cypress/manual_TestSuite/Clipboard_Copy_Spec.js
Normal file
51
app/client/cypress/manual_TestSuite/Clipboard_Copy_Spec.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
const dsl = require("../../../fixtures/tableWidgetDsl.json");
|
||||||
|
|
||||||
|
|
||||||
|
describe("Test for Clipboard Copy", function() {
|
||||||
|
it(" Clipboard copy on selecting a row ", function()
|
||||||
|
{
|
||||||
|
// Add a table widget
|
||||||
|
// Click on the Property Pane
|
||||||
|
// Naviagte to Action Items
|
||||||
|
// Click on "onRow Selection" Dropdown
|
||||||
|
// Select "Copy to Clipboard"
|
||||||
|
// Add Text to be copied
|
||||||
|
// Select a Row from the table
|
||||||
|
// Add an Input Widget
|
||||||
|
// Now paste the copied text
|
||||||
|
// Ensure the text the same as written
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it(" Clipboard copy by adding an action button", function()
|
||||||
|
{
|
||||||
|
// Add a table widget
|
||||||
|
// Click on the Property Pane
|
||||||
|
// Naviagte to Action Items
|
||||||
|
// Click on "Add button"
|
||||||
|
// Click on the dropdown
|
||||||
|
// Select on Copy to Clipboard
|
||||||
|
// Add a Text
|
||||||
|
// Click on the Action Button
|
||||||
|
// Add Input Widget
|
||||||
|
// Paste the text into the widget
|
||||||
|
// Ensure the text the same as written
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it(" Clipboard copy function by converting it to JS ", function()
|
||||||
|
{
|
||||||
|
// Add a table widget
|
||||||
|
// Click on the Property Pane
|
||||||
|
// Naviagte to Action Items
|
||||||
|
// Click on "Add button"
|
||||||
|
// Click on the dropdown
|
||||||
|
// Click on the Js Option
|
||||||
|
// Add Copy to Clipboard FUNTION
|
||||||
|
// Add a Text
|
||||||
|
// Click on the Action Button
|
||||||
|
// Add Input Widget
|
||||||
|
// Paste the text into the widget
|
||||||
|
// Ensure the text the same as written
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
|
||||||
it("Duplicating an application", function() {
|
|
||||||
// Navigate to home Page
|
|
||||||
// Click on any application action icon (Three dots)
|
|
||||||
// Click on "Duplicate" option
|
|
||||||
// Ensure the application gets copied
|
|
||||||
// Ensure the name is appended with the word "Copy"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
const homePage = require("../../../locators/HomePage.json");
|
||||||
|
|
||||||
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
|
||||||
it("Duplicating an application", function() {
|
it("Duplicating an application", function()
|
||||||
|
{
|
||||||
// Navigate to home Page
|
// Navigate to home Page
|
||||||
// Click on any application action icon (Three dots)
|
// Click on any application action icon (Three dots)
|
||||||
// Click on "Duplicate" option
|
// Click on "Duplicate" option
|
||||||
// Ensure the application gets copied
|
// Ensure the application gets copied
|
||||||
// Ensure the name is appended with the word "Copy"
|
// Ensure the name is appended with the word "Copy"
|
||||||
});
|
}
|
||||||
it("Deleting the duplicated Application ", function() {
|
)
|
||||||
|
it("Deleting the duplicated Application ", function()
|
||||||
|
{
|
||||||
// Navigate to home Page
|
// Navigate to home Page
|
||||||
// Click on any application action icon (Three dots)
|
// Click on any application action icon (Three dots)
|
||||||
// Click on "Duplicate" option
|
// Click on "Duplicate" option
|
||||||
|
|
@ -18,5 +21,38 @@ describe("Duplicate an application must duplicate every API ,Query widget and Da
|
||||||
// Click on Delete option
|
// Click on Delete option
|
||||||
// Click on "Are You Sure?" option
|
// Click on "Are You Sure?" option
|
||||||
// Ensure the App gets deleted
|
// Ensure the App gets deleted
|
||||||
});
|
}
|
||||||
});
|
)
|
||||||
|
|
||||||
|
it(" Ensure only the original application is deleted and copy of it exists", function()
|
||||||
|
{
|
||||||
|
// Navigate to home Page
|
||||||
|
// Create an Application
|
||||||
|
// Add a name to the application
|
||||||
|
// Navigate to home page
|
||||||
|
// Now click on the action (Three Dots)
|
||||||
|
// Select "Duplicate" option
|
||||||
|
// Ensure App is created with App name prefixed with Copy
|
||||||
|
// Click on Delete option of Original Application
|
||||||
|
// Click on "Are You Sure?" option
|
||||||
|
// Ensure only Original Application is deleted and not the child application
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it(" Ensure only the Duplicate application is deleted and original Application of it exists", function()
|
||||||
|
{
|
||||||
|
// Navigate to home Page
|
||||||
|
// Create an Application
|
||||||
|
// Add a name to the application
|
||||||
|
// Navigate to home page
|
||||||
|
// Now click on the action (Three Dots)
|
||||||
|
// Select "Duplicate" option
|
||||||
|
// Ensure App is created with App name prefixed with Copy
|
||||||
|
// Click on Delete option of Duplicate Application
|
||||||
|
// Click on "Are You Sure?" option
|
||||||
|
// Ensure only Duplicate Application is deleted and not the Orginal application
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
61
app/client/cypress/manual_TestSuite/Invite_flow_Spec.js
Normal file
61
app/client/cypress/manual_TestSuite/Invite_flow_Spec.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
const homePage = require("../../../locators/HomePage.json");
|
||||||
|
|
||||||
|
|
||||||
|
describe("adding role without Email Id", function() {
|
||||||
|
it("Empty Email ID Invite flow", function()
|
||||||
|
{
|
||||||
|
// Navigate to Home Page
|
||||||
|
// Click on "Share" option
|
||||||
|
// Add Role from the dropdown
|
||||||
|
// Ensure the "Invite" option is "Inactive"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it("Error message must be dispalyed to user on inappropriate Email ID", function()
|
||||||
|
{
|
||||||
|
// Navigate to Home Page
|
||||||
|
// Click on "Share" option
|
||||||
|
// Add inappropriate Email Id
|
||||||
|
// Select the "Role"
|
||||||
|
// Ensure the "Invite" option is "Inactive" and error message is displayed to user
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it("Clicking on the organisation list the user must be lead to organisation Station ", function()
|
||||||
|
{
|
||||||
|
// Navigate to Home Page
|
||||||
|
// Navigate to Organisation list
|
||||||
|
// Click on one of the organisation name
|
||||||
|
// Ensure user is directed to the organisation
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it("Admin can only assign another Admin ", function()
|
||||||
|
{
|
||||||
|
// Navigate to Organisation Setting
|
||||||
|
// Navigate to Members
|
||||||
|
// Navigate to roles
|
||||||
|
// Ensure your also an "Admin"
|
||||||
|
// Change the role "Admin"
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it("Ensure the user can not delete or create an application in the organisation", function()
|
||||||
|
{
|
||||||
|
// Navigate to Home page
|
||||||
|
// Navigate to Members
|
||||||
|
// Navigate to roles
|
||||||
|
// Ensure role is "App Viewer"
|
||||||
|
// Ensure user is not able to delete or add any user for the application
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
it("Ensure On invaild Email Id the box must get highlighted", function()
|
||||||
|
{
|
||||||
|
// Navigate to Home page
|
||||||
|
// Click on the Share option
|
||||||
|
// Ensure the pop up opens
|
||||||
|
// Enter an Invaild Email Id
|
||||||
|
// and ensure the Email ID box is highlight
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
34
app/client/cypress/manual_TestSuite/Login_Spec.js
Normal file
34
app/client/cypress/manual_TestSuite/Login_Spec.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const onboarding = require("../../../locators/Onboarding.json");
|
||||||
|
const explorer = require("../../../locators/explorerlocators.json");
|
||||||
|
const homePage = require("../../../locators/HomePage.json");
|
||||||
|
const loginPage = require("../../../locators/LoginPage.json");
|
||||||
|
|
||||||
|
describe("Onboarding flow", function()
|
||||||
|
{
|
||||||
|
it("Onboarding using Google Id ", function()
|
||||||
|
{
|
||||||
|
// Navigate to Login Page
|
||||||
|
// Click on "Sign In with Google"
|
||||||
|
// Ensure user is navigated to Google Account
|
||||||
|
// Do select the Google Id
|
||||||
|
// Ensure user is navigated into the Appsmith
|
||||||
|
// Click on the icon on the right with single letter (Profile)
|
||||||
|
// Check the Email Id
|
||||||
|
// Click on Logout
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it("Onboarding using Github ID ", function()
|
||||||
|
{
|
||||||
|
// Navigate to Login Page
|
||||||
|
// Click on "Sign In with Github"
|
||||||
|
// Ensure user is navigated to Github Account
|
||||||
|
// Do select the Github Id
|
||||||
|
// Ensure user is navigated into the Appsmith
|
||||||
|
// Click on the icon on the right with single letter (Profile)
|
||||||
|
// Check the Email Id
|
||||||
|
// Click on Logout
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Deletion of organisational Logo ", function() {
|
|
||||||
it(" org logo upload ", function()
|
|
||||||
{
|
|
||||||
//Click on the dropdown next to organisational Name
|
|
||||||
// Navigate between tabs
|
|
||||||
// Naviagte to General Tab
|
|
||||||
// Add an Organisational Logo
|
|
||||||
// Wait until it loads
|
|
||||||
// Switch between Tabs
|
|
||||||
// Click on the remove Icon
|
|
||||||
//Ensure the organisational Logo is deleted
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
|
|
||||||
describe("insert organisational Logo ", function() {
|
|
||||||
it(" org logo upload ", function()
|
|
||||||
{
|
|
||||||
//Click on the dropdown next to organisational Name
|
|
||||||
// Navigate between tabs
|
|
||||||
// Naviagte to General Tab
|
|
||||||
// Add an Organisational Logo
|
|
||||||
//Wait until it loads
|
|
||||||
// Switch between Tabs
|
|
||||||
// Navigate to General Tab and ensure the logo exsits
|
|
||||||
//navigate back to Homepage
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
describe("Checking for error message on Organisation Name ", function() {
|
|
||||||
it("Ensure of Inactive Submit button ", function() {
|
|
||||||
// Navigate to home Page
|
|
||||||
// Click on Create Organisation
|
|
||||||
// Type "Space" as first character
|
|
||||||
// Ensure "Submit" button does not get Active
|
|
||||||
// Now click on "X" (Close icon) ensure the pop up closes
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -43,5 +43,35 @@ describe("Checking for error message on Organisation Name ", function() {
|
||||||
// Ensure the application can be created with the same name
|
// Ensure the application can be created with the same name
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it("User must not be able to add empty organisation name", function()
|
||||||
|
{
|
||||||
|
// Navigate to home Page
|
||||||
|
// Click on the "Create Organisation" button
|
||||||
|
// Ensure "Organisation Name" field is empty
|
||||||
|
// Ensure "Submit" is inactive
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it("Cancel creating an Organisation when the Organisation name is empty", function()
|
||||||
|
{
|
||||||
|
// Navigate to home Page
|
||||||
|
// Click on the "Create Organisation" button
|
||||||
|
// Ensure "Organisation Name" field is empty
|
||||||
|
// Click on "Cancel" option
|
||||||
|
// Observe the organisation is not created
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
it("Cancel creating an Organisation when the Organisation name is dually filled", function()
|
||||||
|
{
|
||||||
|
// Navigate to home Page
|
||||||
|
// Click on the "Create Organisation" button
|
||||||
|
// Ensure "Organisation Name" field is enterd respectively
|
||||||
|
// Click on "Cancel" option
|
||||||
|
// Observe the organisation is not created
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
describe("Reuse the name of the deleted application name inside the same organisation", function() {
|
|
||||||
it("Reuse the name of the deleted application name ", function() {
|
|
||||||
// Navigate to home Page
|
|
||||||
// Create an Application by name "XYZ"
|
|
||||||
// Add some widgets
|
|
||||||
// Navigate back to the application
|
|
||||||
// Delete the Application
|
|
||||||
// Click on "Create New" option under samee organisation
|
|
||||||
// Enter the name "XYZ"
|
|
||||||
// Ensure the application can be created with the same name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Shared user icon ", function() {
|
|
||||||
it(" User Icon is disaplyed to user ", function()
|
|
||||||
{
|
|
||||||
// Navigate to home Page
|
|
||||||
//Click on Share Icon
|
|
||||||
// Click on Field to add an Email Id
|
|
||||||
// Click on the Roles field
|
|
||||||
// Add an role from the Dropdown
|
|
||||||
// CLick on Invite
|
|
||||||
//Now observe the icon next to the Share Icon
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
const homePage = require("../../../locators/HomePage.json");
|
|
||||||
|
|
||||||
describe("Adding Special Character ", function() {
|
|
||||||
it("Adding Special Character ", function() {
|
|
||||||
// Navigate to home Page
|
|
||||||
// Click on Create Organisation
|
|
||||||
// Add special as first character
|
|
||||||
// Ensure "Submit" get Active
|
|
||||||
// Now click outside and ensure the pop up closes
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
const dsl = require("../../../fixtures/tableWidgetDsl.json");
|
|
||||||
|
|
||||||
|
|
||||||
describe("Test for Table Filter ", function() {
|
|
||||||
it("Table Filter", function()
|
|
||||||
{
|
|
||||||
//Add a table
|
|
||||||
// click on the column action item
|
|
||||||
// Click on Select a datatype
|
|
||||||
// Click on Filter option
|
|
||||||
// ensure to add filter
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1262,7 +1262,9 @@ Cypress.Commands.add("togglebarDisable", (value) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("getAlert", (alertcss) => {
|
Cypress.Commands.add("getAlert", (alertcss) => {
|
||||||
cy.get(commonlocators.dropdownSelectButton).click({ force: true });
|
cy.get(commonlocators.dropdownSelectButton)
|
||||||
|
.first()
|
||||||
|
.click({ force: true });
|
||||||
cy.get(widgetsPage.menubar)
|
cy.get(widgetsPage.menubar)
|
||||||
.contains("Show Message")
|
.contains("Show Message")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
|
@ -1279,6 +1281,19 @@ Cypress.Commands.add("getAlert", (alertcss) => {
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("addAPIFromLightningMenu", (ApiName) => {
|
||||||
|
cy.get(commonlocators.dropdownSelectButton)
|
||||||
|
.click({ force: true })
|
||||||
|
.get("ul.bp3-menu")
|
||||||
|
.children()
|
||||||
|
.contains("Call An API")
|
||||||
|
.click({ force: true })
|
||||||
|
.get("ul.bp3-menu")
|
||||||
|
.children()
|
||||||
|
.contains(ApiName)
|
||||||
|
.click({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("radioInput", (index, text) => {
|
Cypress.Commands.add("radioInput", (index, text) => {
|
||||||
cy.get(widgetsPage.RadioInput)
|
cy.get(widgetsPage.RadioInput)
|
||||||
.eq(index)
|
.eq(index)
|
||||||
|
|
@ -1424,17 +1439,10 @@ Cypress.Commands.add("fillMongoDatasourceForm", () => {
|
||||||
cy.get(datasourceEditor["password"]).type(
|
cy.get(datasourceEditor["password"]).type(
|
||||||
datasourceFormData["mongo-password"],
|
datasourceFormData["mongo-password"],
|
||||||
);
|
);
|
||||||
|
|
||||||
cy.get(datasourceEditor.sectionSSL).click();
|
|
||||||
cy.get(datasourceEditor["authenticationAuthtype"]).click();
|
cy.get(datasourceEditor["authenticationAuthtype"]).click();
|
||||||
cy.contains(datasourceFormData["mongo-authenticationAuthtype"]).click({
|
cy.contains(datasourceFormData["mongo-authenticationAuthtype"]).click({
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get(datasourceEditor["sslAuthtype"]).click();
|
|
||||||
cy.contains(datasourceFormData["mongo-sslAuthtype"]).click({
|
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("fillPostgresDatasourceForm", () => {
|
Cypress.Commands.add("fillPostgresDatasourceForm", () => {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { BatchAction, batchAction } from "actions/batchActions";
|
||||||
|
|
||||||
export const updateWidgetPropertyRequest = (
|
export const updateWidgetPropertyRequest = (
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
propertyName: string,
|
propertyPath: string,
|
||||||
propertyValue: any,
|
propertyValue: any,
|
||||||
renderMode: RenderMode,
|
renderMode: RenderMode,
|
||||||
): ReduxAction<UpdateWidgetPropertyRequestPayload> => {
|
): ReduxAction<UpdateWidgetPropertyRequestPayload> => {
|
||||||
|
|
@ -12,7 +12,7 @@ export const updateWidgetPropertyRequest = (
|
||||||
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST,
|
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST,
|
||||||
payload: {
|
payload: {
|
||||||
widgetId,
|
widgetId,
|
||||||
propertyName,
|
propertyPath,
|
||||||
propertyValue,
|
propertyValue,
|
||||||
renderMode,
|
renderMode,
|
||||||
},
|
},
|
||||||
|
|
@ -21,29 +21,49 @@ export const updateWidgetPropertyRequest = (
|
||||||
|
|
||||||
export const updateWidgetProperty = (
|
export const updateWidgetProperty = (
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
propertyName: string,
|
updates: Record<string, unknown>,
|
||||||
propertyValue: any,
|
|
||||||
): BatchAction<UpdateWidgetPropertyPayload> => {
|
): BatchAction<UpdateWidgetPropertyPayload> => {
|
||||||
return batchAction({
|
return batchAction({
|
||||||
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
|
type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
|
||||||
payload: {
|
payload: {
|
||||||
widgetId,
|
widgetId,
|
||||||
propertyName,
|
updates,
|
||||||
propertyValue,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const batchUpdateWidgetProperty = (
|
||||||
|
widgetId: string,
|
||||||
|
updates: Record<string, unknown>,
|
||||||
|
): ReduxAction<UpdateWidgetPropertyPayload> => ({
|
||||||
|
type: ReduxActionTypes.BATCH_UPDATE_WIDGET_PROPERTY,
|
||||||
|
payload: {
|
||||||
|
widgetId,
|
||||||
|
updates,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const deleteWidgetProperty = (
|
||||||
|
widgetId: string,
|
||||||
|
propertyPath: string,
|
||||||
|
): ReduxAction<DeleteWidgetPropertyPayload> => ({
|
||||||
|
type: ReduxActionTypes.DELETE_WIDGET_PROPERTY,
|
||||||
|
payload: {
|
||||||
|
widgetId,
|
||||||
|
propertyPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const setWidgetDynamicProperty = (
|
export const setWidgetDynamicProperty = (
|
||||||
widgetId: string,
|
widgetId: string,
|
||||||
propertyName: string,
|
propertyPath: string,
|
||||||
isDynamic: boolean,
|
isDynamic: boolean,
|
||||||
): ReduxAction<SetWidgetDynamicPropertyPayload> => {
|
): ReduxAction<SetWidgetDynamicPropertyPayload> => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY,
|
type: ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY,
|
||||||
payload: {
|
payload: {
|
||||||
widgetId,
|
widgetId,
|
||||||
propertyName,
|
propertyPath,
|
||||||
isDynamic,
|
isDynamic,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -51,19 +71,23 @@ export const setWidgetDynamicProperty = (
|
||||||
|
|
||||||
export interface UpdateWidgetPropertyRequestPayload {
|
export interface UpdateWidgetPropertyRequestPayload {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
propertyName: string;
|
propertyPath: string;
|
||||||
propertyValue: any;
|
propertyValue: any;
|
||||||
renderMode: RenderMode;
|
renderMode: RenderMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWidgetPropertyPayload {
|
export interface UpdateWidgetPropertyPayload {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
propertyName: string;
|
updates: Record<string, unknown>;
|
||||||
propertyValue: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetWidgetDynamicPropertyPayload {
|
export interface SetWidgetDynamicPropertyPayload {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
propertyName: string;
|
propertyPath: string;
|
||||||
isDynamic: boolean;
|
isDynamic: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteWidgetPropertyPayload {
|
||||||
|
widgetId: string;
|
||||||
|
propertyPath: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,11 @@ export const updateCurrentPage = (id: string) => ({
|
||||||
payload: { id },
|
payload: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateCanvas = (
|
export const initCanvasLayout = (
|
||||||
payload: UpdateCanvasPayload,
|
payload: UpdateCanvasPayload,
|
||||||
): ReduxAction<UpdateCanvasPayload> => {
|
): ReduxAction<UpdateCanvasPayload> => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.UPDATE_CANVAS,
|
type: ReduxActionTypes.INIT_CANVAS_LAYOUT,
|
||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
|
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
|
||||||
} from "constants/ApiConstants";
|
} from "constants/ApiConstants";
|
||||||
import axios, { AxiosPromise, CancelTokenSource } from "axios";
|
import axios, { AxiosPromise, CancelTokenSource } from "axios";
|
||||||
import { Action } from "entities/Action";
|
import { Action, ActionViewMode } from "entities/Action";
|
||||||
|
|
||||||
export interface CreateActionRequest<T> extends APIRequest {
|
export interface CreateActionRequest<T> extends APIRequest {
|
||||||
datasourceId: string;
|
datasourceId: string;
|
||||||
|
|
@ -127,7 +127,7 @@ class ActionAPI extends API {
|
||||||
|
|
||||||
static fetchActionsForViewMode(
|
static fetchActionsForViewMode(
|
||||||
applicationId: string,
|
applicationId: string,
|
||||||
): AxiosPromise<GenericApiResponse<Action[]>> {
|
): AxiosPromise<GenericApiResponse<ActionViewMode[]>> {
|
||||||
return API.get(`${ActionAPI.url}/view`, { applicationId });
|
return API.get(`${ActionAPI.url}/view`, { applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
BIN
app/client/src/assets/images/Github_inverted.png
Normal file
BIN
app/client/src/assets/images/Github_inverted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 335 B |
BIN
app/client/src/assets/images/invalid-page.png
Normal file
BIN
app/client/src/assets/images/invalid-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -58,6 +58,7 @@ type ButtonProps = CommonComponentProps & {
|
||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
href?: string;
|
href?: string;
|
||||||
tag?: "a" | "button";
|
tag?: "a" | "button";
|
||||||
|
type?: "submit" | "reset" | "button";
|
||||||
};
|
};
|
||||||
|
|
||||||
const stateStyles = (
|
const stateStyles = (
|
||||||
|
|
|
||||||
|
|
@ -269,13 +269,15 @@ export const EditableText = (props: EditableTextProps) => {
|
||||||
onCancel={onConfirm}
|
onCancel={onConfirm}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconWrapper className="icon-wrapper">
|
{savingState === SavingState.STARTED ? (
|
||||||
{savingState === SavingState.STARTED ? (
|
<IconWrapper className="icon-wrapper">
|
||||||
<Spinner size={IconSize.XL} />
|
<Spinner size={IconSize.XL} />
|
||||||
) : value ? (
|
</IconWrapper>
|
||||||
|
) : value && !props.hideEditIcon ? (
|
||||||
|
<IconWrapper className="icon-wrapper">
|
||||||
<Icon name={iconName} size={IconSize.XL} />
|
<Icon name={iconName} size={IconSize.XL} />
|
||||||
) : null}
|
</IconWrapper>
|
||||||
</IconWrapper>
|
) : null}
|
||||||
</TextContainer>
|
</TextContainer>
|
||||||
{isEditing && !!isInvalid ? (
|
{isEditing && !!isInvalid ? (
|
||||||
<Text className="error-message" type={TextType.P2}>
|
<Text className="error-message" type={TextType.P2}>
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ const Container = styled.div<{
|
||||||
savingState: SavingState;
|
savingState: SavingState;
|
||||||
isInvalid: boolean;
|
isInvalid: boolean;
|
||||||
}>`
|
}>`
|
||||||
|
position: relative;
|
||||||
.editable-text-container {
|
.editable-text-container {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&&& .${Classes.EDITABLE_TEXT}, .icon-wrapper {
|
&&& .${Classes.EDITABLE_TEXT}, .icon-wrapper {
|
||||||
padding: 5px 10px;
|
padding: 5px 0px;
|
||||||
height: 25px;
|
height: 31px;
|
||||||
background-color: ${(props) =>
|
background-color: ${(props) =>
|
||||||
(props.isInvalid && props.isEditing) ||
|
(props.isInvalid && props.isEditing) ||
|
||||||
props.savingState === SavingState.ERROR
|
props.savingState === SavingState.ERROR
|
||||||
|
|
@ -29,15 +30,14 @@ const Container = styled.div<{
|
||||||
: "transparent"};
|
: "transparent"};
|
||||||
}
|
}
|
||||||
|
|
||||||
&&&& .${Classes.EDITABLE_TEXT} {
|
&&&& .${Classes.EDITABLE_TEXT}:hover {
|
||||||
${(props) =>
|
${(props) =>
|
||||||
!props.isEditing
|
!props.isEditing
|
||||||
? `
|
? `
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
max-width: 194px;
|
||||||
`
|
`
|
||||||
: null}
|
: null}
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +47,8 @@ const Container = styled.div<{
|
||||||
!props.isEditing
|
!props.isEditing
|
||||||
? `
|
? `
|
||||||
min-width: 0px !important;
|
min-width: 0px !important;
|
||||||
|
height: auto !important;
|
||||||
|
line-height: ${props.theme.typography.h4.lineHeight}px !important;
|
||||||
`
|
`
|
||||||
: null}
|
: null}
|
||||||
}
|
}
|
||||||
|
|
@ -57,15 +59,15 @@ const Container = styled.div<{
|
||||||
font-size: ${(props) => props.theme.typography.h4.fontSize}px;
|
font-size: ${(props) => props.theme.typography.h4.fontSize}px;
|
||||||
line-height: ${(props) => props.theme.typography.h4.lineHeight}px;
|
line-height: ${(props) => props.theme.typography.h4.lineHeight}px;
|
||||||
letter-spacing: ${(props) => props.theme.typography.h4.letterSpacing}px;
|
letter-spacing: ${(props) => props.theme.typography.h4.letterSpacing}px;
|
||||||
font-weight: ${(props) => props.theme.typography.h4.fontWeight}px;
|
font-weight: ${(props) => props.theme.typography.h4.fontWeight};
|
||||||
}
|
padding-right: 0px;
|
||||||
|
|
||||||
.error-message {
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-wrapper {
|
.icon-wrapper {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,14 @@ const StyledInput = styled.input<
|
||||||
background-color: ${(props) => props.inputStyle.bgColor};
|
background-color: ${(props) => props.inputStyle.bgColor};
|
||||||
color: ${(props) => props.inputStyle.color};
|
color: ${(props) => props.inputStyle.color};
|
||||||
|
|
||||||
|
&:-internal-autofill-selected,
|
||||||
|
&:-webkit-autofill,
|
||||||
|
&:-webkit-autofill:hover,
|
||||||
|
&:-webkit-autofill:focus {
|
||||||
|
-webkit-box-shadow: 0 0 0 30px ${(props) => props.inputStyle.bgColor} inset !important;
|
||||||
|
-webkit-text-fill-color: ${(props) => props.inputStyle.color} !important;
|
||||||
|
}
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: ${(props) => props.theme.colors.textInput.placeholder};
|
color: ${(props) => props.theme.colors.textInput.placeholder};
|
||||||
}
|
}
|
||||||
|
|
@ -199,3 +207,5 @@ const TextInput = forwardRef(
|
||||||
TextInput.displayName = "TextInput";
|
TextInput.displayName = "TextInput";
|
||||||
|
|
||||||
export default TextInput;
|
export default TextInput;
|
||||||
|
|
||||||
|
export type InputType = "text" | "password" | "number" | "email" | "tel";
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,8 @@ export const Toaster = {
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
toastId: toastId,
|
toastId: toastId,
|
||||||
pauseOnHover: true,
|
pauseOnHover: !config.dispatchableAction && !config.hideProgressBar,
|
||||||
|
pauseOnFocusLoss: !config.dispatchableAction && !config.hideProgressBar,
|
||||||
autoClose: false,
|
autoClose: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
hideProgressBar: config.hideProgressBar,
|
hideProgressBar: config.hideProgressBar,
|
||||||
|
|
|
||||||
34
app/client/src/components/ads/formFields/FieldError.tsx
Normal file
34
app/client/src/components/ads/formFields/FieldError.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { IntentColors } from "constants/DefaultTheme";
|
||||||
|
// Note: This component is only for the input fields which donot have the
|
||||||
|
// popover error tooltip. This is also only for Appsmith components
|
||||||
|
// Not to be used in widgets / canvas
|
||||||
|
|
||||||
|
const StyledError = styled.span<{ show: boolean }>`
|
||||||
|
text-align: left;
|
||||||
|
color: ${IntentColors.danger};
|
||||||
|
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
||||||
|
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
margin-top: ${(props) => props.theme.spaces[1]}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type FormFieldErrorProps = {
|
||||||
|
error?: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormFieldError = (props: FormFieldErrorProps) => {
|
||||||
|
return (
|
||||||
|
<StyledError
|
||||||
|
className={props.className ? props.className : undefined}
|
||||||
|
show={!!props.error}
|
||||||
|
>
|
||||||
|
{props.error || " "}
|
||||||
|
</StyledError>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormFieldError;
|
||||||
22
app/client/src/components/ads/formFields/FormGroup.tsx
Normal file
22
app/client/src/components/ads/formFields/FormGroup.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { FormGroup, Classes } from "@blueprintjs/core";
|
||||||
|
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||||
|
type FormGroupProps = {
|
||||||
|
fill?: boolean;
|
||||||
|
};
|
||||||
|
const StyledFormGroup = styled(FormGroup)<FormGroupProps>`
|
||||||
|
& {
|
||||||
|
width: ${(props) => (props.fill ? "100%" : "auto")};
|
||||||
|
&.${Classes.FORM_GROUP} {
|
||||||
|
margin: 0 0 ${(props) => props.theme.spaces[5]}px;
|
||||||
|
}
|
||||||
|
&.${Classes.FORM_GROUP} .${Classes.FORM_HELPER_TEXT} {
|
||||||
|
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
||||||
|
}
|
||||||
|
&.${Classes.FORM_GROUP} .${Classes.LABEL} {
|
||||||
|
${(props) => getTypographyByKey(props, "h5")}
|
||||||
|
color: ${(props) => props.theme.colors.textInput.normal.text};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export default StyledFormGroup;
|
||||||
85
app/client/src/components/ads/formFields/FormMessage.tsx
Normal file
85
app/client/src/components/ads/formFields/FormMessage.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Intent } from "constants/DefaultTheme";
|
||||||
|
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export type MessageAction = {
|
||||||
|
url?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
text: string;
|
||||||
|
intent: Intent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledMessage = styled.div<{ intent: Intent }>`
|
||||||
|
& {
|
||||||
|
${(props) => getTypographyByKey(props, "p1")}
|
||||||
|
width: 100%;
|
||||||
|
padding: ${(props) => props.theme.spaces[4]}px;
|
||||||
|
color: ${(props) => props.theme.colors.formMessage.text[props.intent]};
|
||||||
|
background-color: ${(props) =>
|
||||||
|
props.theme.colors.formMessage.background[props.intent]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledAction = styled.div<{ intent: Intent }>`
|
||||||
|
margin-top: ${(props) => props.theme.spaces[5]}px;
|
||||||
|
${(props) => getTypographyByKey(props, "h5")}
|
||||||
|
font-weight: 600;
|
||||||
|
& a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: ${(props) => props.theme.colors.formMessage.text[props.intent]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionButton = (props: MessageAction) => {
|
||||||
|
if (props.url) {
|
||||||
|
const isExternal = props.url.indexOf("//") !== -1;
|
||||||
|
return (
|
||||||
|
<StyledAction intent={props.intent}>
|
||||||
|
{isExternal ? (
|
||||||
|
<a href={props.url} target="_blank" rel="noreferrer">
|
||||||
|
{props.text}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<Link to={props.url}>{props.text}</Link>
|
||||||
|
)}
|
||||||
|
</StyledAction>
|
||||||
|
);
|
||||||
|
} else if (props.onClick) {
|
||||||
|
return (
|
||||||
|
<StyledAction onClick={props.onClick} intent={props.intent}>
|
||||||
|
{props.text}
|
||||||
|
</StyledAction>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormMessageProps = {
|
||||||
|
intent: Intent;
|
||||||
|
message: string;
|
||||||
|
actions?: MessageAction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormMessage = (props: FormMessageProps) => {
|
||||||
|
const actions =
|
||||||
|
props.actions &&
|
||||||
|
props.actions.map((action) => (
|
||||||
|
<ActionButton key={action.text} {...action} intent={props.intent} />
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<StyledMessage intent={props.intent} className="form-message-container">
|
||||||
|
{props.message}
|
||||||
|
{actions && <ActionsContainer>{actions}</ActionsContainer>}
|
||||||
|
</StyledMessage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormMessage;
|
||||||
45
app/client/src/components/ads/formFields/TextField.tsx
Normal file
45
app/client/src/components/ads/formFields/TextField.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
Field,
|
||||||
|
WrappedFieldMetaProps,
|
||||||
|
WrappedFieldInputProps,
|
||||||
|
} from "redux-form";
|
||||||
|
import InputComponent, { InputType } from "../TextInput";
|
||||||
|
import { Intent } from "constants/DefaultTheme";
|
||||||
|
import FormFieldError from "./FieldError";
|
||||||
|
|
||||||
|
const renderComponent = (
|
||||||
|
componentProps: FormTextFieldProps & {
|
||||||
|
meta: Partial<WrappedFieldMetaProps>;
|
||||||
|
input: Partial<WrappedFieldInputProps>;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const showError = componentProps.meta.touched && !componentProps.meta.active;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<InputComponent {...componentProps} {...componentProps.input} fill />
|
||||||
|
<FormFieldError error={showError && componentProps.meta.error} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormTextFieldProps = {
|
||||||
|
name: string;
|
||||||
|
placeholder: string;
|
||||||
|
type?: InputType;
|
||||||
|
label?: string;
|
||||||
|
intent?: Intent;
|
||||||
|
disabled?: boolean;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormTextField = (props: FormTextFieldProps) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Field component={renderComponent} {...props} asyncControl />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormTextField;
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
import React, { createRef, useEffect, useState } from "react";
|
import React, { createRef, useEffect, useState } from "react";
|
||||||
import { Tooltip } from "@blueprintjs/core";
|
import { Tooltip } from "@blueprintjs/core";
|
||||||
import { CellWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
import { CellWrapper } from "components/designSystems/appsmith/TableStyledWrappers";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const TooltipContentWrapper = styled.div<{ width: number }>`
|
||||||
|
word-break: break-all;
|
||||||
|
max-width: ${(props) => props.width}px;
|
||||||
|
`;
|
||||||
|
|
||||||
const AutoToolTipComponent = (props: {
|
const AutoToolTipComponent = (props: {
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
|
tableWidth?: number;
|
||||||
}) => {
|
}) => {
|
||||||
const ref = createRef<HTMLDivElement>();
|
const ref = createRef<HTMLDivElement>();
|
||||||
const [useToolTip, updateToolTip] = useState(false);
|
const [useToolTip, updateToolTip] = useState(false);
|
||||||
|
|
@ -23,7 +30,11 @@ const AutoToolTipComponent = (props: {
|
||||||
<Tooltip
|
<Tooltip
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
hoverOpenDelay={1000}
|
hoverOpenDelay={1000}
|
||||||
content={props.title}
|
content={
|
||||||
|
<TooltipContentWrapper width={(props.tableWidth || 300) - 32}>
|
||||||
|
{props.title}
|
||||||
|
</TooltipContentWrapper>
|
||||||
|
}
|
||||||
position="top"
|
position="top"
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ const StyledRemoveIcon = styled(
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&.hide-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const LabelWrapper = styled.div`
|
const LabelWrapper = styled.div`
|
||||||
|
|
@ -246,7 +249,7 @@ const RenderOptions = (props: {
|
||||||
(i) => i.value === props.value,
|
(i) => i.value === props.value,
|
||||||
);
|
);
|
||||||
if (selectedOptions && selectedOptions.length) {
|
if (selectedOptions && selectedOptions.length) {
|
||||||
selectValue(selectedOptions[0].value);
|
selectValue(selectedOptions[0].label);
|
||||||
} else {
|
} else {
|
||||||
selectValue(props.placeholder);
|
selectValue(props.placeholder);
|
||||||
}
|
}
|
||||||
|
|
@ -290,6 +293,7 @@ type CascadeFieldProps = {
|
||||||
value: any;
|
value: any;
|
||||||
operator: Operator;
|
operator: Operator;
|
||||||
index: number;
|
index: number;
|
||||||
|
hasAnyFilters: boolean;
|
||||||
applyFilter: (filter: ReactTableFilter, index: number) => void;
|
applyFilter: (filter: ReactTableFilter, index: number) => void;
|
||||||
removeFilter: (index: number) => void;
|
removeFilter: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
@ -447,7 +451,7 @@ const CascadeField = (props: CascadeFieldProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Fields = (props: CascadeFieldProps & { state: CascadeFieldState }) => {
|
const Fields = (props: CascadeFieldProps & { state: CascadeFieldState }) => {
|
||||||
const { index, removeFilter, applyFilter } = props;
|
const { index, removeFilter, applyFilter, hasAnyFilters } = props;
|
||||||
const [state, dispatch] = React.useReducer(CaseCaseFieldReducer, props.state);
|
const [state, dispatch] = React.useReducer(CaseCaseFieldReducer, props.state);
|
||||||
const handleRemoveFilter = () => {
|
const handleRemoveFilter = () => {
|
||||||
dispatch({ type: CascadeFieldActionTypes.DELETE_FILTER });
|
dispatch({ type: CascadeFieldActionTypes.DELETE_FILTER });
|
||||||
|
|
@ -515,7 +519,9 @@ const Fields = (props: CascadeFieldProps & { state: CascadeFieldState }) => {
|
||||||
height={16}
|
height={16}
|
||||||
width={16}
|
width={16}
|
||||||
color={Colors.RIVER_BED}
|
color={Colors.RIVER_BED}
|
||||||
className="t--table-filter-remove-btn"
|
className={`t--table-filter-remove-btn ${
|
||||||
|
hasAnyFilters ? "" : "hide-icon"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
{index === 1 ? (
|
{index === 1 ? (
|
||||||
<DropdownWrapper width={75}>
|
<DropdownWrapper width={75}>
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ interface ReactTableComponentProps {
|
||||||
multiRowSelection?: boolean;
|
multiRowSelection?: boolean;
|
||||||
hiddenColumns?: string[];
|
hiddenColumns?: string[];
|
||||||
columnNameMap?: { [key: string]: string };
|
columnNameMap?: { [key: string]: string };
|
||||||
|
triggerRowSelection: boolean;
|
||||||
columnTypeMap?: {
|
columnTypeMap?: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -319,6 +320,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
||||||
pageNo={props.pageNo - 1}
|
pageNo={props.pageNo - 1}
|
||||||
updatePageNo={props.updatePageNo}
|
updatePageNo={props.updatePageNo}
|
||||||
columnActions={props.columnActions}
|
columnActions={props.columnActions}
|
||||||
|
triggerRowSelection={props.triggerRowSelection}
|
||||||
nextPageClick={() => {
|
nextPageClick={() => {
|
||||||
props.nextPageClick();
|
props.nextPageClick();
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useSpring, animated, interpolate } from "react-spring";
|
||||||
|
|
||||||
|
const ScrollTrack = styled.div<{
|
||||||
|
isVisible: boolean;
|
||||||
|
}>`
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
top: 0;
|
||||||
|
right: 2px;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: ${(props) => (props.isVisible ? 1 : 0)};
|
||||||
|
transition: opacity 0.15s ease-in;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ScrollThumb = styled(animated.div)`
|
||||||
|
width: 4px;
|
||||||
|
background-color: #ebeef0aa;
|
||||||
|
border-radius: 3px;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
containerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
}
|
||||||
|
const ScrollIndicator = ({ containerRef }: Props) => {
|
||||||
|
const [{ thumbPosition }, setThumbPosition] = useSpring(() => ({
|
||||||
|
thumbPosition: 0,
|
||||||
|
config: {
|
||||||
|
clamp: true,
|
||||||
|
friction: 10,
|
||||||
|
precision: 0.1,
|
||||||
|
tension: 800,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const [isScrollVisible, setIsScrollVisible] = useState(false);
|
||||||
|
const thumbRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleContainerScroll = (e: any): void => {
|
||||||
|
setIsScrollVisible(true);
|
||||||
|
const thumbHeight =
|
||||||
|
e.target.offsetHeight / (e.target.scrollHeight / e.target.offsetHeight);
|
||||||
|
const thumbPosition = (e.target.scrollTop / e.target.offsetHeight) * 100;
|
||||||
|
/* set scroll thumb height */
|
||||||
|
if (thumbRef.current) {
|
||||||
|
thumbRef.current.style.height = thumbHeight + "px";
|
||||||
|
}
|
||||||
|
setThumbPosition({
|
||||||
|
thumbPosition,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
containerRef.current?.addEventListener("scroll", handleContainerScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
containerRef.current?.removeEventListener(
|
||||||
|
"scroll",
|
||||||
|
handleContainerScroll,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isScrollVisible) {
|
||||||
|
hideScrollbar();
|
||||||
|
}
|
||||||
|
}, [isScrollVisible]);
|
||||||
|
|
||||||
|
const hideScrollbar = _.debounce(() => {
|
||||||
|
setIsScrollVisible(false);
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollTrack isVisible={isScrollVisible}>
|
||||||
|
<ScrollThumb
|
||||||
|
ref={thumbRef}
|
||||||
|
style={{
|
||||||
|
transform: interpolate(
|
||||||
|
[thumbPosition],
|
||||||
|
(top: number) => `translate3d(0px, ${top}%, 0)`,
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ScrollTrack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollIndicator;
|
||||||
|
|
@ -53,6 +53,7 @@ interface TableProps {
|
||||||
selectedRowIndices: number[];
|
selectedRowIndices: number[];
|
||||||
disableDrag: () => void;
|
disableDrag: () => void;
|
||||||
enableDrag: () => void;
|
enableDrag: () => void;
|
||||||
|
triggerRowSelection: boolean;
|
||||||
searchTableData: (searchKey: any) => void;
|
searchTableData: (searchKey: any) => void;
|
||||||
filters?: ReactTableFilter[];
|
filters?: ReactTableFilter[];
|
||||||
applyFilter: (filters: ReactTableFilter[]) => void;
|
applyFilter: (filters: ReactTableFilter[]) => void;
|
||||||
|
|
@ -120,6 +121,7 @@ export const Table = (props: TableProps) => {
|
||||||
height={props.height}
|
height={props.height}
|
||||||
tableSizes={tableSizes}
|
tableSizes={tableSizes}
|
||||||
id={`table${props.widgetId}`}
|
id={`table${props.widgetId}`}
|
||||||
|
triggerRowSelection={props.triggerRowSelection}
|
||||||
backgroundColor={Colors.ATHENS_GRAY_DARKER}
|
backgroundColor={Colors.ATHENS_GRAY_DARKER}
|
||||||
>
|
>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
|
|
|
||||||
|
|
@ -151,8 +151,11 @@ const TableFilters = (props: TableFilterProps) => {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const showAddFilter =
|
const hasAnyFilters = !!(
|
||||||
filters.length >= 1 && filters[0].column && filters[0].condition;
|
filters.length >= 1 &&
|
||||||
|
filters[0].column &&
|
||||||
|
filters[0].condition
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
minimal
|
minimal
|
||||||
|
|
@ -170,7 +173,7 @@ const TableFilters = (props: TableFilterProps) => {
|
||||||
className="t--table-filter-toggle-btn"
|
className="t--table-filter-toggle-btn"
|
||||||
selected={selected}
|
selected={selected}
|
||||||
icon={
|
icon={
|
||||||
showAddFilter ? (
|
hasAnyFilters ? (
|
||||||
<SelectedFilterWrapper>{filters.length}</SelectedFilterWrapper>
|
<SelectedFilterWrapper>{filters.length}</SelectedFilterWrapper>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +197,7 @@ const TableFilters = (props: TableFilterProps) => {
|
||||||
condition={filter.condition}
|
condition={filter.condition}
|
||||||
value={filter.value}
|
value={filter.value}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
hasAnyFilters={hasAnyFilters}
|
||||||
applyFilter={(filter: ReactTableFilter, index: number) => {
|
applyFilter={(filter: ReactTableFilter, index: number) => {
|
||||||
const updatedFilters = props.filters
|
const updatedFilters = props.filters
|
||||||
? [...props.filters]
|
? [...props.filters]
|
||||||
|
|
@ -215,7 +219,7 @@ const TableFilters = (props: TableFilterProps) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{showAddFilter ? (
|
{hasAnyFilters ? (
|
||||||
<ButtonWrapper className={Classes.POPOVER_DISMISS}>
|
<ButtonWrapper className={Classes.POPOVER_DISMISS}>
|
||||||
<Button
|
<Button
|
||||||
intent="primary"
|
intent="primary"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export const TableWrapper = styled.div<{
|
||||||
height: number;
|
height: number;
|
||||||
tableSizes: TableSizes;
|
tableSizes: TableSizes;
|
||||||
backgroundColor?: Color;
|
backgroundColor?: Color;
|
||||||
|
triggerRowSelection: boolean;
|
||||||
}>`
|
}>`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -41,12 +42,8 @@ export const TableWrapper = styled.div<{
|
||||||
}
|
}
|
||||||
.tr {
|
.tr {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
:nth-child(even) {
|
cursor: ${(props) => props.triggerRowSelection && "pointer"};
|
||||||
background: ${Colors.ATHENS_GRAY_DARKER};
|
background: ${Colors.WHITE};
|
||||||
}
|
|
||||||
:nth-child(odd) {
|
|
||||||
background: ${Colors.WHITE};
|
|
||||||
}
|
|
||||||
&.selected-row {
|
&.selected-row {
|
||||||
background: ${Colors.POLAR};
|
background: ${Colors.POLAR};
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,7 @@ export const renderCell = (
|
||||||
value: any,
|
value: any,
|
||||||
columnType: string,
|
columnType: string,
|
||||||
isHidden: boolean,
|
isHidden: boolean,
|
||||||
|
tableWidth: number,
|
||||||
) => {
|
) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return <CellWrapper isHidden={isHidden}></CellWrapper>;
|
return <CellWrapper isHidden={isHidden}></CellWrapper>;
|
||||||
|
|
@ -519,7 +520,11 @@ export const renderCell = (
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<AutoToolTipComponent title={value.toString()} isHidden={isHidden}>
|
<AutoToolTipComponent
|
||||||
|
title={value.toString()}
|
||||||
|
isHidden={isHidden}
|
||||||
|
tableWidth={tableWidth}
|
||||||
|
>
|
||||||
{value.toString()}
|
{value.toString()}
|
||||||
</AutoToolTipComponent>
|
</AutoToolTipComponent>
|
||||||
);
|
);
|
||||||
|
|
@ -883,7 +888,7 @@ export const ConditionFunctions: {
|
||||||
[key: string]: (a: any, b: any) => boolean;
|
[key: string]: (a: any, b: any) => boolean;
|
||||||
} = {
|
} = {
|
||||||
isExactly: (a: any, b: any) => {
|
isExactly: (a: any, b: any) => {
|
||||||
return a === b;
|
return a.toString() === b.toString();
|
||||||
},
|
},
|
||||||
empty: (a: any) => {
|
empty: (a: any) => {
|
||||||
return a === "" || a === undefined || a === null;
|
return a === "" || a === undefined || a === null;
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,22 @@ class InputComponent extends React.Component<
|
||||||
return "text";
|
return "text";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onKeyDownTextArea = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
const isEnterKey = e.key === "Enter" || e.keyCode === 13;
|
||||||
|
const { disableNewLineOnPressEnterKey } = this.props;
|
||||||
|
if (isEnterKey && disableNewLineOnPressEnterKey && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (typeof this.props.onKeyDown === "function") {
|
||||||
|
this.props.onKeyDown(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (typeof this.props.onKeyDown === "function") {
|
||||||
|
this.props.onKeyDown(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private numericInputComponent = () => (
|
private numericInputComponent = () => (
|
||||||
<NumericInput
|
<NumericInput
|
||||||
value={this.props.value}
|
value={this.props.value}
|
||||||
|
|
@ -155,6 +171,7 @@ class InputComponent extends React.Component<
|
||||||
stepSize={this.props.stepSize}
|
stepSize={this.props.stepSize}
|
||||||
onFocus={() => this.setFocusState(true)}
|
onFocus={() => this.setFocusState(true)}
|
||||||
onBlur={() => this.setFocusState(false)}
|
onBlur={() => this.setFocusState(false)}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
private textAreaInputComponent = () => (
|
private textAreaInputComponent = () => (
|
||||||
|
|
@ -169,6 +186,7 @@ class InputComponent extends React.Component<
|
||||||
growVertically={false}
|
growVertically={false}
|
||||||
onFocus={() => this.setFocusState(true)}
|
onFocus={() => this.setFocusState(true)}
|
||||||
onBlur={() => this.setFocusState(false)}
|
onBlur={() => this.setFocusState(false)}
|
||||||
|
onKeyDown={this.onKeyDownTextArea}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -199,6 +217,7 @@ class InputComponent extends React.Component<
|
||||||
type={this.getType(this.props.inputType)}
|
type={this.getType(this.props.inputType)}
|
||||||
onFocus={() => this.setFocusState(true)}
|
onFocus={() => this.setFocusState(true)}
|
||||||
onBlur={() => this.setFocusState(false)}
|
onBlur={() => this.setFocusState(false)}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
private renderInputComponent = (inputType: InputType, isTextArea: boolean) =>
|
private renderInputComponent = (inputType: InputType, isTextArea: boolean) =>
|
||||||
|
|
@ -267,6 +286,12 @@ export interface InputComponentProps extends ComponentProps {
|
||||||
isInvalid: boolean;
|
isInvalid: boolean;
|
||||||
showError: boolean;
|
showError: boolean;
|
||||||
onFocusChange: (state: boolean) => void;
|
onFocusChange: (state: boolean) => void;
|
||||||
|
disableNewLineOnPressEnterKey?: boolean;
|
||||||
|
onKeyDown?: (
|
||||||
|
e:
|
||||||
|
| React.KeyboardEvent<HTMLTextAreaElement>
|
||||||
|
| React.KeyboardEvent<HTMLInputElement>,
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InputComponent;
|
export default InputComponent;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import PerformanceTracker, {
|
||||||
|
|
||||||
const SidebarWrapper = styled.div`
|
const SidebarWrapper = styled.div`
|
||||||
background-color: ${Colors.MINE_SHAFT};
|
background-color: ${Colors.MINE_SHAFT};
|
||||||
padding: 0px 0 0 6px;
|
padding: 0;
|
||||||
width: ${(props) => props.theme.sidebarWidth};
|
width: ${(props) => props.theme.sidebarWidth};
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
createNewApiAction,
|
createNewApiAction,
|
||||||
createNewQueryAction,
|
createNewQueryAction,
|
||||||
} from "actions/apiPaneActions";
|
} from "actions/apiPaneActions";
|
||||||
|
import { NavigationTargetType } from "../../../sagas/ActionExecutionSagas";
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
/* TODO: Function and object types need to be updated to enable the lint rule */
|
/* TODO: Function and object types need to be updated to enable the lint rule */
|
||||||
|
|
@ -47,6 +48,19 @@ const FILE_TYPE_OPTIONS = [
|
||||||
{ label: "SVG", value: "'image/svg+xml'", id: "image/svg+xml" },
|
{ label: "SVG", value: "'image/svg+xml'", id: "image/svg+xml" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const NAVIGATION_TARGET_FIELD_OPTIONS = [
|
||||||
|
{
|
||||||
|
label: "Same window",
|
||||||
|
value: `'${NavigationTargetType.SAME_WINDOW}'`,
|
||||||
|
id: NavigationTargetType.SAME_WINDOW,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "New window",
|
||||||
|
value: `'${NavigationTargetType.NEW_WINDOW}'`,
|
||||||
|
id: NavigationTargetType.NEW_WINDOW,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const FUNC_ARGS_REGEX = /((["][^"]*["])|([\[].*[\]])|([\{].*[\}])|(['][^']*['])|([\(].*[\)[=][>][{].*[}])|([^'",][^,"+]*[^'",]*))*/gi;
|
const FUNC_ARGS_REGEX = /((["][^"]*["])|([\[].*[\]])|([\{].*[\}])|(['][^']*['])|([\(].*[\)[=][>][{].*[}])|([^'",][^,"+]*[^'",]*))*/gi;
|
||||||
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
|
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
|
||||||
//Old Regex:: /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
|
//Old Regex:: /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
|
||||||
|
|
@ -316,6 +330,7 @@ const FieldType = {
|
||||||
DOWNLOAD_FILE_NAME_FIELD: "DOWNLOAD_FILE_NAME_FIELD",
|
DOWNLOAD_FILE_NAME_FIELD: "DOWNLOAD_FILE_NAME_FIELD",
|
||||||
DOWNLOAD_FILE_TYPE_FIELD: "DOWNLOAD_FILE_TYPE_FIELD",
|
DOWNLOAD_FILE_TYPE_FIELD: "DOWNLOAD_FILE_TYPE_FIELD",
|
||||||
COPY_TEXT_FIELD: "COPY_TEXT_FIELD",
|
COPY_TEXT_FIELD: "COPY_TEXT_FIELD",
|
||||||
|
NAVIGATION_TARGET_FIELD: "NAVIGATION_TARGET_FIELD",
|
||||||
};
|
};
|
||||||
type FieldType = typeof FieldType[keyof typeof FieldType];
|
type FieldType = typeof FieldType[keyof typeof FieldType];
|
||||||
|
|
||||||
|
|
@ -406,6 +421,15 @@ const fieldConfigs: FieldConfigs = {
|
||||||
},
|
},
|
||||||
view: ViewTypes.TEXT_VIEW,
|
view: ViewTypes.TEXT_VIEW,
|
||||||
},
|
},
|
||||||
|
[FieldType.NAVIGATION_TARGET_FIELD]: {
|
||||||
|
getter: (value: any) => {
|
||||||
|
return enumTypeGetter(value, 2, NavigationTargetType.SAME_WINDOW);
|
||||||
|
},
|
||||||
|
setter: (option: any, currentValue: string) => {
|
||||||
|
return enumTypeSetter(option.value, currentValue, 2);
|
||||||
|
},
|
||||||
|
view: ViewTypes.SELECTOR_VIEW,
|
||||||
|
},
|
||||||
[FieldType.ALERT_TEXT_FIELD]: {
|
[FieldType.ALERT_TEXT_FIELD]: {
|
||||||
getter: (value: string) => {
|
getter: (value: string) => {
|
||||||
return textGetter(value, 0);
|
return textGetter(value, 0);
|
||||||
|
|
@ -628,6 +652,9 @@ function getFieldFromValue(
|
||||||
fields.push({
|
fields.push({
|
||||||
field: FieldType.QUERY_PARAMS_FIELD,
|
field: FieldType.QUERY_PARAMS_FIELD,
|
||||||
});
|
});
|
||||||
|
fields.push({
|
||||||
|
field: FieldType.NAVIGATION_TARGET_FIELD,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.indexOf("showModal") !== -1) {
|
if (value.indexOf("showModal") !== -1) {
|
||||||
|
|
@ -718,6 +745,7 @@ function renderField(props: {
|
||||||
case FieldType.PAGE_SELECTOR_FIELD:
|
case FieldType.PAGE_SELECTOR_FIELD:
|
||||||
case FieldType.ALERT_TYPE_SELECTOR_FIELD:
|
case FieldType.ALERT_TYPE_SELECTOR_FIELD:
|
||||||
case FieldType.DOWNLOAD_FILE_TYPE_FIELD:
|
case FieldType.DOWNLOAD_FILE_TYPE_FIELD:
|
||||||
|
case FieldType.NAVIGATION_TARGET_FIELD:
|
||||||
let label = "";
|
let label = "";
|
||||||
let defaultText = "Select Action";
|
let defaultText = "Select Action";
|
||||||
let options = props.apiOptionTree;
|
let options = props.apiOptionTree;
|
||||||
|
|
@ -776,6 +804,11 @@ function renderField(props: {
|
||||||
options = FILE_TYPE_OPTIONS;
|
options = FILE_TYPE_OPTIONS;
|
||||||
defaultText = "Select file type (optional)";
|
defaultText = "Select file type (optional)";
|
||||||
}
|
}
|
||||||
|
if (fieldType === FieldType.NAVIGATION_TARGET_FIELD) {
|
||||||
|
label = "Target";
|
||||||
|
options = NAVIGATION_TARGET_FIELD_OPTIONS;
|
||||||
|
defaultText = "Navigation target";
|
||||||
|
}
|
||||||
viewElement = (view as (props: SelectorViewProps) => JSX.Element)({
|
viewElement = (view as (props: SelectorViewProps) => JSX.Element)({
|
||||||
options: options,
|
options: options,
|
||||||
label: label,
|
label: label,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export enum EventType {
|
||||||
ON_PAGE_LOAD = "ON_PAGE_LOAD",
|
ON_PAGE_LOAD = "ON_PAGE_LOAD",
|
||||||
ON_PREV_PAGE = "ON_PREV_PAGE",
|
ON_PREV_PAGE = "ON_PREV_PAGE",
|
||||||
ON_NEXT_PAGE = "ON_NEXT_PAGE",
|
ON_NEXT_PAGE = "ON_NEXT_PAGE",
|
||||||
|
ON_PAGE_SIZE_CHANGE = "ON_PAGE_SIZE_CHANGE",
|
||||||
ON_ERROR = "ON_ERROR",
|
ON_ERROR = "ON_ERROR",
|
||||||
ON_SUCCESS = "ON_SUCCESS",
|
ON_SUCCESS = "ON_SUCCESS",
|
||||||
ON_ROW_SELECTED = "ON_ROW_SELECTED",
|
ON_ROW_SELECTED = "ON_ROW_SELECTED",
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,13 @@ export const scrollbarDark = css`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const getTypographyByKey = (props: Record<string, any>, key: string) => `
|
||||||
|
font-weight: ${props.theme.typography[key].fontWeight};
|
||||||
|
font-size: ${props.theme.typography[key].fontSize}px;
|
||||||
|
line-height: ${props.theme.typography[key].lineHeight}px;
|
||||||
|
letter-spacing: ${props.theme.typography[key].letterSpacing}px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const BlueprintControlTransform = css`
|
export const BlueprintControlTransform = css`
|
||||||
&& {
|
&& {
|
||||||
.${Classes.CONTROL} {
|
.${Classes.CONTROL} {
|
||||||
|
|
@ -312,11 +319,8 @@ export type Theme = {
|
||||||
};
|
};
|
||||||
authCard: {
|
authCard: {
|
||||||
width: number;
|
width: number;
|
||||||
borderRadius: number;
|
|
||||||
background: Color;
|
|
||||||
padding: number;
|
|
||||||
dividerSpacing: number;
|
dividerSpacing: number;
|
||||||
shadow: string;
|
formMessageWidth: number;
|
||||||
};
|
};
|
||||||
shadows: string[];
|
shadows: string[];
|
||||||
widgets: {
|
widgets: {
|
||||||
|
|
@ -735,6 +739,35 @@ type ColorType = {
|
||||||
bg: ShadeColor;
|
bg: ShadeColor;
|
||||||
};
|
};
|
||||||
floatingBtn: any;
|
floatingBtn: any;
|
||||||
|
auth: any;
|
||||||
|
formMessage: Record<string, Record<Intent, string>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auth: any = {
|
||||||
|
background: darkShades[1],
|
||||||
|
cardBackground: lightShades[10],
|
||||||
|
btnPrimary: "#F86A2B",
|
||||||
|
inputBackground: darkShades[1],
|
||||||
|
headingText: "#FFF",
|
||||||
|
link: "#106ba3",
|
||||||
|
text: darkShades[7],
|
||||||
|
placeholder: darkShades[5],
|
||||||
|
socialBtnText: darkShades[8],
|
||||||
|
socialBtnBorder: darkShades[8],
|
||||||
|
socialBtnHighlight: darkShades[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formMessage = {
|
||||||
|
background: {
|
||||||
|
danger: "rgba(226,44,44,0.08)",
|
||||||
|
success: "#172320",
|
||||||
|
warning: "rgba(224, 179, 14, 0.08)",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
danger: "#E22C2C",
|
||||||
|
success: "#03B365",
|
||||||
|
warning: "#E0B30E",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dark: ColorType = {
|
export const dark: ColorType = {
|
||||||
|
|
@ -862,9 +895,9 @@ export const dark: ColorType = {
|
||||||
border: darkShades[2],
|
border: darkShades[2],
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
bg: lightShades[10],
|
bg: darkShades[0],
|
||||||
text: darkShades[9],
|
|
||||||
border: darkShades[0],
|
border: darkShades[0],
|
||||||
|
text: darkShades[7],
|
||||||
},
|
},
|
||||||
placeholder: darkShades[5],
|
placeholder: darkShades[5],
|
||||||
readOnly: {
|
readOnly: {
|
||||||
|
|
@ -1019,6 +1052,8 @@ export const dark: ColorType = {
|
||||||
backgroundColor: darkShades[3],
|
backgroundColor: darkShades[3],
|
||||||
iconColor: darkShades[6],
|
iconColor: darkShades[6],
|
||||||
},
|
},
|
||||||
|
auth,
|
||||||
|
formMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const light: ColorType = {
|
export const light: ColorType = {
|
||||||
|
|
@ -1303,6 +1338,8 @@ export const light: ColorType = {
|
||||||
backgroundColor: lightShades[3],
|
backgroundColor: lightShades[3],
|
||||||
iconColor: lightShades[7],
|
iconColor: lightShades[7],
|
||||||
},
|
},
|
||||||
|
auth,
|
||||||
|
formMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const theme: Theme = {
|
export const theme: Theme = {
|
||||||
|
|
@ -1395,6 +1432,18 @@ export const theme: Theme = {
|
||||||
letterSpacing: -0.24,
|
letterSpacing: -0.24,
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
},
|
},
|
||||||
|
authCardHeader: {
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 25,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
authCardSubheader: {
|
||||||
|
fontStyle: "normal",
|
||||||
|
fontWeight: "normal",
|
||||||
|
fontSize: 15,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
iconSizes: {
|
iconSizes: {
|
||||||
XXS: 8,
|
XXS: 8,
|
||||||
|
|
@ -1545,12 +1594,9 @@ export const theme: Theme = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authCard: {
|
authCard: {
|
||||||
width: 612,
|
width: 440,
|
||||||
borderRadius: 16,
|
|
||||||
background: Colors.WHITE,
|
|
||||||
padding: 40,
|
|
||||||
dividerSpacing: 32,
|
dividerSpacing: 32,
|
||||||
shadow: "0px 4px 8px rgba(9, 30, 66, 0.25)",
|
formMessageWidth: 370,
|
||||||
},
|
},
|
||||||
shadows: [
|
shadows: [
|
||||||
/* 0. tab */
|
/* 0. tab */
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
FLUSH_AND_REDIRECT: "FLUSH_AND_REDIRECT",
|
FLUSH_AND_REDIRECT: "FLUSH_AND_REDIRECT",
|
||||||
SAFE_CRASH_APPSMITH: "SAFE_CRASH_APPSMITH",
|
SAFE_CRASH_APPSMITH: "SAFE_CRASH_APPSMITH",
|
||||||
SAFE_CRASH_APPSMITH_REQUEST: "SAFE_CRASH_APPSMITH_REQUEST",
|
SAFE_CRASH_APPSMITH_REQUEST: "SAFE_CRASH_APPSMITH_REQUEST",
|
||||||
UPDATE_CANVAS: "UPDATE_CANVAS",
|
INIT_CANVAS_LAYOUT: "INIT_CANVAS_LAYOUT",
|
||||||
FETCH_CANVAS: "FETCH_CANVAS",
|
FETCH_CANVAS: "FETCH_CANVAS",
|
||||||
CLEAR_CANVAS: "CLEAR_CANVAS",
|
CLEAR_CANVAS: "CLEAR_CANVAS",
|
||||||
FETCH_PAGE_INIT: "FETCH_PAGE_INIT",
|
FETCH_PAGE_INIT: "FETCH_PAGE_INIT",
|
||||||
|
|
@ -55,6 +55,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
UPDATE_WIDGET_PROPERTY_REQUEST: "UPDATE_WIDGET_PROPERTY_REQUEST",
|
UPDATE_WIDGET_PROPERTY_REQUEST: "UPDATE_WIDGET_PROPERTY_REQUEST",
|
||||||
UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY",
|
UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY",
|
||||||
UPDATE_WIDGET_DYNAMIC_PROPERTY: "UPDATE_WIDGET_DYNAMIC_PROPERTY",
|
UPDATE_WIDGET_DYNAMIC_PROPERTY: "UPDATE_WIDGET_DYNAMIC_PROPERTY",
|
||||||
|
BATCH_UPDATE_WIDGET_PROPERTY: "BATCH_UPDATE_WIDGET_PROPERTY",
|
||||||
|
DELETE_WIDGET_PROPERTY: "DELETE_WIDGET_PROPERTY",
|
||||||
FETCH_PROPERTY_PANE_CONFIGS_INIT: "FETCH_PROPERTY_PANE_CONFIGS_INIT",
|
FETCH_PROPERTY_PANE_CONFIGS_INIT: "FETCH_PROPERTY_PANE_CONFIGS_INIT",
|
||||||
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
|
FETCH_PROPERTY_PANE_CONFIGS_SUCCESS: "FETCH_PROPERTY_PANE_CONFIGS_SUCCESS",
|
||||||
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
|
FETCH_CONFIGS_INIT: "FETCH_CONFIGS_INIT",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { GoogleOAuthURL, GithubOAuthURL } from "constants/ApiConstants";
|
import { GoogleOAuthURL, GithubOAuthURL } from "constants/ApiConstants";
|
||||||
import GithubLogo from "assets/images/Github.png";
|
|
||||||
|
import GithubLogo from "assets/images/Github_inverted.png";
|
||||||
import GoogleLogo from "assets/images/Google.png";
|
import GoogleLogo from "assets/images/Google.png";
|
||||||
export type SocialLoginButtonProps = {
|
export type SocialLoginButtonProps = {
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export const LOGIN_FORM_EMAIL_FIELD_NAME = "username";
|
||||||
export const LOGIN_FORM_PASSWORD_FIELD_NAME = "password";
|
export const LOGIN_FORM_PASSWORD_FIELD_NAME = "password";
|
||||||
|
|
||||||
export const SIGNUP_FORM_NAME = "SignupForm";
|
export const SIGNUP_FORM_NAME = "SignupForm";
|
||||||
|
export const SIGNUP_FORM_EMAIL_FIELD_NAME = "email";
|
||||||
export const FORGOT_PASSWORD_FORM_NAME = "ForgotPasswordForm";
|
export const FORGOT_PASSWORD_FORM_NAME = "ForgotPasswordForm";
|
||||||
export const RESET_PASSWORD_FORM_NAME = "ResetPasswordForm";
|
export const RESET_PASSWORD_FORM_NAME = "ResetPasswordForm";
|
||||||
export const CREATE_PASSWORD_FORM_NAME = "CreatePasswordForm";
|
export const CREATE_PASSWORD_FORM_NAME = "CreatePasswordForm";
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ export const ENTER_VIDEO_URL = "Please provide a valid url";
|
||||||
export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password";
|
export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password";
|
||||||
export const FORM_VALIDATION_PASSWORD_RULE =
|
export const FORM_VALIDATION_PASSWORD_RULE =
|
||||||
"Please provide a password with a minimum of 6 characters";
|
"Please provide a password with a minimum of 6 characters";
|
||||||
export const FORM_VALIDATION_INVALID_PASSWORD = "Please enter a valid password";
|
export const FORM_VALIDATION_INVALID_PASSWORD = FORM_VALIDATION_PASSWORD_RULE;
|
||||||
|
|
||||||
export const LOGIN_PAGE_SUBTITLE = "Use your organization email";
|
export const LOGIN_PAGE_SUBTITLE = "Use your organization email";
|
||||||
export const LOGIN_PAGE_TITLE = "Login";
|
export const LOGIN_PAGE_TITLE = "Sign In to your account";
|
||||||
export const LOGIN_PAGE_EMAIL_INPUT_LABEL = "Email";
|
export const LOGIN_PAGE_EMAIL_INPUT_LABEL = "Email";
|
||||||
export const LOGIN_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
export const LOGIN_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
||||||
export const LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email";
|
export const LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email";
|
||||||
|
|
@ -29,12 +29,13 @@ export const LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password";
|
||||||
export const LOGIN_PAGE_INVALID_CREDS_ERROR =
|
export const LOGIN_PAGE_INVALID_CREDS_ERROR =
|
||||||
"It looks like you may have entered incorrect/invalid credentials. Please try again or reset password using the button below.";
|
"It looks like you may have entered incorrect/invalid credentials. Please try again or reset password using the button below.";
|
||||||
export const LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK = "Reset Password";
|
export const LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK = "Reset Password";
|
||||||
|
export const NEW_TO_APPSMITH = "New to Appsmith?";
|
||||||
|
|
||||||
export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "Login";
|
export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "sign in";
|
||||||
export const LOGIN_PAGE_FORGOT_PASSWORD_TEXT = "Forgot Password";
|
export const LOGIN_PAGE_FORGOT_PASSWORD_TEXT = "Forgot Password";
|
||||||
export const LOGIN_PAGE_REMEMBER_ME_LABEL = "Remember";
|
export const LOGIN_PAGE_REMEMBER_ME_LABEL = "Remember";
|
||||||
export const LOGIN_PAGE_SIGN_UP_LINK_TEXT = "New to Appsmith? Sign up";
|
export const LOGIN_PAGE_SIGN_UP_LINK_TEXT = "Sign up";
|
||||||
export const SIGNUP_PAGE_TITLE = "Sign Up";
|
export const SIGNUP_PAGE_TITLE = "Create your free account";
|
||||||
export const SIGNUP_PAGE_SUBTITLE = "Use your organization email";
|
export const SIGNUP_PAGE_SUBTITLE = "Use your organization email";
|
||||||
export const SIGNUP_PAGE_EMAIL_INPUT_LABEL = "Email";
|
export const SIGNUP_PAGE_EMAIL_INPUT_LABEL = "Email";
|
||||||
export const SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER = " Email";
|
export const SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER = " Email";
|
||||||
|
|
@ -42,16 +43,17 @@ export const SIGNUP_PAGE_NAME_INPUT_PLACEHOLDER = "Name";
|
||||||
export const SIGNUP_PAGE_NAME_INPUT_LABEL = "Name";
|
export const SIGNUP_PAGE_NAME_INPUT_LABEL = "Name";
|
||||||
export const SIGNUP_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
export const SIGNUP_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
||||||
export const SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password";
|
export const SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password";
|
||||||
export const SIGNUP_PAGE_LOGIN_LINK_TEXT = "Have an account? Login";
|
export const SIGNUP_PAGE_LOGIN_LINK_TEXT = "Sign In";
|
||||||
export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "How should we call you?";
|
export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "How should we call you?";
|
||||||
export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = "Sign Up";
|
export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = "Sign Up";
|
||||||
|
export const ALREADY_HAVE_AN_ACCOUNT = "Already have an account?";
|
||||||
|
|
||||||
export const SIGNUP_PAGE_SUCCESS = "Awesome! You have successfully registered.";
|
export const SIGNUP_PAGE_SUCCESS = "Awesome! You have successfully registered.";
|
||||||
export const SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT = "Login";
|
export const SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT = "Login";
|
||||||
|
|
||||||
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL = "New Password";
|
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL = "New Password";
|
||||||
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER = "New Password";
|
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER = "New Password";
|
||||||
export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Changed your mind? Login";
|
export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Back to Sign In";
|
||||||
export const RESET_PASSWORD_PAGE_TITLE = "Reset Password";
|
export const RESET_PASSWORD_PAGE_TITLE = "Reset Password";
|
||||||
export const RESET_PASSWORD_SUBMIT_BUTTON_TEXT = "Reset";
|
export const RESET_PASSWORD_SUBMIT_BUTTON_TEXT = "Reset";
|
||||||
export const RESET_PASSWORD_PAGE_SUBTITLE =
|
export const RESET_PASSWORD_PAGE_SUBTITLE =
|
||||||
|
|
@ -90,6 +92,9 @@ export const WIDGET_TYPE_VALIDATION_ERROR = "Value does not match type";
|
||||||
export const URL_HTTP_VALIDATION_ERROR = "Please enter a valid URL";
|
export const URL_HTTP_VALIDATION_ERROR = "Please enter a valid URL";
|
||||||
export const NAVIGATE_TO_VALIDATION_ERROR =
|
export const NAVIGATE_TO_VALIDATION_ERROR =
|
||||||
"Please enter a valid URL or page name";
|
"Please enter a valid URL or page name";
|
||||||
|
export const PAGE_NOT_FOUND_ERROR =
|
||||||
|
"The page you’re looking for either does not exist, or cannot be found";
|
||||||
|
export const INVALID_URL_ERROR = "Invalid URL";
|
||||||
|
|
||||||
export const INVITE_USERS_VALIDATION_EMAIL_LIST =
|
export const INVITE_USERS_VALIDATION_EMAIL_LIST =
|
||||||
"Invalid Email address(es) found";
|
"Invalid Email address(es) found";
|
||||||
|
|
@ -129,7 +134,7 @@ export const DELETING_APPLICATION = "Deleting application...";
|
||||||
export const DUPLICATING_APPLICATION = "Duplicating application...";
|
export const DUPLICATING_APPLICATION = "Duplicating application...";
|
||||||
|
|
||||||
export const CURL_IMPORT_SUCCESS = "Curl Import Successfull";
|
export const CURL_IMPORT_SUCCESS = "Curl Import Successfull";
|
||||||
export const FORGOT_PASSWORD_PAGE_LOGIN_LINK = "Back to Login";
|
export const FORGOT_PASSWORD_PAGE_LOGIN_LINK = "Back to Sign In";
|
||||||
export const ADD_API_TO_PAGE_SUCCESS_MESSAGE = "Api added to page.";
|
export const ADD_API_TO_PAGE_SUCCESS_MESSAGE = "Api added to page.";
|
||||||
export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
|
export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,9 @@ export const getApplicationViewerPageURL = (
|
||||||
return url + queryParams;
|
return url + queryParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
function convertToQueryParams(params: Record<string, string> = {}): string {
|
export function convertToQueryParams(
|
||||||
|
params: Record<string, string> = {},
|
||||||
|
): string {
|
||||||
const paramKeys = Object.keys(params);
|
const paramKeys = Object.keys(params);
|
||||||
const queryParams: string[] = [];
|
const queryParams: string[] = [];
|
||||||
if (paramKeys) {
|
if (paramKeys) {
|
||||||
|
|
|
||||||
|
|
@ -108,4 +108,13 @@ export interface QueryAction extends BaseAction {
|
||||||
datasource: StoredDatasource;
|
datasource: StoredDatasource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ActionViewMode = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
pageId: string;
|
||||||
|
jsonPathKeys: string[];
|
||||||
|
confirmBeforeExecute?: boolean;
|
||||||
|
timeoutInMillisecond?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type Action = ApiAction | QueryAction;
|
export type Action = ApiAction | QueryAction;
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,8 @@ const ThemedAppWithProps = connect(
|
||||||
)(ThemedApp);
|
)(ThemedApp);
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById("root"));
|
ReactDOM.render(<App />, document.getElementById("root"));
|
||||||
|
|
||||||
|
// expose store when run in Cypress
|
||||||
|
if ((window as any).Cypress) {
|
||||||
|
(window as any).store = store;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,14 @@ const PropertyPaneConfigResponse: PropertyPaneConfigsResponse["data"] = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7.2.4",
|
id: "7.2.4",
|
||||||
|
helpText: "Triggers an action when a table page size is changed",
|
||||||
|
propertyName: "onPageSizeChange",
|
||||||
|
label: "onPageSizeChange",
|
||||||
|
controlType: "ACTION_SELECTOR",
|
||||||
|
isJSConvertible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "7.2.5",
|
||||||
propertyName: "onSearchTextChanged",
|
propertyName: "onSearchTextChanged",
|
||||||
label: "onSearchTextChanged",
|
label: "onSearchTextChanged",
|
||||||
controlType: "ACTION_SELECTOR",
|
controlType: "ACTION_SELECTOR",
|
||||||
|
|
@ -717,6 +725,15 @@ const PropertyPaneConfigResponse: PropertyPaneConfigsResponse["data"] = {
|
||||||
controlType: "ACTION_SELECTOR",
|
controlType: "ACTION_SELECTOR",
|
||||||
isJSConvertible: true,
|
isJSConvertible: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "5.11.3",
|
||||||
|
helpText:
|
||||||
|
"Triggers an action on submit (when the enter key is pressed)",
|
||||||
|
propertyName: "onSubmit",
|
||||||
|
label: "onSubmit",
|
||||||
|
controlType: "ACTION_SELECTOR",
|
||||||
|
isJSConvertible: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import { getCurrentUser } from "selectors/usersSelectors";
|
||||||
import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
|
import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
|
||||||
import { isEllipsisActive } from "utils/helpers";
|
import { isEllipsisActive } from "utils/helpers";
|
||||||
import TooltipComponent from "components/ads/Tooltip";
|
import TooltipComponent from "components/ads/Tooltip";
|
||||||
|
import Text, { TextType } from "components/ads/Text";
|
||||||
|
import { Classes } from "components/ads/common";
|
||||||
|
|
||||||
const HeaderWrapper = styled(StyledHeader)<{ hasPages: boolean }>`
|
const HeaderWrapper = styled(StyledHeader)<{ hasPages: boolean }>`
|
||||||
background: ${Colors.BALTIC_SEA};
|
background: ${Colors.BALTIC_SEA};
|
||||||
|
|
@ -40,6 +42,13 @@ const HeaderWrapper = styled(StyledHeader)<{ hasPages: boolean }>`
|
||||||
color: white;
|
color: white;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
.${Classes.TEXT} {
|
||||||
|
max-width: 194px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HeaderRow = styled.div<{ justify: string }>`
|
const HeaderRow = styled.div<{ justify: string }>`
|
||||||
|
|
@ -82,13 +91,6 @@ const ShareButton = styled(Button)`
|
||||||
color: white !important;
|
color: white !important;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledApplicationName = styled.span`
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 14px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PageTab = styled(NavLink)`
|
const PageTab = styled(NavLink)`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
@ -236,11 +238,9 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => {
|
||||||
<AppsmithLogoImg src={AppsmithLogo} alt="Appsmith logo" />
|
<AppsmithLogoImg src={AppsmithLogo} alt="Appsmith logo" />
|
||||||
</Link>
|
</Link>
|
||||||
</HeaderSection>
|
</HeaderSection>
|
||||||
<HeaderSection justify={"center"}>
|
<HeaderSection justify={"center"} className="current-app-name">
|
||||||
{currentApplicationDetails && (
|
{currentApplicationDetails && (
|
||||||
<StyledApplicationName>
|
<Text type={TextType.H4}>{currentApplicationDetails.name}</Text>
|
||||||
{currentApplicationDetails.name}
|
|
||||||
</StyledApplicationName>
|
|
||||||
)}
|
)}
|
||||||
</HeaderSection>
|
</HeaderSection>
|
||||||
<HeaderSection justify={"flex-end"}>
|
<HeaderSection justify={"flex-end"}>
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ const UpdatesIcon = withTheme(({ theme }) => (
|
||||||
));
|
));
|
||||||
|
|
||||||
const UpdatesButton = ({ newReleasesCount }: { newReleasesCount: string }) => (
|
const UpdatesButton = ({ newReleasesCount }: { newReleasesCount: string }) => (
|
||||||
<StyledUpdatesButton>
|
<StyledUpdatesButton data-cy="t--product-updates-btn">
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
<UpdatesIcon />
|
<UpdatesIcon />
|
||||||
<UpdatesButtonTextContainer>What's New?</UpdatesButtonTextContainer>
|
<UpdatesButtonTextContainer>What's New?</UpdatesButtonTextContainer>
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,10 @@ const Header = withTheme(
|
||||||
>
|
>
|
||||||
View on Github
|
View on Github
|
||||||
</ViewInGithubLink>
|
</ViewInGithubLink>
|
||||||
<CloseIconContainer onClick={onClose}>
|
<CloseIconContainer
|
||||||
|
onClick={onClose}
|
||||||
|
data-cy="t--product-updates-close-btn"
|
||||||
|
>
|
||||||
<CloseIcon
|
<CloseIcon
|
||||||
height={20}
|
height={20}
|
||||||
width={20}
|
width={20}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
|
||||||
import { ApplicationPayload } from "constants/ReduxActionConstants";
|
import { ApplicationPayload } from "constants/ReduxActionConstants";
|
||||||
|
|
||||||
const LoadingContainer = styled(CenteredWrapper)`
|
const LoadingContainer = styled(CenteredWrapper)`
|
||||||
|
|
@ -144,6 +145,9 @@ class ApiEditor extends React.Component<Props> {
|
||||||
paginationType,
|
paginationType,
|
||||||
isEditorInitialized,
|
isEditorInitialized,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
if (!this.props.pluginId && this.props.match.params.apiId) {
|
||||||
|
return <EntityNotFoundPane />;
|
||||||
|
}
|
||||||
if (isCreating || !isEditorInitialized) {
|
if (isCreating || !isEditorInitialized) {
|
||||||
return (
|
return (
|
||||||
<LoadingContainer>
|
<LoadingContainer>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ import BackButton from "./BackButton";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
||||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||||
|
import { isHidden } from "components/formControls/utils";
|
||||||
|
import log from "loglevel";
|
||||||
|
|
||||||
const { cloudHosting } = getAppsmithConfigs();
|
const { cloudHosting } = getAppsmithConfigs();
|
||||||
|
|
||||||
|
|
@ -412,6 +414,7 @@ class DatasourceDBEditor extends React.Component<
|
||||||
};
|
};
|
||||||
|
|
||||||
renderMainSection = (section: any, index: number) => {
|
renderMainSection = (section: any, index: number) => {
|
||||||
|
if (isHidden(this.props.formData, section.hidden)) return null;
|
||||||
return (
|
return (
|
||||||
<Collapsible title={section.sectionName} defaultIsOpen={index === 0}>
|
<Collapsible title={section.sectionName} defaultIsOpen={index === 0}>
|
||||||
{this.renderEachConfig(section)}
|
{this.renderEachConfig(section)}
|
||||||
|
|
@ -419,60 +422,73 @@ class DatasourceDBEditor extends React.Component<
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderEachConfig(section: any) {
|
renderSingleConfig = (
|
||||||
const keyValueItems: any = [];
|
config: ControlProps,
|
||||||
|
multipleConfig?: ControlProps[],
|
||||||
return (
|
) => {
|
||||||
<div key={section.id}>
|
multipleConfig = multipleConfig || [];
|
||||||
<div>
|
try {
|
||||||
{_.map(section.children, (propertyControlOrSection: ControlProps) => {
|
this.setupConfig(config);
|
||||||
if ("children" in propertyControlOrSection) {
|
return (
|
||||||
return this.renderEachConfig(propertyControlOrSection);
|
<div key={config.configProperty} style={{ marginTop: "16px" }}>
|
||||||
} else {
|
<FormControl
|
||||||
try {
|
config={config}
|
||||||
const {
|
formName={DATASOURCE_DB_FORM}
|
||||||
controlType,
|
multipleConfig={multipleConfig}
|
||||||
isRequired,
|
/>
|
||||||
configProperty,
|
|
||||||
} = propertyControlOrSection;
|
|
||||||
const config = { ...propertyControlOrSection };
|
|
||||||
const multipleConfig = keyValueItems;
|
|
||||||
|
|
||||||
this.configDetails[configProperty] = controlType;
|
|
||||||
|
|
||||||
if (isRequired) {
|
|
||||||
this.requiredFields[
|
|
||||||
configProperty
|
|
||||||
] = propertyControlOrSection;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
controlType === "KEYVALUE_ARRAY" &&
|
|
||||||
keyValueItems.length < 2
|
|
||||||
) {
|
|
||||||
keyValueItems.push(config);
|
|
||||||
|
|
||||||
if (keyValueItems.length < 2) return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={configProperty} style={{ marginTop: "16px" }}>
|
|
||||||
<FormControl
|
|
||||||
config={config}
|
|
||||||
formName={DATASOURCE_DB_FORM}
|
|
||||||
multipleConfig={multipleConfig}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupConfig = (config: ControlProps) => {
|
||||||
|
const { controlType, isRequired, configProperty } = config;
|
||||||
|
this.configDetails[configProperty] = controlType;
|
||||||
|
|
||||||
|
if (isRequired) {
|
||||||
|
this.requiredFields[configProperty] = config;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
isKVArray = (children: Array<ControlProps>) => {
|
||||||
|
if (!Array.isArray(children) || children.length < 2) return false;
|
||||||
|
return (
|
||||||
|
children[0].controlType && children[0].controlType === "KEYVALUE_ARRAY"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderKVArray = (children: Array<ControlProps>) => {
|
||||||
|
try {
|
||||||
|
// setup config for each child
|
||||||
|
children.forEach((c) => this.setupConfig(c));
|
||||||
|
// We pass last child for legacy reasons, to keep the logic here exactly same as before.
|
||||||
|
return this.renderSingleConfig(children[children.length - 1], children);
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderEachConfig = (section: any) => {
|
||||||
|
return (
|
||||||
|
<div key={section.sectionName}>
|
||||||
|
{_.map(section.children, (propertyControlOrSection: ControlProps) => {
|
||||||
|
// If the section is hidden, skip rendering
|
||||||
|
if (isHidden(this.props.formData, section.hidden)) return null;
|
||||||
|
if ("children" in propertyControlOrSection) {
|
||||||
|
const { children } = propertyControlOrSection;
|
||||||
|
if (this.isKVArray(children)) {
|
||||||
|
return this.renderKVArray(children);
|
||||||
|
}
|
||||||
|
return this.renderEachConfig(propertyControlOrSection);
|
||||||
|
} else {
|
||||||
|
return this.renderSingleConfig(propertyControlOrSection);
|
||||||
|
}
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default reduxForm<Datasource, DatasourceDBEditorProps>({
|
export default reduxForm<Datasource, DatasourceDBEditorProps>({
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import React from "react";
|
||||||
import { map, get } from "lodash";
|
import { map, get } from "lodash";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { isHidden } from "components/formControls/utils";
|
||||||
|
import log from "loglevel";
|
||||||
|
|
||||||
const Key = styled.div`
|
const Key = styled.div`
|
||||||
color: ${Colors.DOVE_GRAY};
|
color: ${Colors.DOVE_GRAY};
|
||||||
|
|
@ -14,7 +16,6 @@ const Value = styled.div`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-transform: uppercase;
|
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ export const renderDatasourceSection = (
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={datasource.id}>
|
<React.Fragment key={datasource.id}>
|
||||||
{map(config.children, (section) => {
|
{map(config.children, (section) => {
|
||||||
|
if (isHidden(datasource, section.hidden)) return null;
|
||||||
if ("children" in section) {
|
if ("children" in section) {
|
||||||
return renderDatasourceSection(section, datasource);
|
return renderDatasourceSection(section, datasource);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -85,12 +87,25 @@ export const renderDatasourceSection = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (controlType === "DROP_DOWN") {
|
||||||
|
if (Array.isArray(section.options)) {
|
||||||
|
const option = section.options.find(
|
||||||
|
(el: any) => el.value === value,
|
||||||
|
);
|
||||||
|
if (option && option.label) {
|
||||||
|
value = option.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldWrapper key={reactKey}>
|
<FieldWrapper key={reactKey}>
|
||||||
<Key>{label}: </Key> <Value>{value}</Value>
|
<Key>{label}: </Key> <Value>{value}</Value>
|
||||||
</FieldWrapper>
|
</FieldWrapper>
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import DatasourceHome from "./DatasourceHome";
|
||||||
import DataSourceEditorForm from "./DBForm";
|
import DataSourceEditorForm from "./DBForm";
|
||||||
import { Datasource } from "entities/Datasource";
|
import { Datasource } from "entities/Datasource";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
|
import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
formData: Datasource;
|
formData: Datasource;
|
||||||
|
|
@ -88,7 +89,9 @@ class DataSourceEditor extends React.Component<Props> {
|
||||||
setDatasourceEditorMode,
|
setDatasourceEditorMode,
|
||||||
pluginType,
|
pluginType,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
if (!pluginId && datasourceId) {
|
||||||
|
return <EntityNotFoundPane />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{datasourceId ? (
|
{datasourceId ? (
|
||||||
|
|
|
||||||
71
app/client/src/pages/Editor/EntityNotFoundPane.tsx
Normal file
71
app/client/src/pages/Editor/EntityNotFoundPane.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Button, { Size, Category } from "components/ads/Button";
|
||||||
|
import PageUnavailableImage from "assets/images/invalid-page.png";
|
||||||
|
import { PAGE_NOT_FOUND_ERROR, INVALID_URL_ERROR } from "constants/messages";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-top: 15%;
|
||||||
|
background: #fcfcfc;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.page-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 450px;
|
||||||
|
}
|
||||||
|
.bold-text {
|
||||||
|
font-weight: ${(props) => props.theme.fontWeights[3]};
|
||||||
|
font-size: 24px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.page-message {
|
||||||
|
margin-top: 14px;
|
||||||
|
color: #716e6e;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 17px;
|
||||||
|
letter-spacing: 0.733333px;
|
||||||
|
}
|
||||||
|
.page-unavailable-img {
|
||||||
|
width: 72px;
|
||||||
|
}
|
||||||
|
.button-position {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EntityNotFoundPane = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<img
|
||||||
|
src={PageUnavailableImage}
|
||||||
|
alt="Page Unavailable"
|
||||||
|
className="page-unavailable-img"
|
||||||
|
/>
|
||||||
|
<div className="page-details">
|
||||||
|
<p className="bold-text">{INVALID_URL_ERROR}</p>
|
||||||
|
<p className="page-message">{PAGE_NOT_FOUND_ERROR}</p>
|
||||||
|
<Button
|
||||||
|
tag="button"
|
||||||
|
text="Go Back"
|
||||||
|
cypressSelector="t--invalid-page-go-back"
|
||||||
|
className="button-position"
|
||||||
|
size={Size.large}
|
||||||
|
category={Category.secondary}
|
||||||
|
onClick={history.goBack}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntityNotFoundPane;
|
||||||
|
|
@ -62,7 +62,7 @@ export const EntityProperties = memo(
|
||||||
actionProperty = actionProperty + "()";
|
actionProperty = actionProperty + "()";
|
||||||
}
|
}
|
||||||
if (actionProperty === "data") {
|
if (actionProperty === "data") {
|
||||||
value = entity.data;
|
value = entity.data?.body;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
propertyName: actionProperty,
|
propertyName: actionProperty,
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ export const EntityItem = styled.div<{
|
||||||
}>`
|
}>`
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding-left: ${(props) => props.step * props.theme.spaces[2]}px;
|
padding-left: ${(props) =>
|
||||||
|
props.step * props.theme.spaces[2] + props.theme.spaces[2]}px;
|
||||||
background: ${(props) => (props.active ? Colors.TUNDORA : "none")};
|
background: ${(props) => (props.active ? Colors.TUNDORA : "none")};
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
} from "./hooks";
|
} from "./hooks";
|
||||||
import Search from "./ExplorerSearch";
|
import Search from "./ExplorerSearch";
|
||||||
import ExplorerPageGroup from "./Pages/PageGroup";
|
import ExplorerPageGroup from "./Pages/PageGroup";
|
||||||
import { scrollbarDark } from "constants/DefaultTheme";
|
|
||||||
import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core";
|
import { NonIdealState, Classes, IPanelProps } from "@blueprintjs/core";
|
||||||
import WidgetSidebar from "../WidgetSidebar";
|
import WidgetSidebar from "../WidgetSidebar";
|
||||||
import { BUILDER_PAGE_URL } from "constants/routes";
|
import { BUILDER_PAGE_URL } from "constants/routes";
|
||||||
|
|
@ -22,11 +21,17 @@ import PerformanceTracker, {
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { getPlugins } from "selectors/entitiesSelector";
|
import { getPlugins } from "selectors/entitiesSelector";
|
||||||
|
import ScrollIndicator from "components/designSystems/appsmith/ScrollIndicator";
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
${scrollbarDark};
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NoResult = styled(NonIdealState)`
|
const NoResult = styled(NonIdealState)`
|
||||||
|
|
@ -41,6 +46,7 @@ const StyledDivider = styled(Divider)`
|
||||||
|
|
||||||
const EntityExplorer = (props: IPanelProps) => {
|
const EntityExplorer = (props: IPanelProps) => {
|
||||||
const { applicationId } = useParams<ExplorerURLParams>();
|
const { applicationId } = useParams<ExplorerURLParams>();
|
||||||
|
|
||||||
const searchInputRef: MutableRefObject<HTMLInputElement | null> = useRef(
|
const searchInputRef: MutableRefObject<HTMLInputElement | null> = useRef(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
@ -99,6 +105,7 @@ const EntityExplorer = (props: IPanelProps) => {
|
||||||
)}
|
)}
|
||||||
<StyledDivider />
|
<StyledDivider />
|
||||||
<JSDependencies />
|
<JSDependencies />
|
||||||
|
<ScrollIndicator containerRef={explorerRef} />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import { useWidgetSelection } from "utils/hooks/dragResizeHooks";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { getWidgetIcon } from "../ExplorerIcons";
|
import { getWidgetIcon } from "../ExplorerIcons";
|
||||||
|
|
||||||
import { noop } from "lodash";
|
|
||||||
import WidgetContextMenu from "./WidgetContextMenu";
|
import WidgetContextMenu from "./WidgetContextMenu";
|
||||||
import { updateWidgetName } from "actions/propertyPaneActions";
|
import { updateWidgetName } from "actions/propertyPaneActions";
|
||||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
@ -133,7 +132,7 @@ export const WidgetEntity = memo((props: WidgetEntityProps) => {
|
||||||
name={props.widgetName}
|
name={props.widgetName}
|
||||||
entityId={props.widgetId}
|
entityId={props.widgetId}
|
||||||
step={props.step}
|
step={props.step}
|
||||||
updateEntityName={props.pageId === pageId ? updateWidgetName : noop}
|
updateEntityName={props.pageId === pageId ? updateWidgetName : undefined}
|
||||||
searchKeyword={props.searchKeyword}
|
searchKeyword={props.searchKeyword}
|
||||||
isDefaultExpanded={
|
isDefaultExpanded={
|
||||||
shouldExpand ||
|
shouldExpand ||
|
||||||
|
|
|
||||||
39
app/client/src/pages/UserAuth/FooterLinks.tsx
Normal file
39
app/client/src/pages/UserAuth/FooterLinks.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const FooterLink = styled.a`
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: ${(props) => props.theme.colors.text.normal};
|
||||||
|
}
|
||||||
|
font-weight: ${(props) => props.theme.typography.releaseList.fontWeight};
|
||||||
|
font-size: ${(props) => props.theme.typography.releaseList.fontSize}px;
|
||||||
|
line-height: ${(props) => props.theme.typography.releaseList.lineHeight}px;
|
||||||
|
letter-spacing: ${(props) =>
|
||||||
|
props.theme.typography.releaseList.letterSpacing}px;
|
||||||
|
color: ${(props) => props.theme.colors.text.normal};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FooterLinksContainer = styled.div`
|
||||||
|
padding: ${(props) => props.theme.spaces[9]}px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
max-width: ${(props) => props.theme.authCard.width}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FooterLinks = () => (
|
||||||
|
<FooterLinksContainer>
|
||||||
|
<FooterLink target="_blank" href="/privacy-policy.html">
|
||||||
|
Privacy Policy
|
||||||
|
</FooterLink>
|
||||||
|
<FooterLink target="_blank" href="/terms-and-conditions.html">
|
||||||
|
Terms and conditions
|
||||||
|
</FooterLink>
|
||||||
|
</FooterLinksContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FooterLinks;
|
||||||
|
|
@ -4,17 +4,17 @@ import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||||
import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form";
|
import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form";
|
||||||
import StyledForm from "components/editorComponents/Form";
|
import StyledForm from "components/editorComponents/Form";
|
||||||
import {
|
import {
|
||||||
AuthCardContainer,
|
|
||||||
AuthCardHeader,
|
AuthCardHeader,
|
||||||
AuthCardBody,
|
|
||||||
FormActions,
|
FormActions,
|
||||||
AuthCardNavLink,
|
AuthCardNavLink,
|
||||||
|
FormMessagesContainer,
|
||||||
} from "./StyledComponents";
|
} from "./StyledComponents";
|
||||||
|
import { withTheme } from "styled-components";
|
||||||
|
import { Theme } from "constants/DefaultTheme";
|
||||||
import {
|
import {
|
||||||
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL,
|
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL,
|
||||||
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||||
FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT,
|
FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT,
|
||||||
FORGOT_PASSWORD_PAGE_SUBTITLE,
|
|
||||||
FORGOT_PASSWORD_PAGE_TITLE,
|
FORGOT_PASSWORD_PAGE_TITLE,
|
||||||
FORM_VALIDATION_EMPTY_EMAIL,
|
FORM_VALIDATION_EMPTY_EMAIL,
|
||||||
FORM_VALIDATION_INVALID_EMAIL,
|
FORM_VALIDATION_INVALID_EMAIL,
|
||||||
|
|
@ -22,11 +22,12 @@ import {
|
||||||
FORGOT_PASSWORD_PAGE_LOGIN_LINK,
|
FORGOT_PASSWORD_PAGE_LOGIN_LINK,
|
||||||
} from "constants/messages";
|
} from "constants/messages";
|
||||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||||
import FormMessage from "components/editorComponents/form/FormMessage";
|
import FormMessage from "components/ads/formFields/FormMessage";
|
||||||
import { FORGOT_PASSWORD_FORM_NAME } from "constants/forms";
|
import { FORGOT_PASSWORD_FORM_NAME } from "constants/forms";
|
||||||
import FormGroup from "components/editorComponents/form/FormGroup";
|
import FormGroup from "components/ads/formFields/FormGroup";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button, { Size } from "components/ads/Button";
|
||||||
import FormTextField from "components/editorComponents/form/FormTextField";
|
import FormTextField from "components/ads/formFields/TextField";
|
||||||
|
import { Icon } from "@blueprintjs/core";
|
||||||
import { isEmail, isEmptyString } from "utils/formhelpers";
|
import { isEmail, isEmptyString } from "utils/formhelpers";
|
||||||
import {
|
import {
|
||||||
ForgotPasswordFormValues,
|
ForgotPasswordFormValues,
|
||||||
|
|
@ -52,46 +53,56 @@ type ForgotPasswordProps = InjectedFormProps<
|
||||||
> &
|
> &
|
||||||
RouteComponentProps<{ email: string }> & { emailValue: string };
|
RouteComponentProps<{ email: string }> & { emailValue: string };
|
||||||
|
|
||||||
export const ForgotPassword = (props: ForgotPasswordProps) => {
|
export const ForgotPassword = withTheme(
|
||||||
const {
|
(props: ForgotPasswordProps & { theme: Theme }) => {
|
||||||
error,
|
const {
|
||||||
handleSubmit,
|
error,
|
||||||
submitting,
|
handleSubmit,
|
||||||
submitFailed,
|
submitting,
|
||||||
submitSucceeded,
|
submitFailed,
|
||||||
} = props;
|
submitSucceeded,
|
||||||
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthCardContainer>
|
<>
|
||||||
{submitSucceeded && (
|
<AuthCardHeader>
|
||||||
<FormMessage
|
<h1>{FORGOT_PASSWORD_PAGE_TITLE}</h1>
|
||||||
intent="primary"
|
</AuthCardHeader>
|
||||||
message={`${FORGOT_PASSWORD_SUCCESS_TEXT} ${props.emailValue}`}
|
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||||
/>
|
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
||||||
)}
|
<Icon
|
||||||
{!mailEnabled && (
|
icon="arrow-left"
|
||||||
<FormMessage
|
style={{ marginRight: props.theme.spaces[3] }}
|
||||||
intent="warning"
|
/>
|
||||||
message={
|
{FORGOT_PASSWORD_PAGE_LOGIN_LINK}
|
||||||
"You haven’t setup any email service yet. Please configure your email service to receive a reset link"
|
</AuthCardNavLink>
|
||||||
}
|
</div>
|
||||||
actions={[
|
<FormMessagesContainer>
|
||||||
{
|
{submitSucceeded && (
|
||||||
url: "https://docs.appsmith.com/third-party-services/email",
|
<FormMessage
|
||||||
text: "Configure Email service",
|
intent="success"
|
||||||
intent: "primary",
|
message={`${FORGOT_PASSWORD_SUCCESS_TEXT} ${props.emailValue}`}
|
||||||
},
|
/>
|
||||||
]}
|
)}
|
||||||
/>
|
{!mailEnabled && (
|
||||||
)}
|
<FormMessage
|
||||||
{submitFailed && error && (
|
intent="warning"
|
||||||
<FormMessage intent="warning" message={error} />
|
message={
|
||||||
)}
|
"You haven’t setup any email service yet. Please configure your email service to receive a reset link"
|
||||||
<AuthCardHeader>
|
}
|
||||||
<h1>{FORGOT_PASSWORD_PAGE_TITLE}</h1>
|
actions={[
|
||||||
<h5>{FORGOT_PASSWORD_PAGE_SUBTITLE}</h5>
|
{
|
||||||
</AuthCardHeader>
|
url: "https://docs.appsmith.com/third-party-services/email",
|
||||||
<AuthCardBody>
|
text: "Configure Email service",
|
||||||
|
intent: "primary",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{submitFailed && error && (
|
||||||
|
<FormMessage intent="warning" message={error} />
|
||||||
|
)}
|
||||||
|
</FormMessagesContainer>
|
||||||
<StyledForm onSubmit={handleSubmit(forgotPasswordSubmitHandler)}>
|
<StyledForm onSubmit={handleSubmit(forgotPasswordSubmitHandler)}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
intent={error ? "danger" : "none"}
|
intent={error ? "danger" : "none"}
|
||||||
|
|
@ -105,23 +116,20 @@ export const ForgotPassword = (props: ForgotPasswordProps) => {
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<Button
|
<Button
|
||||||
|
tag="button"
|
||||||
type="submit"
|
type="submit"
|
||||||
text={FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT}
|
text={FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT}
|
||||||
intent="primary"
|
fill
|
||||||
filled
|
size={Size.large}
|
||||||
size="large"
|
|
||||||
disabled={!isEmail(props.emailValue)}
|
disabled={!isEmail(props.emailValue)}
|
||||||
loading={submitting}
|
isLoading={submitting}
|
||||||
/>
|
/>
|
||||||
</FormActions>
|
</FormActions>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
</AuthCardBody>
|
</>
|
||||||
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
);
|
||||||
{FORGOT_PASSWORD_PAGE_LOGIN_LINK}
|
},
|
||||||
</AuthCardNavLink>
|
);
|
||||||
</AuthCardContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selector = formValueSelector(FORGOT_PASSWORD_FORM_NAME);
|
const selector = formValueSelector(FORGOT_PASSWORD_FORM_NAME);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,11 @@ import {
|
||||||
} from "constants/forms";
|
} from "constants/forms";
|
||||||
import { FORGOT_PASSWORD_URL, SIGN_UP_URL } from "constants/routes";
|
import { FORGOT_PASSWORD_URL, SIGN_UP_URL } from "constants/routes";
|
||||||
import {
|
import {
|
||||||
LOGIN_PAGE_SUBTITLE,
|
|
||||||
LOGIN_PAGE_TITLE,
|
LOGIN_PAGE_TITLE,
|
||||||
LOGIN_PAGE_EMAIL_INPUT_LABEL,
|
LOGIN_PAGE_EMAIL_INPUT_LABEL,
|
||||||
LOGIN_PAGE_PASSWORD_INPUT_LABEL,
|
LOGIN_PAGE_PASSWORD_INPUT_LABEL,
|
||||||
LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||||
LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||||
FORM_VALIDATION_EMPTY_EMAIL,
|
|
||||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||||
FORM_VALIDATION_INVALID_EMAIL,
|
FORM_VALIDATION_INVALID_EMAIL,
|
||||||
FORM_VALIDATION_INVALID_PASSWORD,
|
FORM_VALIDATION_INVALID_PASSWORD,
|
||||||
|
|
@ -24,29 +22,28 @@ import {
|
||||||
LOGIN_PAGE_SIGN_UP_LINK_TEXT,
|
LOGIN_PAGE_SIGN_UP_LINK_TEXT,
|
||||||
LOGIN_PAGE_INVALID_CREDS_ERROR,
|
LOGIN_PAGE_INVALID_CREDS_ERROR,
|
||||||
LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK,
|
LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK,
|
||||||
FORM_VALIDATION_PASSWORD_RULE,
|
NEW_TO_APPSMITH,
|
||||||
} from "constants/messages";
|
} from "constants/messages";
|
||||||
import Divider from "components/editorComponents/Divider";
|
import FormMessage from "components/ads/formFields/FormMessage";
|
||||||
import FormMessage from "components/editorComponents/form/FormMessage";
|
import FormGroup from "components/ads/formFields/FormGroup";
|
||||||
import FormGroup from "components/editorComponents/form/FormGroup";
|
import FormTextField from "components/ads/formFields/TextField";
|
||||||
import FormTextField from "components/editorComponents/form/FormTextField";
|
import Button, { Size } from "components/ads/Button";
|
||||||
import Button from "components/editorComponents/Button";
|
|
||||||
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
||||||
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
||||||
import { LoginFormValues } from "./helpers";
|
import { LoginFormValues } from "./helpers";
|
||||||
|
import { withTheme } from "styled-components";
|
||||||
|
import { Theme } from "constants/DefaultTheme";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AuthCardContainer,
|
|
||||||
SpacedSubmitForm,
|
SpacedSubmitForm,
|
||||||
FormActions,
|
FormActions,
|
||||||
AuthCardHeader,
|
AuthCardHeader,
|
||||||
AuthCardFooter,
|
|
||||||
AuthCardNavLink,
|
AuthCardNavLink,
|
||||||
AuthCardBody,
|
SignUpLinkSection,
|
||||||
|
ForgotPasswordLink,
|
||||||
} from "./StyledComponents";
|
} from "./StyledComponents";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { getAppsmithConfigs } from "configs";
|
import { getAppsmithConfigs } from "configs";
|
||||||
import { TncPPLinks } from "./SignUp";
|
|
||||||
import { LOGIN_SUBMIT_PATH } from "constants/ApiConstants";
|
import { LOGIN_SUBMIT_PATH } from "constants/ApiConstants";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
|
|
@ -55,16 +52,14 @@ const { enableGithubOAuth, enableGoogleOAuth } = getAppsmithConfigs();
|
||||||
|
|
||||||
const validate = (values: LoginFormValues) => {
|
const validate = (values: LoginFormValues) => {
|
||||||
const errors: LoginFormValues = {};
|
const errors: LoginFormValues = {};
|
||||||
const email = values[LOGIN_FORM_EMAIL_FIELD_NAME];
|
const email = values[LOGIN_FORM_EMAIL_FIELD_NAME] || "";
|
||||||
const password = values[LOGIN_FORM_PASSWORD_FIELD_NAME];
|
const password = values[LOGIN_FORM_PASSWORD_FIELD_NAME];
|
||||||
if (!password || isEmptyString(password)) {
|
if (!password || isEmptyString(password)) {
|
||||||
errors[LOGIN_FORM_PASSWORD_FIELD_NAME] = FORM_VALIDATION_EMPTY_PASSWORD;
|
errors[LOGIN_FORM_PASSWORD_FIELD_NAME] = FORM_VALIDATION_EMPTY_PASSWORD;
|
||||||
} else if (!isStrongPassword(password)) {
|
} else if (!isStrongPassword(password)) {
|
||||||
errors[LOGIN_FORM_PASSWORD_FIELD_NAME] = FORM_VALIDATION_INVALID_PASSWORD;
|
errors[LOGIN_FORM_PASSWORD_FIELD_NAME] = FORM_VALIDATION_INVALID_PASSWORD;
|
||||||
}
|
}
|
||||||
if (!email || isEmptyString(email)) {
|
if (!isEmptyString(email) && !isEmail(email)) {
|
||||||
errors[LOGIN_FORM_EMAIL_FIELD_NAME] = FORM_VALIDATION_EMPTY_EMAIL;
|
|
||||||
} else if (!isEmail(email)) {
|
|
||||||
errors[LOGIN_FORM_EMAIL_FIELD_NAME] = FORM_VALIDATION_INVALID_EMAIL;
|
errors[LOGIN_FORM_EMAIL_FIELD_NAME] = FORM_VALIDATION_INVALID_EMAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,14 +69,17 @@ const validate = (values: LoginFormValues) => {
|
||||||
type LoginFormProps = { emailValue: string } & InjectedFormProps<
|
type LoginFormProps = { emailValue: string } & InjectedFormProps<
|
||||||
LoginFormValues,
|
LoginFormValues,
|
||||||
{ emailValue: string }
|
{ emailValue: string }
|
||||||
>;
|
> & {
|
||||||
|
theme: Theme;
|
||||||
|
};
|
||||||
|
|
||||||
const SocialLoginList: string[] = [];
|
const SocialLoginList: string[] = [];
|
||||||
if (enableGithubOAuth) SocialLoginList.push(SocialLoginTypes.GITHUB);
|
|
||||||
if (enableGoogleOAuth) SocialLoginList.push(SocialLoginTypes.GOOGLE);
|
if (enableGoogleOAuth) SocialLoginList.push(SocialLoginTypes.GOOGLE);
|
||||||
|
if (enableGithubOAuth) SocialLoginList.push(SocialLoginTypes.GITHUB);
|
||||||
|
|
||||||
export const Login = (props: LoginFormProps) => {
|
export const Login = (props: LoginFormProps) => {
|
||||||
const { error, valid } = props;
|
const { error, valid, emailValue: email } = props;
|
||||||
|
const isFormValid = valid && email && !isEmptyString(email);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
|
@ -103,7 +101,19 @@ export const Login = (props: LoginFormProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthCardContainer>
|
<>
|
||||||
|
<AuthCardHeader>
|
||||||
|
<h1>{LOGIN_PAGE_TITLE}</h1>
|
||||||
|
</AuthCardHeader>
|
||||||
|
<SignUpLinkSection>
|
||||||
|
{NEW_TO_APPSMITH}
|
||||||
|
<AuthCardNavLink
|
||||||
|
to={signupURL}
|
||||||
|
style={{ marginLeft: props.theme.spaces[3] }}
|
||||||
|
>
|
||||||
|
{LOGIN_PAGE_SIGN_UP_LINK_TEXT}
|
||||||
|
</AuthCardNavLink>
|
||||||
|
</SignUpLinkSection>
|
||||||
{showError && (
|
{showError && (
|
||||||
<FormMessage
|
<FormMessage
|
||||||
intent="warning"
|
intent="warning"
|
||||||
|
|
@ -117,68 +127,56 @@ export const Login = (props: LoginFormProps) => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<AuthCardHeader>
|
{SocialLoginList.length > 0 && (
|
||||||
<h1>{LOGIN_PAGE_TITLE}</h1>
|
<ThirdPartyAuth type={"SIGNIN"} logins={SocialLoginList} />
|
||||||
<h5>{LOGIN_PAGE_SUBTITLE}</h5>
|
)}
|
||||||
</AuthCardHeader>
|
<SpacedSubmitForm method="POST" action={loginURL}>
|
||||||
<AuthCardBody>
|
<FormGroup
|
||||||
<SpacedSubmitForm method="POST" action={loginURL}>
|
intent={error ? "danger" : "none"}
|
||||||
<FormGroup
|
label={LOGIN_PAGE_EMAIL_INPUT_LABEL}
|
||||||
intent={error ? "danger" : "none"}
|
>
|
||||||
label={LOGIN_PAGE_EMAIL_INPUT_LABEL}
|
<FormTextField
|
||||||
>
|
name={LOGIN_FORM_EMAIL_FIELD_NAME}
|
||||||
<FormTextField
|
type="email"
|
||||||
name={LOGIN_FORM_EMAIL_FIELD_NAME}
|
placeholder={LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
||||||
type="email"
|
autoFocus
|
||||||
placeholder={LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
/>
|
||||||
autoFocus
|
</FormGroup>
|
||||||
/>
|
<FormGroup
|
||||||
</FormGroup>
|
intent={error ? "danger" : "none"}
|
||||||
<FormGroup
|
label={LOGIN_PAGE_PASSWORD_INPUT_LABEL}
|
||||||
intent={error ? "danger" : "none"}
|
// helperText={FORM_VALIDATION_PASSWORD_RULE}
|
||||||
label={LOGIN_PAGE_PASSWORD_INPUT_LABEL}
|
>
|
||||||
helperText={FORM_VALIDATION_PASSWORD_RULE}
|
<FormTextField
|
||||||
>
|
type="password"
|
||||||
<FormTextField
|
name={LOGIN_FORM_PASSWORD_FIELD_NAME}
|
||||||
type="password"
|
placeholder={LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||||
name={LOGIN_FORM_PASSWORD_FIELD_NAME}
|
/>
|
||||||
placeholder={LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
</FormGroup>
|
||||||
/>
|
|
||||||
</FormGroup>
|
<FormActions>
|
||||||
<Link to={forgotPasswordURL}>{LOGIN_PAGE_FORGOT_PASSWORD_TEXT}</Link>
|
<Button
|
||||||
<FormActions>
|
tag="button"
|
||||||
<Button
|
type="submit"
|
||||||
type="submit"
|
disabled={!isFormValid}
|
||||||
disabled={!valid}
|
text={LOGIN_PAGE_LOGIN_BUTTON_TEXT}
|
||||||
text={LOGIN_PAGE_LOGIN_BUTTON_TEXT}
|
fill
|
||||||
intent="primary"
|
size={Size.large}
|
||||||
filled
|
onClick={() => {
|
||||||
size="large"
|
PerformanceTracker.startTracking(
|
||||||
onClick={() => {
|
PerformanceTransactionName.LOGIN_CLICK,
|
||||||
PerformanceTracker.startTracking(
|
);
|
||||||
PerformanceTransactionName.LOGIN_CLICK,
|
AnalyticsUtil.logEvent("LOGIN_CLICK", {
|
||||||
);
|
loginMethod: "EMAIL",
|
||||||
AnalyticsUtil.logEvent("LOGIN_CLICK", {
|
});
|
||||||
loginMethod: "EMAIL",
|
}}
|
||||||
});
|
/>
|
||||||
}}
|
</FormActions>
|
||||||
/>
|
</SpacedSubmitForm>
|
||||||
</FormActions>
|
<ForgotPasswordLink>
|
||||||
</SpacedSubmitForm>
|
<Link to={forgotPasswordURL}>{LOGIN_PAGE_FORGOT_PASSWORD_TEXT}</Link>
|
||||||
{SocialLoginList.length > 0 && (
|
</ForgotPasswordLink>
|
||||||
<>
|
</>
|
||||||
<Divider />
|
|
||||||
<ThirdPartyAuth type={"SIGNIN"} logins={SocialLoginList} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AuthCardBody>
|
|
||||||
<AuthCardNavLink to={signupURL}>
|
|
||||||
{LOGIN_PAGE_SIGN_UP_LINK_TEXT}
|
|
||||||
</AuthCardNavLink>
|
|
||||||
<AuthCardFooter>
|
|
||||||
<TncPPLinks />
|
|
||||||
</AuthCardFooter>
|
|
||||||
</AuthCardContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -190,5 +188,5 @@ export default connect((state) => ({
|
||||||
validate,
|
validate,
|
||||||
touchOnBlur: true,
|
touchOnBlur: true,
|
||||||
form: LOGIN_FORM_NAME,
|
form: LOGIN_FORM_NAME,
|
||||||
})(Login),
|
})(withTheme(Login)),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,32 @@ import { RESET_PASSWORD_FORM_NAME } from "constants/forms";
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
import { getIsTokenValid, getIsValidatingToken } from "selectors/authSelectors";
|
import { getIsTokenValid, getIsValidatingToken } from "selectors/authSelectors";
|
||||||
import { Icon } from "@blueprintjs/core";
|
import { Icon } from "@blueprintjs/core";
|
||||||
import FormTextField from "components/editorComponents/form/fields/TextField";
|
import FormGroup from "components/ads/formFields/FormGroup";
|
||||||
|
import FormTextField from "components/ads/formFields/TextField";
|
||||||
import FormMessage, {
|
import FormMessage, {
|
||||||
FormMessageProps,
|
|
||||||
MessageAction,
|
MessageAction,
|
||||||
} from "components/editorComponents/form/FormMessage";
|
FormMessageProps,
|
||||||
|
} from "components/ads/formFields/FormMessage";
|
||||||
import Spinner from "components/editorComponents/Spinner";
|
import Spinner from "components/editorComponents/Spinner";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button, { Size } from "components/ads/Button";
|
||||||
import FormGroup from "components/editorComponents/form/FormGroup";
|
|
||||||
import StyledForm from "components/editorComponents/Form";
|
import StyledForm from "components/editorComponents/Form";
|
||||||
import { isEmptyString, isStrongPassword } from "utils/formhelpers";
|
import { isEmptyString, isStrongPassword } from "utils/formhelpers";
|
||||||
import { ResetPasswordFormValues, resetPasswordSubmitHandler } from "./helpers";
|
import { ResetPasswordFormValues, resetPasswordSubmitHandler } from "./helpers";
|
||||||
import {
|
import {
|
||||||
AuthCardHeader,
|
AuthCardHeader,
|
||||||
AuthCardFooter,
|
|
||||||
AuthCardContainer,
|
|
||||||
AuthCardBody,
|
|
||||||
AuthCardNavLink,
|
AuthCardNavLink,
|
||||||
FormActions,
|
FormActions,
|
||||||
} from "./StyledComponents";
|
} from "./StyledComponents";
|
||||||
import { AUTH_LOGIN_URL, FORGOT_PASSWORD_URL } from "constants/routes";
|
import { AUTH_LOGIN_URL, FORGOT_PASSWORD_URL } from "constants/routes";
|
||||||
|
import { withTheme } from "styled-components";
|
||||||
|
import { Theme } from "constants/DefaultTheme";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL,
|
RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL,
|
||||||
RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||||
RESET_PASSWORD_LOGIN_LINK_TEXT,
|
RESET_PASSWORD_LOGIN_LINK_TEXT,
|
||||||
RESET_PASSWORD_SUBMIT_BUTTON_TEXT,
|
RESET_PASSWORD_SUBMIT_BUTTON_TEXT,
|
||||||
RESET_PASSWORD_PAGE_SUBTITLE,
|
|
||||||
RESET_PASSWORD_PAGE_TITLE,
|
RESET_PASSWORD_PAGE_TITLE,
|
||||||
FORM_VALIDATION_INVALID_PASSWORD,
|
FORM_VALIDATION_INVALID_PASSWORD,
|
||||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||||
|
|
@ -43,7 +42,6 @@ import {
|
||||||
RESET_PASSWORD_RESET_SUCCESS,
|
RESET_PASSWORD_RESET_SUCCESS,
|
||||||
RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
|
RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
|
||||||
} from "constants/messages";
|
} from "constants/messages";
|
||||||
import { TncPPLinks } from "./SignUp";
|
|
||||||
|
|
||||||
const validate = (values: ResetPasswordFormValues) => {
|
const validate = (values: ResetPasswordFormValues) => {
|
||||||
const errors: ResetPasswordFormValues = {};
|
const errors: ResetPasswordFormValues = {};
|
||||||
|
|
@ -66,6 +64,7 @@ type ResetPasswordProps = InjectedFormProps<
|
||||||
verifyToken: (token: string, email: string) => void;
|
verifyToken: (token: string, email: string) => void;
|
||||||
isTokenValid: boolean;
|
isTokenValid: boolean;
|
||||||
validatingToken: boolean;
|
validatingToken: boolean;
|
||||||
|
theme: Theme;
|
||||||
} & RouteComponentProps<{ email: string; token: string }>;
|
} & RouteComponentProps<{ email: string; token: string }>;
|
||||||
|
|
||||||
export const ResetPassword = (props: ResetPasswordProps) => {
|
export const ResetPassword = (props: ResetPasswordProps) => {
|
||||||
|
|
@ -154,50 +153,49 @@ export const ResetPassword = (props: ResetPasswordProps) => {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AuthCardContainer>
|
<>
|
||||||
|
<AuthCardHeader>
|
||||||
|
<h1>{RESET_PASSWORD_PAGE_TITLE}</h1>
|
||||||
|
</AuthCardHeader>
|
||||||
|
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
||||||
|
<Icon
|
||||||
|
icon="arrow-left"
|
||||||
|
style={{ marginRight: props.theme.spaces[3] }}
|
||||||
|
/>
|
||||||
|
{RESET_PASSWORD_LOGIN_LINK_TEXT}
|
||||||
|
</AuthCardNavLink>
|
||||||
|
</div>
|
||||||
{(showSuccessMessage || showFailureMessage) && (
|
{(showSuccessMessage || showFailureMessage) && (
|
||||||
<FormMessage {...messageTagProps} />
|
<FormMessage {...messageTagProps} />
|
||||||
)}
|
)}
|
||||||
<AuthCardHeader>
|
<StyledForm onSubmit={handleSubmit(resetPasswordSubmitHandler)}>
|
||||||
<h1>{RESET_PASSWORD_PAGE_TITLE}</h1>
|
<FormGroup
|
||||||
<h5>{RESET_PASSWORD_PAGE_SUBTITLE}</h5>
|
intent={error ? "danger" : "none"}
|
||||||
</AuthCardHeader>
|
label={RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL}
|
||||||
<AuthCardBody>
|
>
|
||||||
<StyledForm onSubmit={handleSubmit(resetPasswordSubmitHandler)}>
|
<FormTextField
|
||||||
<FormGroup
|
name="password"
|
||||||
intent={error ? "danger" : "none"}
|
type="password"
|
||||||
label={RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL}
|
placeholder={RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||||
>
|
disabled={submitSucceeded}
|
||||||
<FormTextField
|
/>
|
||||||
name="password"
|
</FormGroup>
|
||||||
type="password"
|
<Field type="hidden" name="email" component="input" />
|
||||||
placeholder={RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
<Field type="hidden" name="token" component="input" />
|
||||||
disabled={submitSucceeded}
|
<FormActions>
|
||||||
/>
|
<Button
|
||||||
</FormGroup>
|
tag="button"
|
||||||
<Field type="hidden" name="email" component="input" />
|
fill
|
||||||
<Field type="hidden" name="token" component="input" />
|
size={Size.large}
|
||||||
<FormActions>
|
type="submit"
|
||||||
<Button
|
text={RESET_PASSWORD_SUBMIT_BUTTON_TEXT}
|
||||||
filled
|
disabled={pristine || submitSucceeded}
|
||||||
size="large"
|
isLoading={submitting}
|
||||||
type="submit"
|
/>
|
||||||
text={RESET_PASSWORD_SUBMIT_BUTTON_TEXT}
|
</FormActions>
|
||||||
intent="primary"
|
</StyledForm>
|
||||||
disabled={pristine || submitSucceeded}
|
</>
|
||||||
loading={submitting}
|
|
||||||
/>
|
|
||||||
</FormActions>
|
|
||||||
</StyledForm>
|
|
||||||
</AuthCardBody>
|
|
||||||
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
|
||||||
{RESET_PASSWORD_LOGIN_LINK_TEXT}
|
|
||||||
<Icon icon="arrow-right" intent="primary" />
|
|
||||||
</AuthCardNavLink>
|
|
||||||
<AuthCardFooter>
|
|
||||||
<TncPPLinks></TncPPLinks>
|
|
||||||
</AuthCardFooter>
|
|
||||||
</AuthCardContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -232,5 +230,5 @@ export default connect(
|
||||||
validate,
|
validate,
|
||||||
form: RESET_PASSWORD_FORM_NAME,
|
form: RESET_PASSWORD_FORM_NAME,
|
||||||
touchOnBlur: true,
|
touchOnBlur: true,
|
||||||
})(withRouter(ResetPassword)),
|
})(withRouter(withTheme(ResetPassword))),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,33 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { reduxForm, InjectedFormProps } from "redux-form";
|
import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form";
|
||||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||||
import { SIGNUP_FORM_NAME } from "constants/forms";
|
import { SIGNUP_FORM_NAME } from "constants/forms";
|
||||||
import {
|
import { RouteComponentProps, useLocation, withRouter } from "react-router-dom";
|
||||||
Link,
|
|
||||||
RouteComponentProps,
|
|
||||||
useLocation,
|
|
||||||
withRouter,
|
|
||||||
} from "react-router-dom";
|
|
||||||
import Divider from "components/editorComponents/Divider";
|
|
||||||
import {
|
import {
|
||||||
AuthCardHeader,
|
AuthCardHeader,
|
||||||
AuthCardBody,
|
|
||||||
AuthCardFooter,
|
|
||||||
AuthCardNavLink,
|
AuthCardNavLink,
|
||||||
SpacedSubmitForm,
|
SpacedSubmitForm,
|
||||||
FormActions,
|
FormActions,
|
||||||
AuthCardContainer,
|
SignUpLinkSection,
|
||||||
} from "./StyledComponents";
|
} from "./StyledComponents";
|
||||||
import {
|
import {
|
||||||
SIGNUP_PAGE_TITLE,
|
SIGNUP_PAGE_TITLE,
|
||||||
SIGNUP_PAGE_SUBTITLE,
|
|
||||||
SIGNUP_PAGE_EMAIL_INPUT_LABEL,
|
SIGNUP_PAGE_EMAIL_INPUT_LABEL,
|
||||||
SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||||
SIGNUP_PAGE_PASSWORD_INPUT_LABEL,
|
SIGNUP_PAGE_PASSWORD_INPUT_LABEL,
|
||||||
SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||||
SIGNUP_PAGE_LOGIN_LINK_TEXT,
|
SIGNUP_PAGE_LOGIN_LINK_TEXT,
|
||||||
FORM_VALIDATION_EMPTY_EMAIL,
|
|
||||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||||
FORM_VALIDATION_INVALID_EMAIL,
|
FORM_VALIDATION_INVALID_EMAIL,
|
||||||
FORM_VALIDATION_INVALID_PASSWORD,
|
FORM_VALIDATION_INVALID_PASSWORD,
|
||||||
SIGNUP_PAGE_SUBMIT_BUTTON_TEXT,
|
SIGNUP_PAGE_SUBMIT_BUTTON_TEXT,
|
||||||
PRIVACY_POLICY_LINK,
|
ALREADY_HAVE_AN_ACCOUNT,
|
||||||
TERMS_AND_CONDITIONS_LINK,
|
|
||||||
FORM_VALIDATION_PASSWORD_RULE,
|
|
||||||
} from "constants/messages";
|
} from "constants/messages";
|
||||||
import FormMessage from "components/editorComponents/form/FormMessage";
|
import FormMessage from "components/ads/formFields/FormMessage";
|
||||||
import FormGroup from "components/editorComponents/form/FormGroup";
|
import FormGroup from "components/ads/formFields/FormGroup";
|
||||||
import FormTextField from "components/editorComponents/form/FormTextField";
|
import FormTextField from "components/ads/formFields/TextField";
|
||||||
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
||||||
import Button from "components/editorComponents/Button";
|
import Button, { Size } from "components/ads/Button";
|
||||||
|
|
||||||
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
||||||
|
|
||||||
|
|
@ -54,28 +42,16 @@ import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
import { setOnboardingState } from "utils/storage";
|
import { setOnboardingState } from "utils/storage";
|
||||||
const {
|
|
||||||
enableGithubOAuth,
|
import { SIGNUP_FORM_EMAIL_FIELD_NAME } from "constants/forms";
|
||||||
enableGoogleOAuth,
|
|
||||||
enableTNCPP,
|
const { enableGithubOAuth, enableGoogleOAuth } = getAppsmithConfigs();
|
||||||
} = getAppsmithConfigs();
|
|
||||||
const SocialLoginList: string[] = [];
|
const SocialLoginList: string[] = [];
|
||||||
if (enableGithubOAuth) SocialLoginList.push(SocialLoginTypes.GITHUB);
|
if (enableGithubOAuth) SocialLoginList.push(SocialLoginTypes.GITHUB);
|
||||||
if (enableGoogleOAuth) SocialLoginList.push(SocialLoginTypes.GOOGLE);
|
if (enableGoogleOAuth) SocialLoginList.push(SocialLoginTypes.GOOGLE);
|
||||||
|
|
||||||
export const TncPPLinks = () => {
|
import { withTheme } from "styled-components";
|
||||||
if (!enableTNCPP) return null;
|
import { Theme } from "constants/DefaultTheme";
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Link target="_blank" to="/privacy-policy.html">
|
|
||||||
{PRIVACY_POLICY_LINK}
|
|
||||||
</Link>
|
|
||||||
<Link target="_blank" to="/terms-and-conditions.html">
|
|
||||||
{TERMS_AND_CONDITIONS_LINK}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validate = (values: SignupFormValues) => {
|
const validate = (values: SignupFormValues) => {
|
||||||
const errors: SignupFormValues = {};
|
const errors: SignupFormValues = {};
|
||||||
|
|
@ -84,19 +60,24 @@ const validate = (values: SignupFormValues) => {
|
||||||
} else if (!isStrongPassword(values.password)) {
|
} else if (!isStrongPassword(values.password)) {
|
||||||
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
|
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
|
||||||
}
|
}
|
||||||
if (!values.email || isEmptyString(values.email)) {
|
|
||||||
errors.email = FORM_VALIDATION_EMPTY_EMAIL;
|
const email = values.email || "";
|
||||||
} else if (!isEmail(values.email)) {
|
if (!isEmptyString(email) && !isEmail(email)) {
|
||||||
errors.email = FORM_VALIDATION_INVALID_EMAIL;
|
errors.email = FORM_VALIDATION_INVALID_EMAIL;
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SignUpFormProps = InjectedFormProps<SignupFormValues> &
|
type SignUpFormProps = InjectedFormProps<
|
||||||
RouteComponentProps<{ email: string }>;
|
SignupFormValues,
|
||||||
|
{ emailValue: string }
|
||||||
|
> &
|
||||||
|
RouteComponentProps<{ email: string }> & { theme: Theme; emailValue: string };
|
||||||
|
|
||||||
export const SignUp = (props: SignUpFormProps) => {
|
export const SignUp = (props: SignUpFormProps) => {
|
||||||
const { error, submitting, pristine, valid } = props;
|
const { error, submitting, pristine, valid, emailValue: email } = props;
|
||||||
|
const isFormValid = valid && email && !isEmptyString(email);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
let showError = false;
|
let showError = false;
|
||||||
|
|
@ -115,85 +96,84 @@ export const SignUp = (props: SignUpFormProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthCardContainer>
|
<>
|
||||||
{showError && <FormMessage intent="danger" message={errorMessage} />}
|
{showError && <FormMessage intent="danger" message={errorMessage} />}
|
||||||
<AuthCardHeader>
|
<AuthCardHeader>
|
||||||
<h1>{SIGNUP_PAGE_TITLE}</h1>
|
<h1>{SIGNUP_PAGE_TITLE}</h1>
|
||||||
<h5>{SIGNUP_PAGE_SUBTITLE}</h5>
|
|
||||||
</AuthCardHeader>
|
</AuthCardHeader>
|
||||||
<AuthCardBody>
|
<SignUpLinkSection>
|
||||||
<SpacedSubmitForm method="POST" action={signupURL}>
|
{ALREADY_HAVE_AN_ACCOUNT}
|
||||||
<FormGroup
|
<AuthCardNavLink
|
||||||
intent={error ? "danger" : "none"}
|
to={AUTH_LOGIN_URL}
|
||||||
label={SIGNUP_PAGE_EMAIL_INPUT_LABEL}
|
style={{ marginLeft: props.theme.spaces[3] }}
|
||||||
>
|
>
|
||||||
<FormTextField
|
{SIGNUP_PAGE_LOGIN_LINK_TEXT}
|
||||||
name="email"
|
</AuthCardNavLink>
|
||||||
type="email"
|
</SignUpLinkSection>
|
||||||
placeholder={SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
{SocialLoginList.length > 0 && (
|
||||||
autoFocus
|
<ThirdPartyAuth type={"SIGNUP"} logins={SocialLoginList} />
|
||||||
/>
|
)}
|
||||||
</FormGroup>
|
<SpacedSubmitForm method="POST" action={signupURL}>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
intent={error ? "danger" : "none"}
|
intent={error ? "danger" : "none"}
|
||||||
label={SIGNUP_PAGE_PASSWORD_INPUT_LABEL}
|
label={SIGNUP_PAGE_EMAIL_INPUT_LABEL}
|
||||||
helperText={FORM_VALIDATION_PASSWORD_RULE}
|
>
|
||||||
>
|
<FormTextField
|
||||||
<FormTextField
|
name="email"
|
||||||
type="password"
|
type="email"
|
||||||
name="password"
|
placeholder={SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
||||||
placeholder={SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormActions>
|
<FormGroup
|
||||||
<Button
|
intent={error ? "danger" : "none"}
|
||||||
type="submit"
|
label={SIGNUP_PAGE_PASSWORD_INPUT_LABEL}
|
||||||
disabled={pristine || !valid}
|
// helperText={FORM_VALIDATION_PASSWORD_RULE}
|
||||||
loading={submitting}
|
>
|
||||||
text={SIGNUP_PAGE_SUBMIT_BUTTON_TEXT}
|
<FormTextField
|
||||||
intent="primary"
|
type="password"
|
||||||
filled
|
name="password"
|
||||||
size="large"
|
placeholder={SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||||
onClick={() => {
|
/>
|
||||||
AnalyticsUtil.logEvent("SIGNUP_CLICK", {
|
</FormGroup>
|
||||||
signupMethod: "EMAIL",
|
<FormActions>
|
||||||
});
|
<Button
|
||||||
PerformanceTracker.startTracking(
|
tag="button"
|
||||||
PerformanceTransactionName.SIGN_UP,
|
type="submit"
|
||||||
);
|
disabled={pristine || !isFormValid}
|
||||||
setOnboardingState(true);
|
isLoading={submitting}
|
||||||
}}
|
text={SIGNUP_PAGE_SUBMIT_BUTTON_TEXT}
|
||||||
/>
|
fill
|
||||||
</FormActions>
|
size={Size.large}
|
||||||
</SpacedSubmitForm>
|
onClick={() => {
|
||||||
{SocialLoginList.length > 0 && (
|
AnalyticsUtil.logEvent("SIGNUP_CLICK", {
|
||||||
<>
|
signupMethod: "EMAIL",
|
||||||
<Divider />
|
});
|
||||||
<ThirdPartyAuth type={"SIGNUP"} logins={SocialLoginList} />
|
PerformanceTracker.startTracking(
|
||||||
</>
|
PerformanceTransactionName.SIGN_UP,
|
||||||
)}
|
);
|
||||||
</AuthCardBody>
|
setOnboardingState(true);
|
||||||
<AuthCardFooter>
|
}}
|
||||||
<TncPPLinks />
|
/>
|
||||||
</AuthCardFooter>
|
</FormActions>
|
||||||
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
</SpacedSubmitForm>
|
||||||
{SIGNUP_PAGE_LOGIN_LINK_TEXT}
|
</>
|
||||||
</AuthCardNavLink>
|
|
||||||
</AuthCardContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selector = formValueSelector(SIGNUP_FORM_NAME);
|
||||||
export default connect((state: AppState, props: SignUpFormProps) => {
|
export default connect((state: AppState, props: SignUpFormProps) => {
|
||||||
const queryParams = new URLSearchParams(props.location.search);
|
const queryParams = new URLSearchParams(props.location.search);
|
||||||
return {
|
return {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
email: queryParams.get("email"),
|
email: queryParams.get("email"),
|
||||||
},
|
},
|
||||||
|
emailValue: selector(state, SIGNUP_FORM_EMAIL_FIELD_NAME),
|
||||||
};
|
};
|
||||||
}, null)(
|
}, null)(
|
||||||
reduxForm<SignupFormValues>({
|
reduxForm<SignupFormValues, { emailValue: string }>({
|
||||||
validate,
|
validate,
|
||||||
form: SIGNUP_FORM_NAME,
|
form: SIGNUP_FORM_NAME,
|
||||||
touchOnBlur: true,
|
touchOnBlur: true,
|
||||||
})(withRouter(SignUp)),
|
})(withRouter(withTheme(SignUp))),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,69 @@
|
||||||
import styled, { css } from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Form from "components/editorComponents/Form";
|
import Form from "components/editorComponents/Form";
|
||||||
import { Card } from "@blueprintjs/core";
|
import { Card } from "@blueprintjs/core";
|
||||||
|
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||||
|
import { Classes } from "@blueprintjs/core";
|
||||||
|
|
||||||
export const AuthContainer = styled.section`
|
export const AuthContainer = styled.section`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: ${(props) => `calc(100vh - ${props.theme.headerHeight})`};
|
||||||
will-change: transform, opacity;
|
background-color: ${(props) => props.theme.colors.auth.background};
|
||||||
`;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
export const AuthCard = styled(Card)`
|
& .${Classes.FORM_GROUP} {
|
||||||
&& {
|
margin: 0 0 ${(props) => props.theme.spaces[2]}px;
|
||||||
width: ${(props) => props.theme.authCard.width}px;
|
|
||||||
background: ${(props) => props.theme.authCard.background};
|
|
||||||
border-radius: ${(props) => props.theme.authCard.borderRadius}px;
|
|
||||||
padding: ${(props) => props.theme.authCard.padding}px;
|
|
||||||
box-shadow: ${(props) => props.theme.authCard.shadow};
|
|
||||||
border: none;
|
|
||||||
& h1,
|
|
||||||
h5 {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: ${(props) => props.theme.fontWeights[1]};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AuthCardContainer = styled.div``;
|
export const AuthCardContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: ${(props) => props.theme.authCard.padding}px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AuthCard = styled(Card)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: ${(props) => props.theme.colors.auth.cardBackground};
|
||||||
|
padding: ${(props) => props.theme.spaces[15]}px 64px;
|
||||||
|
width: ${(props) => props.theme.authCard.width}px;
|
||||||
|
border: none;
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
${(props) => getTypographyByKey(props, "authCardHeader")}
|
||||||
|
color: ${(props) => props.theme.colors.auth.headingText};
|
||||||
|
}
|
||||||
|
& .form-message-container {
|
||||||
|
width: ${(props) => props.theme.authCard.formMessageWidth}px;
|
||||||
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.form-message-container ~ .form-message-container {
|
||||||
|
margin-top: ${(props) => props.theme.spaces[4]}px;
|
||||||
|
}
|
||||||
|
& > div {
|
||||||
|
margin-bottom: ${(props) => props.theme.spaces[14]}px;
|
||||||
|
}
|
||||||
|
& > div:last-child,
|
||||||
|
& > div:empty {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const AuthCardHeader = styled.header`
|
export const AuthCardHeader = styled.header`
|
||||||
& {
|
& {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: ${(props) => props.theme.fontSizes[6]}px;
|
font-size: ${(props) => props.theme.fontSizes[6]}px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
h5 {
|
h5 {
|
||||||
font-size: ${(props) => props.theme.fontSizes[4]}px;
|
font-size: ${(props) => props.theme.fontSizes[4]}px;
|
||||||
|
|
@ -42,12 +73,10 @@ export const AuthCardHeader = styled.header`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AuthCardNavLink = styled(Link)`
|
export const AuthCardNavLink = styled(Link)`
|
||||||
text-align: center;
|
border-bottom: 1px solid transparent;
|
||||||
margin: 0 auto;
|
&:hover {
|
||||||
display: block;
|
border-bottom: 1px solid ${(props) => props.theme.colors.auth.link};
|
||||||
margin-top: ${(props) => props.theme.spaces[12]}px;
|
text-decoration: none;
|
||||||
& span {
|
|
||||||
margin-left: ${(props) => props.theme.spaces[4]}px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -60,26 +89,15 @@ export const AuthCardFooter = styled.footer`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AuthCardBody = styled.div`
|
export const AuthCardBody = styled.div`
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
& a {
|
& a {
|
||||||
margin-top: ${(props) => props.theme.spaces[8]}px;
|
margin-top: ${(props) => props.theme.spaces[8]}px;
|
||||||
font-size: ${(props) => props.theme.fontSizes[2]}px;
|
font-size: ${(props) => props.theme.fontSizes[2]}px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const formSpacing = css`
|
export const SpacedForm = styled(Form)``;
|
||||||
flex-grow: 1;
|
|
||||||
margin-right: ${(props) => props.theme.authCard.dividerSpacing}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SpacedForm = styled(Form)`
|
|
||||||
${formSpacing}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SpacedSubmitForm = styled.form`
|
export const SpacedSubmitForm = styled.form`
|
||||||
${formSpacing}
|
|
||||||
& a {
|
& a {
|
||||||
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
||||||
}
|
}
|
||||||
|
|
@ -95,8 +113,29 @@ export const FormActions = styled.div`
|
||||||
}
|
}
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
margin-top: ${(props) => props.theme.spaces[2]}px;
|
margin-top: ${(props) => props.theme.spaces[5]}px;
|
||||||
& > label {
|
& > label {
|
||||||
margin-right: ${(props) => props.theme.spaces[11]}px;
|
margin-right: ${(props) => props.theme.spaces[11]}px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const SignUpLinkSection = styled.div`
|
||||||
|
${(props) => getTypographyByKey(props, "authCardSubheader")}
|
||||||
|
color: ${(props) => props.theme.colors.auth.text};
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ForgotPasswordLink = styled.div`
|
||||||
|
${(props) => getTypographyByKey(props, "authCardSubheader")}
|
||||||
|
color: ${(props) => props.theme.colors.auth.text};
|
||||||
|
text-align: center;
|
||||||
|
margin-top: ${(props) => props.theme.spaces[11]}px;
|
||||||
|
& a {
|
||||||
|
color: ${(props) => props.theme.colors.auth.text};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormMessagesContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
getSocialLoginButtonProps,
|
getSocialLoginButtonProps,
|
||||||
SocialLoginType,
|
SocialLoginType,
|
||||||
} from "constants/SocialLogin";
|
} from "constants/SocialLogin";
|
||||||
import { IntentColors, getBorderCSSShorthand } from "constants/DefaultTheme";
|
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||||
import AnalyticsUtil, { EventName } from "utils/AnalyticsUtil";
|
import AnalyticsUtil, { EventName } from "utils/AnalyticsUtil";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
|
|
@ -15,52 +15,42 @@ import { setOnboardingState } from "utils/storage";
|
||||||
const ThirdPartyAuthWrapper = styled.div`
|
const ThirdPartyAuthWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-end;
|
|
||||||
margin-left: ${(props) => props.theme.authCard.dividerSpacing}px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
//TODO(abhinav): Port this to use themes.
|
//TODO(abhinav): Port this to use themes.
|
||||||
const StyledSocialLoginButton = styled.a`
|
const StyledSocialLoginButton = styled.a`
|
||||||
width: 200px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: ${(props) => getBorderCSSShorthand(props.theme.borders[2])};
|
justify-content: center;
|
||||||
padding: 8px;
|
border: solid 1px ${(props) => props.theme.colors.auth.socialBtnBorder};
|
||||||
color: ${(props) => props.theme.colors.textDefault};
|
padding: ${(props) => props.theme.spaces[2]}px;
|
||||||
border-radius: ${(props) => props.theme.radii[1]}px;
|
|
||||||
position: relative;
|
&:first-child {
|
||||||
height: 42px;
|
margin-bottom: ${(props) => props.theme.spaces[4]}px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:only-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background: ${IntentColors.success};
|
background-color: ${(props) => props.theme.colors.auth.socialBtnHighlight};
|
||||||
color: ${(props) => props.theme.colors.textOnDarkBG};
|
|
||||||
}
|
}
|
||||||
& > div {
|
|
||||||
width: 36px;
|
& .login-method {
|
||||||
height: 36px;
|
${(props) => getTypographyByKey(props, "btnLarge")}
|
||||||
padding: ${(props) => props.theme.radii[1]}px;
|
color: ${(props) => props.theme.colors.auth.socialBtnText};
|
||||||
position: absolute;
|
text-transform: uppercase;
|
||||||
left: 2px;
|
|
||||||
top: 2px;
|
|
||||||
background: white;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
& img {
|
|
||||||
width: 80%;
|
|
||||||
height: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
& p {
|
|
||||||
display: block;
|
|
||||||
margin: 0 0 0 36px;
|
|
||||||
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
|
||||||
font-weight: ${(props) => props.theme.fontWeights[3]};
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ButtonLogo = styled.img`
|
||||||
|
margin: ${(props) => props.theme.spaces[2]}px;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const SocialLoginTypes: Record<string, string> = {
|
export const SocialLoginTypes: Record<string, string> = {
|
||||||
GOOGLE: "google",
|
GOOGLE: "google",
|
||||||
GITHUB: "github",
|
GITHUB: "github",
|
||||||
|
|
@ -102,10 +92,8 @@ const SocialLoginButton = (props: {
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
<ButtonLogo alt={` ${props.name} login`} src={props.logo} />
|
||||||
<img alt={` ${props.name} login`} src={props.logo} />
|
<div className="login-method">{`continue with ${props.name}`}</div>
|
||||||
</div>
|
|
||||||
<p>{`Sign in with ${props.name}`}</p>
|
|
||||||
</StyledSocialLoginButton>
|
</StyledSocialLoginButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,43 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Switch, useRouteMatch, useLocation, Route } from "react-router-dom";
|
import { Switch, useRouteMatch, useLocation, Route } from "react-router-dom";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import Centered from "components/designSystems/appsmith/CenteredWrapper";
|
import { AuthContainer, AuthCard, AuthCardContainer } from "./StyledComponents";
|
||||||
import { animated, useTransition } from "react-spring";
|
|
||||||
import { AuthContainer, AuthCard } from "./StyledComponents";
|
|
||||||
import SignUp from "./SignUp";
|
import SignUp from "./SignUp";
|
||||||
import ForgotPassword from "./ForgotPassword";
|
import ForgotPassword from "./ForgotPassword";
|
||||||
import ResetPassword from "./ResetPassword";
|
import ResetPassword from "./ResetPassword";
|
||||||
import PageNotFound from "pages/common/PageNotFound";
|
import PageNotFound from "pages/common/PageNotFound";
|
||||||
|
import FooterLinks from "./FooterLinks";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||||
|
|
||||||
const AnimatedAuthCard = animated(AuthContainer);
|
|
||||||
export const UserAuth = () => {
|
export const UserAuth = () => {
|
||||||
const { path } = useRouteMatch();
|
const { path } = useRouteMatch();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const transitions = useTransition(location, (location) => location.pathname, {
|
|
||||||
from: { opacity: 0, transform: "translate3d(50%,0,0)" },
|
return (
|
||||||
enter: { opacity: 1, transform: "translate3d(0%,0,0)" },
|
<AuthContainer>
|
||||||
leave: { opacity: 0, transform: "translate3d(-50%,0,0)" },
|
<AuthCardContainer>
|
||||||
reset: true,
|
<AuthCard>
|
||||||
});
|
<Switch location={location}>
|
||||||
const renderTransitions = transitions.map(
|
<SentryRoute exact path={`${path}/login`} component={Login} />
|
||||||
({ item: location, props, key }) => (
|
<SentryRoute exact path={`${path}/signup`} component={SignUp} />
|
||||||
<AnimatedAuthCard key={key} style={props}>
|
<SentryRoute
|
||||||
<Centered>
|
exact
|
||||||
<AuthCard>
|
path={`${path}/resetPassword`}
|
||||||
<Switch location={location}>
|
component={ResetPassword}
|
||||||
<SentryRoute exact path={`${path}/login`} component={Login} />
|
/>
|
||||||
<SentryRoute exact path={`${path}/signup`} component={SignUp} />
|
<SentryRoute
|
||||||
<SentryRoute
|
exact
|
||||||
exact
|
path={`${path}/forgotPassword`}
|
||||||
path={`${path}/resetPassword`}
|
component={ForgotPassword}
|
||||||
component={ResetPassword}
|
/>
|
||||||
/>
|
<SentryRoute component={PageNotFound} />
|
||||||
<SentryRoute
|
</Switch>
|
||||||
exact
|
</AuthCard>
|
||||||
path={`${path}/forgotPassword`}
|
</AuthCardContainer>
|
||||||
component={ForgotPassword}
|
<FooterLinks />
|
||||||
/>
|
</AuthContainer>
|
||||||
<SentryRoute component={PageNotFound} />
|
|
||||||
</Switch>
|
|
||||||
</AuthCard>
|
|
||||||
</Centered>
|
|
||||||
</AnimatedAuthCard>
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return <React.Fragment>{renderTransitions}</React.Fragment>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserAuth;
|
export default UserAuth;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const StyledPageHeader = styled(StyledHeader)`
|
||||||
height: 48px;
|
height: 48px;
|
||||||
background: ${Colors.BALTIC_SEA};
|
background: ${Colors.BALTIC_SEA};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.05);
|
||||||
padding: 0px ${(props) => props.theme.spaces[12]};
|
padding: 0px ${(props) => props.theme.spaces[12]};
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ export default function MemberSettings(props: PageProps) {
|
||||||
{
|
{
|
||||||
Header: "Delete",
|
Header: "Delete",
|
||||||
accessor: "delete",
|
accessor: "delete",
|
||||||
|
disableSortBy: true,
|
||||||
Cell: function DeleteCell(cellProps: any) {
|
Cell: function DeleteCell(cellProps: any) {
|
||||||
if (
|
if (
|
||||||
cellProps.cell.row.values.username ===
|
cellProps.cell.row.values.username ===
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { WidgetProps } from "widgets/BaseWidget";
|
import { WidgetProps } from "widgets/BaseWidget";
|
||||||
import { UpdateWidgetPropertyPayload } from "actions/controlActions";
|
import { UpdateWidgetPropertyPayload } from "actions/controlActions";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const initialState: CanvasWidgetsReduxState = {};
|
const initialState: CanvasWidgetsReduxState = {};
|
||||||
|
|
||||||
|
|
@ -14,8 +15,7 @@ export type FlattenedWidgetProps = WidgetProps & {
|
||||||
};
|
};
|
||||||
|
|
||||||
const canvasWidgetsReducer = createImmerReducer(initialState, {
|
const canvasWidgetsReducer = createImmerReducer(initialState, {
|
||||||
// TODO Rename to INIT_LAYOUT
|
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: (
|
||||||
[ReduxActionTypes.UPDATE_CANVAS]: (
|
|
||||||
state: CanvasWidgetsReduxState,
|
state: CanvasWidgetsReduxState,
|
||||||
action: ReduxAction<UpdateCanvasPayload>,
|
action: ReduxAction<UpdateCanvasPayload>,
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -31,8 +31,13 @@ const canvasWidgetsReducer = createImmerReducer(initialState, {
|
||||||
state: CanvasWidgetsReduxState,
|
state: CanvasWidgetsReduxState,
|
||||||
action: ReduxAction<UpdateWidgetPropertyPayload>,
|
action: ReduxAction<UpdateWidgetPropertyPayload>,
|
||||||
) => {
|
) => {
|
||||||
state[action.payload.widgetId][action.payload.propertyName] =
|
// We loop over all updates
|
||||||
action.payload.propertyValue;
|
Object.entries(action.payload.updates).forEach(
|
||||||
|
([propertyPath, propertyValue]) => {
|
||||||
|
// since property paths could be nested, we use lodash set method
|
||||||
|
_.set(state[action.payload.widgetId], propertyPath, propertyValue);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ const editorReducer = createReducer(initialState, {
|
||||||
state.loadingStates.savingError = true;
|
state.loadingStates.savingError = true;
|
||||||
return { ...state };
|
return { ...state };
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.UPDATE_CANVAS]: (
|
[ReduxActionTypes.INIT_CANVAS_LAYOUT]: (
|
||||||
state: EditorReduxState,
|
state: EditorReduxState,
|
||||||
action: ReduxAction<UpdateCanvasPayload>,
|
action: ReduxAction<UpdateCanvasPayload>,
|
||||||
) => {
|
) => {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import history from "utils/history";
|
import history from "utils/history";
|
||||||
import {
|
import {
|
||||||
BUILDER_PAGE_URL,
|
BUILDER_PAGE_URL,
|
||||||
|
convertToQueryParams,
|
||||||
getApplicationViewerPageURL,
|
getApplicationViewerPageURL,
|
||||||
} from "constants/routes";
|
} from "constants/routes";
|
||||||
import {
|
import {
|
||||||
|
|
@ -86,42 +87,61 @@ import {
|
||||||
} from "./EvaluationsSaga";
|
} from "./EvaluationsSaga";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
|
|
||||||
|
export enum NavigationTargetType {
|
||||||
|
SAME_WINDOW = "SAME_WINDOW",
|
||||||
|
NEW_WINDOW = "NEW_WINDOW",
|
||||||
|
}
|
||||||
|
|
||||||
function* navigateActionSaga(
|
function* navigateActionSaga(
|
||||||
action: { pageNameOrUrl: string; params: Record<string, string> },
|
action: {
|
||||||
|
pageNameOrUrl: string;
|
||||||
|
params: Record<string, string>;
|
||||||
|
target?: NavigationTargetType;
|
||||||
|
},
|
||||||
event: ExecuteActionPayloadEvent,
|
event: ExecuteActionPayloadEvent,
|
||||||
) {
|
) {
|
||||||
const pageList = yield select(getPageList);
|
const pageList = yield select(getPageList);
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
|
const {
|
||||||
|
pageNameOrUrl,
|
||||||
|
params,
|
||||||
|
target = NavigationTargetType.SAME_WINDOW,
|
||||||
|
} = action;
|
||||||
const page = _.find(
|
const page = _.find(
|
||||||
pageList,
|
pageList,
|
||||||
(page: Page) => page.pageName === action.pageNameOrUrl,
|
(page: Page) => page.pageName === pageNameOrUrl,
|
||||||
);
|
);
|
||||||
if (page) {
|
if (page) {
|
||||||
AnalyticsUtil.logEvent("NAVIGATE", {
|
AnalyticsUtil.logEvent("NAVIGATE", {
|
||||||
pageName: action.pageNameOrUrl,
|
pageName: pageNameOrUrl,
|
||||||
pageParams: action.params,
|
pageParams: params,
|
||||||
});
|
});
|
||||||
// TODO need to make this check via RENDER_MODE;
|
const appMode = yield select(getAppMode);
|
||||||
const path =
|
const path =
|
||||||
history.location.pathname.indexOf("/edit") !== -1
|
appMode === APP_MODE.EDIT
|
||||||
? BUILDER_PAGE_URL(applicationId, page.pageId, action.params)
|
? BUILDER_PAGE_URL(applicationId, page.pageId, params)
|
||||||
: getApplicationViewerPageURL(
|
: getApplicationViewerPageURL(applicationId, page.pageId, params);
|
||||||
applicationId,
|
if (target === NavigationTargetType.SAME_WINDOW) {
|
||||||
page.pageId,
|
history.push(path);
|
||||||
action.params,
|
} else if (target === NavigationTargetType.NEW_WINDOW) {
|
||||||
);
|
window.open(path, "_blank");
|
||||||
history.push(path);
|
}
|
||||||
if (event.callback) event.callback({ success: true });
|
if (event.callback) event.callback({ success: true });
|
||||||
} else {
|
} else {
|
||||||
AnalyticsUtil.logEvent("NAVIGATE", {
|
AnalyticsUtil.logEvent("NAVIGATE", {
|
||||||
navUrl: action.pageNameOrUrl,
|
navUrl: pageNameOrUrl,
|
||||||
});
|
});
|
||||||
// Add a default protocol if it doesn't exist.
|
// Add a default protocol if it doesn't exist.
|
||||||
let url = action.pageNameOrUrl;
|
let url = pageNameOrUrl + convertToQueryParams(params);
|
||||||
if (url.indexOf("://") === -1) {
|
if (url.indexOf("://") === -1) {
|
||||||
url = "https://" + url;
|
url = "https://" + url;
|
||||||
}
|
}
|
||||||
window.location.assign(url);
|
if (target === NavigationTargetType.SAME_WINDOW) {
|
||||||
|
window.location.assign(url);
|
||||||
|
} else if (target === NavigationTargetType.NEW_WINDOW) {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
}
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import {
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||||
import { Action } from "entities/Action";
|
import { Action, ActionViewMode } from "entities/Action";
|
||||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||||
import {
|
import {
|
||||||
getAction,
|
getAction,
|
||||||
|
|
@ -145,14 +145,22 @@ export function* fetchActionsForViewModeSaga(
|
||||||
{ mode: "VIEWER", appId: applicationId },
|
{ mode: "VIEWER", appId: applicationId },
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const response: GenericApiResponse<Action[]> = yield ActionAPI.fetchActionsForViewMode(
|
const response: GenericApiResponse<ActionViewMode[]> = yield ActionAPI.fetchActionsForViewMode(
|
||||||
applicationId,
|
applicationId,
|
||||||
);
|
);
|
||||||
|
const correctFormatResponse = response.data.map((action) => {
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
actionConfiguration: {
|
||||||
|
timeoutInMillisecond: action.timeoutInMillisecond,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
const isValidResponse = yield validateResponse(response);
|
const isValidResponse = yield validateResponse(response);
|
||||||
if (isValidResponse) {
|
if (isValidResponse) {
|
||||||
yield put({
|
yield put({
|
||||||
type: ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
|
type: ReduxActionTypes.FETCH_ACTIONS_VIEW_MODE_SUCCESS,
|
||||||
payload: response.data,
|
payload: correctFormatResponse,
|
||||||
});
|
});
|
||||||
PerformanceTracker.stopAsyncTracking(
|
PerformanceTracker.stopAsyncTracking(
|
||||||
PerformanceTransactionName.FETCH_ACTIONS_API,
|
PerformanceTransactionName.FETCH_ACTIONS_API,
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,9 @@ function* switchDatasourceSaga(action: ReduxAction<{ datasourceId: string }>) {
|
||||||
(datasource: Datasource) => datasource.id === datasourceId,
|
(datasource: Datasource) => datasource.id === datasourceId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
yield put(changeDatasource(datasource));
|
if (datasource) {
|
||||||
|
yield put(changeDatasource(datasource));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* formValueChangeSaga(
|
function* formValueChangeSaga(
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import {
|
||||||
} from "constants/OnboardingConstants";
|
} from "constants/OnboardingConstants";
|
||||||
import AnalyticsUtil from "../utils/AnalyticsUtil";
|
import AnalyticsUtil from "../utils/AnalyticsUtil";
|
||||||
import { get } from "lodash";
|
import { get } from "lodash";
|
||||||
|
import { updateWidgetProperty } from "../actions/controlActions";
|
||||||
|
|
||||||
export const getCurrentStep = (state: AppState) =>
|
export const getCurrentStep = (state: AppState) =>
|
||||||
state.ui.onBoarding.currentStep;
|
state.ui.onBoarding.currentStep;
|
||||||
|
|
@ -85,14 +86,9 @@ function* listenForWidgetAdditions() {
|
||||||
canvasWidgets[selectedWidget.widgetId]
|
canvasWidgets[selectedWidget.widgetId]
|
||||||
) {
|
) {
|
||||||
if (selectedWidget.tableData === initialTableData) {
|
if (selectedWidget.tableData === initialTableData) {
|
||||||
yield put({
|
yield put(
|
||||||
type: "UPDATE_WIDGET_PROPERTY",
|
updateWidgetProperty(selectedWidget.widgetId, { tableData: [] }),
|
||||||
payload: {
|
);
|
||||||
widgetId: selectedWidget.widgetId,
|
|
||||||
propertyName: "tableData",
|
|
||||||
propertyValue: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsUtil.logEvent("ONBOARDING_ADD_WIDGET");
|
AnalyticsUtil.logEvent("ONBOARDING_ADD_WIDGET");
|
||||||
|
|
@ -107,11 +103,11 @@ function* listenForWidgetAdditions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* listenForSuccessfullBinding() {
|
function* listenForSuccessfulBinding() {
|
||||||
while (true) {
|
while (true) {
|
||||||
yield take();
|
yield take();
|
||||||
|
|
||||||
let bindSuccessfull = true;
|
let bindSuccessful = true;
|
||||||
const selectedWidget = yield select(getSelectedWidget);
|
const selectedWidget = yield select(getSelectedWidget);
|
||||||
if (selectedWidget && selectedWidget.type === "TABLE_WIDGET") {
|
if (selectedWidget && selectedWidget.type === "TABLE_WIDGET") {
|
||||||
const dataTree = yield select(getDataTree);
|
const dataTree = yield select(getDataTree);
|
||||||
|
|
@ -124,19 +120,19 @@ function* listenForSuccessfullBinding() {
|
||||||
const hasBinding =
|
const hasBinding =
|
||||||
dynamicBindingPathList && !!dynamicBindingPathList.length;
|
dynamicBindingPathList && !!dynamicBindingPathList.length;
|
||||||
|
|
||||||
bindSuccessfull =
|
bindSuccessful =
|
||||||
bindSuccessfull && hasBinding && tableHasData && tableHasData.length;
|
bindSuccessful && hasBinding && tableHasData && tableHasData.length;
|
||||||
|
|
||||||
if (widgetProperties.invalidProps) {
|
if (widgetProperties.invalidProps) {
|
||||||
bindSuccessfull =
|
bindSuccessful =
|
||||||
bindSuccessfull &&
|
bindSuccessful &&
|
||||||
!(
|
!(
|
||||||
"tableData" in widgetProperties.invalidProps &&
|
"tableData" in widgetProperties.invalidProps &&
|
||||||
widgetProperties.invalidProps.tableData
|
widgetProperties.invalidProps.tableData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bindSuccessfull) {
|
if (bindSuccessful) {
|
||||||
yield put(showTooltip(OnboardingStep.NONE));
|
yield put(showTooltip(OnboardingStep.NONE));
|
||||||
AnalyticsUtil.logEvent("ONBOARDING_SUCCESSFUL_BINDING");
|
AnalyticsUtil.logEvent("ONBOARDING_SUCCESSFUL_BINDING");
|
||||||
yield put(setCurrentStep(OnboardingStep.SUCCESSFUL_BINDING));
|
yield put(setCurrentStep(OnboardingStep.SUCCESSFUL_BINDING));
|
||||||
|
|
@ -320,7 +316,7 @@ export default function* onboardingSagas() {
|
||||||
takeEvery(ReduxActionTypes.LISTEN_FOR_ADD_WIDGET, listenForWidgetAdditions),
|
takeEvery(ReduxActionTypes.LISTEN_FOR_ADD_WIDGET, listenForWidgetAdditions),
|
||||||
takeEvery(
|
takeEvery(
|
||||||
ReduxActionTypes.LISTEN_FOR_TABLE_WIDGET_BINDING,
|
ReduxActionTypes.LISTEN_FOR_TABLE_WIDGET_BINDING,
|
||||||
listenForSuccessfullBinding,
|
listenForSuccessfulBinding,
|
||||||
),
|
),
|
||||||
takeEvery(ReduxActionTypes.SET_CURRENT_STEP, setupOnboardingStep),
|
takeEvery(ReduxActionTypes.SET_CURRENT_STEP, setupOnboardingStep),
|
||||||
takeEvery(ReduxActionTypes.LISTEN_FOR_DEPLOY, listenForDeploySaga),
|
takeEvery(ReduxActionTypes.LISTEN_FOR_DEPLOY, listenForDeploySaga),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
fetchPublishedPageSuccess,
|
fetchPublishedPageSuccess,
|
||||||
savePageSuccess,
|
savePageSuccess,
|
||||||
setUrlData,
|
setUrlData,
|
||||||
updateCanvas,
|
initCanvasLayout,
|
||||||
updateCurrentPage,
|
updateCurrentPage,
|
||||||
updateWidgetNameSuccess,
|
updateWidgetNameSuccess,
|
||||||
} from "actions/pageActions";
|
} from "actions/pageActions";
|
||||||
|
|
@ -179,7 +179,7 @@ export function* fetchPageSaga(
|
||||||
// Get Canvas payload
|
// Get Canvas payload
|
||||||
const canvasWidgetsPayload = getCanvasWidgetsPayload(fetchPageResponse);
|
const canvasWidgetsPayload = getCanvasWidgetsPayload(fetchPageResponse);
|
||||||
// Update the canvas
|
// Update the canvas
|
||||||
yield put(updateCanvas(canvasWidgetsPayload));
|
yield put(initCanvasLayout(canvasWidgetsPayload));
|
||||||
// set current page
|
// set current page
|
||||||
yield put(updateCurrentPage(id));
|
yield put(updateCurrentPage(id));
|
||||||
// dispatch fetch page success
|
// dispatch fetch page success
|
||||||
|
|
@ -242,7 +242,7 @@ export function* fetchPublishedPageSaga(
|
||||||
// Get Canvas payload
|
// Get Canvas payload
|
||||||
const canvasWidgetsPayload = getCanvasWidgetsPayload(response);
|
const canvasWidgetsPayload = getCanvasWidgetsPayload(response);
|
||||||
// Update the canvas
|
// Update the canvas
|
||||||
yield put(updateCanvas(canvasWidgetsPayload));
|
yield put(initCanvasLayout(canvasWidgetsPayload));
|
||||||
// set current page
|
// set current page
|
||||||
yield put(updateCurrentPage(pageId));
|
yield put(updateCurrentPage(pageId));
|
||||||
// dispatch fetch page success
|
// dispatch fetch page success
|
||||||
|
|
@ -613,7 +613,7 @@ export function* updateCanvasWithDSL(
|
||||||
pageActions: data.layoutOnLoadActions,
|
pageActions: data.layoutOnLoadActions,
|
||||||
widgets: normalizedWidgets.entities.canvasWidgets,
|
widgets: normalizedWidgets.entities.canvasWidgets,
|
||||||
};
|
};
|
||||||
yield put(updateCanvas(canvasWidgetsPayload));
|
yield put(initCanvasLayout(canvasWidgetsPayload));
|
||||||
yield put(fetchActionsForPage(pageId));
|
yield put(fetchActionsForPage(pageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,44 @@
|
||||||
import {
|
import {
|
||||||
ReduxActionTypes,
|
|
||||||
ReduxActionErrorTypes,
|
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
|
ReduxActionErrorTypes,
|
||||||
|
ReduxActionTypes,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import {
|
import {
|
||||||
WidgetAddChild,
|
|
||||||
WidgetResize,
|
|
||||||
WidgetMove,
|
|
||||||
WidgetDelete,
|
|
||||||
updateAndSaveLayout,
|
updateAndSaveLayout,
|
||||||
|
WidgetAddChild,
|
||||||
WidgetAddChildren,
|
WidgetAddChildren,
|
||||||
|
WidgetDelete,
|
||||||
|
WidgetMove,
|
||||||
|
WidgetResize,
|
||||||
} from "actions/pageActions";
|
} from "actions/pageActions";
|
||||||
import {
|
import {
|
||||||
FlattenedWidgetProps,
|
|
||||||
CanvasWidgetsReduxState,
|
CanvasWidgetsReduxState,
|
||||||
|
FlattenedWidgetProps,
|
||||||
} from "reducers/entityReducers/canvasWidgetsReducer";
|
} from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import {
|
import {
|
||||||
getWidgets,
|
|
||||||
getWidget,
|
|
||||||
getSelectedWidget,
|
getSelectedWidget,
|
||||||
|
getWidget,
|
||||||
getWidgetMetaProps,
|
getWidgetMetaProps,
|
||||||
|
getWidgets,
|
||||||
} from "./selectors";
|
} from "./selectors";
|
||||||
import {
|
import {
|
||||||
generateWidgetProps,
|
generateWidgetProps,
|
||||||
updateWidgetPosition,
|
updateWidgetPosition,
|
||||||
} from "utils/WidgetPropsUtils";
|
} from "utils/WidgetPropsUtils";
|
||||||
import {
|
import {
|
||||||
|
all,
|
||||||
call,
|
call,
|
||||||
put,
|
put,
|
||||||
select,
|
select,
|
||||||
takeEvery,
|
takeEvery,
|
||||||
takeLatest,
|
takeLatest,
|
||||||
all,
|
|
||||||
} from "redux-saga/effects";
|
} from "redux-saga/effects";
|
||||||
import { convertToString, getNextEntityName } from "utils/AppsmithUtils";
|
import { convertToString, getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import {
|
import {
|
||||||
|
DeleteWidgetPropertyPayload,
|
||||||
SetWidgetDynamicPropertyPayload,
|
SetWidgetDynamicPropertyPayload,
|
||||||
updateWidgetProperty,
|
updateWidgetProperty,
|
||||||
|
UpdateWidgetPropertyPayload,
|
||||||
UpdateWidgetPropertyRequestPayload,
|
UpdateWidgetPropertyRequestPayload,
|
||||||
} from "actions/controlActions";
|
} from "actions/controlActions";
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,12 +46,13 @@ import {
|
||||||
getEntityDynamicBindingPathList,
|
getEntityDynamicBindingPathList,
|
||||||
getWidgetDynamicPropertyPathList,
|
getWidgetDynamicPropertyPathList,
|
||||||
getWidgetDynamicTriggerPathList,
|
getWidgetDynamicTriggerPathList,
|
||||||
|
isChildPropertyPath,
|
||||||
isDynamicValue,
|
isDynamicValue,
|
||||||
isPathADynamicBinding,
|
isPathADynamicBinding,
|
||||||
isPathADynamicTrigger,
|
isPathADynamicTrigger,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import { WidgetProps } from "widgets/BaseWidget";
|
import { WidgetProps } from "widgets/BaseWidget";
|
||||||
import _ from "lodash";
|
import _, { cloneDeep } from "lodash";
|
||||||
import WidgetFactory from "utils/WidgetFactory";
|
import WidgetFactory from "utils/WidgetFactory";
|
||||||
import {
|
import {
|
||||||
buildWidgetBlueprint,
|
buildWidgetBlueprint,
|
||||||
|
|
@ -58,24 +61,23 @@ import {
|
||||||
import { resetWidgetMetaProperty } from "actions/metaActions";
|
import { resetWidgetMetaProperty } from "actions/metaActions";
|
||||||
import {
|
import {
|
||||||
GridDefaults,
|
GridDefaults,
|
||||||
WidgetTypes,
|
|
||||||
MAIN_CONTAINER_WIDGET_ID,
|
MAIN_CONTAINER_WIDGET_ID,
|
||||||
WIDGET_DELETE_UNDO_TIMEOUT,
|
|
||||||
RenderModes,
|
RenderModes,
|
||||||
|
WIDGET_DELETE_UNDO_TIMEOUT,
|
||||||
WidgetType,
|
WidgetType,
|
||||||
|
WidgetTypes,
|
||||||
} from "constants/WidgetConstants";
|
} from "constants/WidgetConstants";
|
||||||
import WidgetConfigResponse from "mockResponses/WidgetConfigResponse";
|
import WidgetConfigResponse from "mockResponses/WidgetConfigResponse";
|
||||||
import {
|
import {
|
||||||
|
flushDeletedWidgets,
|
||||||
|
getCopiedWidgets,
|
||||||
|
getDeletedWidgets,
|
||||||
saveCopiedWidgets,
|
saveCopiedWidgets,
|
||||||
saveDeletedWidgets,
|
saveDeletedWidgets,
|
||||||
flushDeletedWidgets,
|
|
||||||
getDeletedWidgets,
|
|
||||||
getCopiedWidgets,
|
|
||||||
} from "utils/storage";
|
} from "utils/storage";
|
||||||
import { generateReactKey } from "utils/generators";
|
import { generateReactKey } from "utils/generators";
|
||||||
import { flashElementById } from "utils/helpers";
|
import { flashElementById } from "utils/helpers";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { cloneDeep } from "lodash";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/WidgetEntity";
|
import { navigateToCanvas } from "pages/Editor/Explorer/Widgets/WidgetEntity";
|
||||||
import {
|
import {
|
||||||
|
|
@ -86,8 +88,8 @@ import { forceOpenPropertyPane } from "actions/widgetActions";
|
||||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||||
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||||
import {
|
import {
|
||||||
validateProperty,
|
|
||||||
clearEvalPropertyCacheOfWidget,
|
clearEvalPropertyCacheOfWidget,
|
||||||
|
validateProperty,
|
||||||
} from "./EvaluationsSaga";
|
} from "./EvaluationsSaga";
|
||||||
import { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer";
|
import { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer";
|
||||||
import { Toaster } from "components/ads/Toast";
|
import { Toaster } from "components/ads/Toast";
|
||||||
|
|
@ -221,7 +223,7 @@ function* generateChildWidgets(
|
||||||
// Add the parentId prop to this widget
|
// Add the parentId prop to this widget
|
||||||
widget.parentId = parent.widgetId;
|
widget.parentId = parent.widgetId;
|
||||||
// Remove the blueprint from the widget (if any)
|
// Remove the blueprint from the widget (if any)
|
||||||
// as blueprints are not useful beyont this point.
|
// as blueprints are not useful beyond this point.
|
||||||
delete widget.blueprint;
|
delete widget.blueprint;
|
||||||
return { widgetId: widget.widgetId, widgets };
|
return { widgetId: widget.widgetId, widgets };
|
||||||
}
|
}
|
||||||
|
|
@ -461,21 +463,21 @@ export function* undoDeleteSaga(action: ReduxAction<{ widgetId: string }>) {
|
||||||
const deletedWidgets: FlattenedWidgetProps[] = yield getDeletedWidgets(
|
const deletedWidgets: FlattenedWidgetProps[] = yield getDeletedWidgets(
|
||||||
action.payload.widgetId,
|
action.payload.widgetId,
|
||||||
);
|
);
|
||||||
// Find the parent in the list of deleted widgets
|
if (deletedWidgets && Array.isArray(deletedWidgets)) {
|
||||||
const deletedWidget = deletedWidgets.find(
|
// Find the parent in the list of deleted widgets
|
||||||
(widget) => widget.widgetId === action.payload.widgetId,
|
const deletedWidget = deletedWidgets.find(
|
||||||
);
|
(widget) => widget.widgetId === action.payload.widgetId,
|
||||||
|
);
|
||||||
|
|
||||||
// If the deleted widget is infact available.
|
// If the deleted widget is in fact available.
|
||||||
if (deletedWidget) {
|
if (deletedWidget) {
|
||||||
// Log an undo event
|
// Log an undo event
|
||||||
AnalyticsUtil.logEvent("WIDGET_DELETE_UNDO", {
|
AnalyticsUtil.logEvent("WIDGET_DELETE_UNDO", {
|
||||||
widgetName: deletedWidget.widgetName,
|
widgetName: deletedWidget.widgetName,
|
||||||
widgetType: deletedWidget.type,
|
widgetType: deletedWidget.type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deletedWidgets) {
|
|
||||||
// Get the current list of widgets from reducer
|
// Get the current list of widgets from reducer
|
||||||
const stateWidgets = yield select(getWidgets);
|
const stateWidgets = yield select(getWidgets);
|
||||||
let widgets = { ...stateWidgets };
|
let widgets = { ...stateWidgets };
|
||||||
|
|
@ -522,7 +524,7 @@ export function* undoDeleteSaga(action: ReduxAction<{ widgetId: string }>) {
|
||||||
}
|
}
|
||||||
let newChildren = [widget.widgetId];
|
let newChildren = [widget.widgetId];
|
||||||
if (widgets[widget.parentId].children) {
|
if (widgets[widget.parentId].children) {
|
||||||
// Concatenate the list of paren't children with the current widgetId
|
// Concatenate the list of parents children with the current widgetId
|
||||||
newChildren = newChildren.concat(widgets[widget.parentId].children);
|
newChildren = newChildren.concat(widgets[widget.parentId].children);
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|
@ -636,79 +638,86 @@ export function* resizeSaga(resizeAction: ReduxAction<WidgetResize>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateDynamicTriggers(
|
enum DynamicPathUpdateEffectEnum {
|
||||||
|
ADD = "ADD",
|
||||||
|
REMOVE = "REMOVE",
|
||||||
|
NOOP = "NOOP",
|
||||||
|
}
|
||||||
|
|
||||||
|
type DynamicPathUpdate = {
|
||||||
|
propertyPath: string;
|
||||||
|
effect: DynamicPathUpdateEffectEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDynamicTriggerPathListUpdate(
|
||||||
widget: WidgetProps,
|
widget: WidgetProps,
|
||||||
propertyPath: string,
|
propertyPath: string,
|
||||||
propertyValue: string,
|
propertyValue: string,
|
||||||
) {
|
): DynamicPathUpdate {
|
||||||
// TODO WIDGETFACTORY
|
if (propertyValue && !isPathADynamicTrigger(widget, propertyPath)) {
|
||||||
const triggerProperties = WidgetFactory.getWidgetTriggerPropertiesMap(
|
return {
|
||||||
widget.type,
|
propertyPath,
|
||||||
);
|
effect: DynamicPathUpdateEffectEnum.ADD,
|
||||||
if (propertyPath in triggerProperties) {
|
};
|
||||||
let dynamicTriggerPathList: DynamicPath[] = getWidgetDynamicTriggerPathList(
|
} else if (!propertyValue && !isPathADynamicTrigger(widget, propertyPath)) {
|
||||||
widget,
|
return {
|
||||||
);
|
propertyPath,
|
||||||
if (propertyValue && !isPathADynamicTrigger(widget, propertyPath)) {
|
effect: DynamicPathUpdateEffectEnum.REMOVE,
|
||||||
dynamicTriggerPathList.push({
|
};
|
||||||
key: propertyPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!propertyValue && !isPathADynamicTrigger(widget, propertyPath)) {
|
|
||||||
dynamicTriggerPathList = _.reject(dynamicTriggerPathList, {
|
|
||||||
key: propertyValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
yield put(
|
|
||||||
updateWidgetProperty(
|
|
||||||
widget.widgetId,
|
|
||||||
"dynamicTriggerPathList",
|
|
||||||
dynamicTriggerPathList,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return {
|
||||||
|
propertyPath,
|
||||||
|
effect: DynamicPathUpdateEffectEnum.NOOP,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateDynamicBindings(
|
function getDynamicBindingPathListUpdate(
|
||||||
widget: WidgetProps,
|
widget: WidgetProps,
|
||||||
propertyName: string,
|
propertyPath: string,
|
||||||
propertyValue: any,
|
propertyValue: any,
|
||||||
) {
|
): DynamicPathUpdate {
|
||||||
let stringProp = propertyValue;
|
let stringProp = propertyValue;
|
||||||
if (_.isObject(propertyValue)) {
|
if (_.isObject(propertyValue)) {
|
||||||
// Stringify this because composite controls may have bindings in the sub controls
|
// Stringify this because composite controls may have bindings in the sub controls
|
||||||
stringProp = JSON.stringify(propertyValue);
|
stringProp = JSON.stringify(propertyValue);
|
||||||
}
|
}
|
||||||
const isDynamic = isDynamicValue(stringProp);
|
const isDynamic = isDynamicValue(stringProp);
|
||||||
let dynamicBindingPathList: DynamicPath[] = getEntityDynamicBindingPathList(
|
if (!isDynamic && isPathADynamicBinding(widget, propertyPath)) {
|
||||||
widget,
|
return {
|
||||||
);
|
propertyPath,
|
||||||
if (!isDynamic && isPathADynamicBinding(widget, propertyName)) {
|
effect: DynamicPathUpdateEffectEnum.REMOVE,
|
||||||
dynamicBindingPathList = _.reject(dynamicBindingPathList, {
|
};
|
||||||
key: propertyName,
|
} else if (isDynamic && !isPathADynamicBinding(widget, propertyPath)) {
|
||||||
});
|
return {
|
||||||
|
propertyPath,
|
||||||
|
effect: DynamicPathUpdateEffectEnum.ADD,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (isDynamic && !isPathADynamicBinding(widget, propertyName)) {
|
return {
|
||||||
dynamicBindingPathList.push({
|
propertyPath,
|
||||||
key: propertyName,
|
effect: DynamicPathUpdateEffectEnum.NOOP,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDynamicPathUpdates(
|
||||||
|
currentList: DynamicPath[],
|
||||||
|
update: DynamicPathUpdate,
|
||||||
|
): DynamicPath[] {
|
||||||
|
if (update.effect === DynamicPathUpdateEffectEnum.ADD) {
|
||||||
|
currentList.push({
|
||||||
|
key: update.propertyPath,
|
||||||
});
|
});
|
||||||
|
} else if (update.effect === DynamicPathUpdateEffectEnum.REMOVE) {
|
||||||
|
_.reject(currentList, { key: update.propertyPath });
|
||||||
}
|
}
|
||||||
yield put(
|
return currentList;
|
||||||
updateWidgetProperty(
|
|
||||||
widget.widgetId,
|
|
||||||
"dynamicBindingPathList",
|
|
||||||
dynamicBindingPathList,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateWidgetPropertySaga(
|
function* updateWidgetPropertySaga(
|
||||||
updateAction: ReduxAction<UpdateWidgetPropertyRequestPayload>,
|
updateAction: ReduxAction<UpdateWidgetPropertyRequestPayload>,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
payload: { propertyValue, propertyName, widgetId },
|
payload: { propertyValue, propertyPath, widgetId },
|
||||||
} = updateAction;
|
} = updateAction;
|
||||||
if (!widgetId) {
|
if (!widgetId) {
|
||||||
// Handling the case where sometimes widget id is not passed through here
|
// Handling the case where sometimes widget id is not passed through here
|
||||||
|
|
@ -717,55 +726,190 @@ function* updateWidgetPropertySaga(
|
||||||
const stateWidget: WidgetProps = yield select(getWidget, widgetId);
|
const stateWidget: WidgetProps = yield select(getWidget, widgetId);
|
||||||
const widget = { ...stateWidget };
|
const widget = { ...stateWidget };
|
||||||
|
|
||||||
const dynamicTriggersUpdated = yield updateDynamicTriggers(
|
// Holder object to collect all updates
|
||||||
widget,
|
const updates: Record<string, unknown> = {
|
||||||
propertyName,
|
[propertyPath]: propertyValue,
|
||||||
propertyValue,
|
};
|
||||||
|
|
||||||
|
// Check if the path is a of a dynamic trigger property
|
||||||
|
const triggerProperties = WidgetFactory.getWidgetTriggerPropertiesMap(
|
||||||
|
widget.type,
|
||||||
);
|
);
|
||||||
if (!dynamicTriggersUpdated) {
|
const isTriggerProperty = propertyPath in triggerProperties;
|
||||||
yield updateDynamicBindings(widget, propertyName, propertyValue);
|
// If it is a trigger property, it will go in a different list than the general
|
||||||
|
// dynamicBindingPathList.
|
||||||
|
if (isTriggerProperty) {
|
||||||
|
const currentDynamicTriggerPathList: DynamicPath[] = getWidgetDynamicTriggerPathList(
|
||||||
|
widget,
|
||||||
|
);
|
||||||
|
const effect = getDynamicTriggerPathListUpdate(
|
||||||
|
widget,
|
||||||
|
propertyPath,
|
||||||
|
propertyValue,
|
||||||
|
);
|
||||||
|
updates.dynamicTriggerPathList = applyDynamicPathUpdates(
|
||||||
|
currentDynamicTriggerPathList,
|
||||||
|
effect,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const currentDynamicBindingPathList: DynamicPath[] = getEntityDynamicBindingPathList(
|
||||||
|
widget,
|
||||||
|
);
|
||||||
|
const effect = getDynamicBindingPathListUpdate(
|
||||||
|
widget,
|
||||||
|
propertyPath,
|
||||||
|
propertyValue,
|
||||||
|
);
|
||||||
|
updates.dynamicBindingPathList = applyDynamicPathUpdates(
|
||||||
|
currentDynamicBindingPathList,
|
||||||
|
effect,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(updateWidgetProperty(widgetId, propertyName, propertyValue));
|
// Send the updates
|
||||||
|
yield put(updateWidgetProperty(widgetId, updates));
|
||||||
|
|
||||||
const stateWidgets = yield select(getWidgets);
|
const stateWidgets = yield select(getWidgets);
|
||||||
const widgets = { ...stateWidgets, [widgetId]: widget };
|
const widgets = { ...stateWidgets, [widgetId]: widget };
|
||||||
|
|
||||||
|
// Save the layout
|
||||||
yield put(updateAndSaveLayout(widgets));
|
yield put(updateAndSaveLayout(widgets));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* setWidgetDynamicPropertySaga(
|
function* setWidgetDynamicPropertySaga(
|
||||||
action: ReduxAction<SetWidgetDynamicPropertyPayload>,
|
action: ReduxAction<SetWidgetDynamicPropertyPayload>,
|
||||||
) {
|
) {
|
||||||
const { isDynamic, propertyName, widgetId } = action.payload;
|
const { isDynamic, propertyPath, widgetId } = action.payload;
|
||||||
const widget: WidgetProps = yield select(getWidget, widgetId);
|
const widget: WidgetProps = yield select(getWidget, widgetId);
|
||||||
// const tree = yield select(evaluateDataTree);
|
const propertyValue = _.get(widget, propertyPath);
|
||||||
const propertyValue = _.get(widget, propertyName);
|
|
||||||
let dynamicPropertyPathList = getWidgetDynamicPropertyPathList(widget);
|
let dynamicPropertyPathList = getWidgetDynamicPropertyPathList(widget);
|
||||||
|
const propertyUpdates: Record<string, unknown> = {};
|
||||||
if (isDynamic) {
|
if (isDynamic) {
|
||||||
dynamicPropertyPathList.push({
|
dynamicPropertyPathList.push({
|
||||||
key: propertyName,
|
key: propertyPath,
|
||||||
});
|
});
|
||||||
const value = convertToString(propertyValue);
|
propertyUpdates[propertyPath] = convertToString(propertyValue);
|
||||||
yield put(updateWidgetProperty(widgetId, propertyName, value));
|
|
||||||
} else {
|
} else {
|
||||||
dynamicPropertyPathList = _.reject(dynamicPropertyPathList, {
|
dynamicPropertyPathList = _.reject(dynamicPropertyPathList, {
|
||||||
key: propertyName,
|
key: propertyPath,
|
||||||
});
|
});
|
||||||
const { parsed } = yield call(
|
const { parsed } = yield call(
|
||||||
validateProperty,
|
validateProperty,
|
||||||
widget.type,
|
widget.type,
|
||||||
propertyName,
|
propertyPath,
|
||||||
propertyValue,
|
propertyValue,
|
||||||
widget,
|
widget,
|
||||||
);
|
);
|
||||||
yield put(updateWidgetProperty(widgetId, propertyName, parsed));
|
propertyUpdates[propertyPath] = parsed;
|
||||||
}
|
}
|
||||||
yield put(
|
propertyUpdates.dynamicPropertyPathList = dynamicPropertyPathList;
|
||||||
updateWidgetProperty(
|
|
||||||
widgetId,
|
yield put(updateWidgetProperty(widgetId, propertyUpdates));
|
||||||
"dynamicPropertyPathList",
|
|
||||||
dynamicPropertyPathList,
|
const stateWidgets = yield select(getWidgets);
|
||||||
),
|
const widgets = { ...stateWidgets, [widgetId]: widget };
|
||||||
|
|
||||||
|
// Save the layout
|
||||||
|
yield put(updateAndSaveLayout(widgets));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* batchUpdateWidgetPropertySaga(
|
||||||
|
action: ReduxAction<UpdateWidgetPropertyPayload>,
|
||||||
|
) {
|
||||||
|
const { updates, widgetId } = action.payload;
|
||||||
|
if (!widgetId) {
|
||||||
|
// Handling the case where sometimes widget id is not passed through here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const widget: WidgetProps = yield select(getWidget, widgetId);
|
||||||
|
const triggerProperties = WidgetFactory.getWidgetTriggerPropertiesMap(
|
||||||
|
widget.type,
|
||||||
);
|
);
|
||||||
|
const propertyUpdates: Record<string, unknown> = {};
|
||||||
|
const currentDynamicTriggerPathList: DynamicPath[] = getWidgetDynamicTriggerPathList(
|
||||||
|
widget,
|
||||||
|
);
|
||||||
|
const currentDynamicBindingPathList: DynamicPath[] = getEntityDynamicBindingPathList(
|
||||||
|
widget,
|
||||||
|
);
|
||||||
|
const dynamicTriggerPathListUpdates: DynamicPathUpdate[] = [];
|
||||||
|
const dynamicBindingPathListUpdates: DynamicPathUpdate[] = [];
|
||||||
|
Object.entries(updates).forEach(([propertyPath, propertyValue]) => {
|
||||||
|
// Set the actual property update
|
||||||
|
propertyUpdates[propertyPath] = propertyValue;
|
||||||
|
|
||||||
|
// Check if the path is a of a dynamic trigger property
|
||||||
|
const isTriggerProperty = propertyPath in triggerProperties;
|
||||||
|
// If it is a trigger property, it will go in a different list than the general
|
||||||
|
// dynamicBindingPathList.
|
||||||
|
if (isTriggerProperty && _.isString(propertyValue)) {
|
||||||
|
dynamicTriggerPathListUpdates.push(
|
||||||
|
getDynamicTriggerPathListUpdate(widget, propertyPath, propertyValue),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dynamicBindingPathListUpdates.push(
|
||||||
|
getDynamicBindingPathListUpdate(widget, propertyPath, propertyValue),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
propertyUpdates.dynamicTriggerPathList = dynamicTriggerPathListUpdates.reduce(
|
||||||
|
applyDynamicPathUpdates,
|
||||||
|
currentDynamicTriggerPathList,
|
||||||
|
);
|
||||||
|
propertyUpdates.dynamicBindingPathList = dynamicBindingPathListUpdates.reduce(
|
||||||
|
applyDynamicPathUpdates,
|
||||||
|
currentDynamicBindingPathList,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send the updates
|
||||||
|
yield put(updateWidgetProperty(widgetId, updates));
|
||||||
|
|
||||||
|
const stateWidgets = yield select(getWidgets);
|
||||||
|
const widgets = { ...stateWidgets, [widgetId]: widget };
|
||||||
|
|
||||||
|
// Save the layout
|
||||||
|
yield put(updateAndSaveLayout(widgets));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* deleteWidgetPropertySaga(
|
||||||
|
action: ReduxAction<DeleteWidgetPropertyPayload>,
|
||||||
|
) {
|
||||||
|
const { widgetId, propertyPath } = action.payload;
|
||||||
|
if (!widgetId) {
|
||||||
|
// Handling the case where sometimes widget id is not passed through here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stateWidget: WidgetProps = yield select(getWidget, widgetId);
|
||||||
|
const dynamicTriggerPathList: DynamicPath[] = getWidgetDynamicTriggerPathList(
|
||||||
|
stateWidget,
|
||||||
|
);
|
||||||
|
const dynamicBindingPathList: DynamicPath[] = getEntityDynamicBindingPathList(
|
||||||
|
stateWidget,
|
||||||
|
);
|
||||||
|
|
||||||
|
dynamicTriggerPathList.filter((dynamicPath) => {
|
||||||
|
return !isChildPropertyPath(propertyPath, dynamicPath.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamicBindingPathList.forEach((dynamicPath) => {
|
||||||
|
return !isChildPropertyPath(propertyPath, dynamicPath.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
yield put(
|
||||||
|
updateWidgetProperty(widgetId, {
|
||||||
|
dynamicTriggerPathList,
|
||||||
|
dynamicBindingPathList,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const stateWidgets = yield select(getWidgets);
|
||||||
|
const widget = { ...stateWidget };
|
||||||
|
_.unset(widget, propertyPath);
|
||||||
|
const widgets = { ...stateWidgets, [widgetId]: widget };
|
||||||
|
|
||||||
|
// Save the layout
|
||||||
|
yield put(updateAndSaveLayout(widgets));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* getWidgetChildren(widgetId: string): any {
|
function* getWidgetChildren(widgetId: string): any {
|
||||||
|
|
@ -774,11 +918,13 @@ function* getWidgetChildren(widgetId: string): any {
|
||||||
const { children } = widget;
|
const { children } = widget;
|
||||||
if (children && children.length) {
|
if (children && children.length) {
|
||||||
for (const childIndex in children) {
|
for (const childIndex in children) {
|
||||||
const child = children[childIndex];
|
if (children.hasOwnProperty(childIndex)) {
|
||||||
childrenIds.push(child);
|
const child = children[childIndex];
|
||||||
const grandChildren = yield call(getWidgetChildren, child);
|
childrenIds.push(child);
|
||||||
if (grandChildren.length) {
|
const grandChildren = yield call(getWidgetChildren, child);
|
||||||
childrenIds.push(...grandChildren);
|
if (grandChildren.length) {
|
||||||
|
childrenIds.push(...grandChildren);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -803,6 +949,10 @@ function* resetEvaluatedWidgetMetaProperties(widgetIds: string[]) {
|
||||||
for (const index in widgetIds) {
|
for (const index in widgetIds) {
|
||||||
const widgetId = widgetIds[index];
|
const widgetId = widgetIds[index];
|
||||||
const widget = _.find(evaluatedDataTree, { widgetId }) as DataTreeWidget;
|
const widget = _.find(evaluatedDataTree, { widgetId }) as DataTreeWidget;
|
||||||
|
|
||||||
|
// the widget was not found in the data tree, so don't do anything
|
||||||
|
if (!widget) continue;
|
||||||
|
|
||||||
const widgetToUpdate = { ...widget };
|
const widgetToUpdate = { ...widget };
|
||||||
const metaPropsMap = WidgetFactory.getWidgetMetaPropertiesMap(widget.type);
|
const metaPropsMap = WidgetFactory.getWidgetMetaPropertiesMap(widget.type);
|
||||||
const defaultPropertiesMap = WidgetFactory.getWidgetDefaultPropertiesMap(
|
const defaultPropertiesMap = WidgetFactory.getWidgetDefaultPropertiesMap(
|
||||||
|
|
@ -843,7 +993,9 @@ function* updateCanvasSize(
|
||||||
// TODO(abhinav): This considers that the topRow will always be zero
|
// TODO(abhinav): This considers that the topRow will always be zero
|
||||||
// Check this out when non canvas widgets are updating snapRows
|
// Check this out when non canvas widgets are updating snapRows
|
||||||
// erstwhile: Math.round((rows * props.snapRowSpace) / props.parentRowSpace),
|
// erstwhile: Math.round((rows * props.snapRowSpace) / props.parentRowSpace),
|
||||||
yield put(updateWidgetProperty(canvasWidgetId, "bottomRow", newBottomRow));
|
yield put(
|
||||||
|
updateWidgetProperty(canvasWidgetId, { bottomRow: newBottomRow }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -852,11 +1004,9 @@ function* createWidgetCopy() {
|
||||||
if (!selectedWidget) return;
|
if (!selectedWidget) return;
|
||||||
const widgets = yield select(getWidgets);
|
const widgets = yield select(getWidgets);
|
||||||
const widgetsToStore = getAllWidgetsInTree(selectedWidget.widgetId, widgets);
|
const widgetsToStore = getAllWidgetsInTree(selectedWidget.widgetId, widgets);
|
||||||
const saveResult = yield saveCopiedWidgets(
|
return yield saveCopiedWidgets(
|
||||||
JSON.stringify({ widgetId: selectedWidget.widgetId, list: widgetsToStore }),
|
JSON.stringify({ widgetId: selectedWidget.widgetId, list: widgetsToStore }),
|
||||||
);
|
);
|
||||||
|
|
||||||
return saveResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) {
|
function* copyWidgetSaga(action: ReduxAction<{ isShortcut: boolean }>) {
|
||||||
|
|
@ -1232,7 +1382,7 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
|
||||||
// The following is computed to be used in the entity explorer
|
// The following is computed to be used in the entity explorer
|
||||||
// Every time a widget is selected, we need to expand widget entities
|
// Every time a widget is selected, we need to expand widget entities
|
||||||
// in the entity explorer so that the selected widget is visible
|
// in the entity explorer so that the selected widget is visible
|
||||||
function* selectedWidgetAncestorySaga(
|
function* selectedWidgetAncestrySaga(
|
||||||
action: ReduxAction<{ widgetId: string }>,
|
action: ReduxAction<{ widgetId: string }>,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1259,7 +1409,7 @@ function* selectedWidgetAncestorySaga(
|
||||||
payload: widgetIdsExpandList,
|
payload: widgetIdsExpandList,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.debug("Could not compute selected widget's ancestory", error);
|
log.debug("Could not compute selected widget's ancestry", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1285,12 +1435,20 @@ export default function* widgetOperationSagas() {
|
||||||
ReduxActionTypes.RESET_CHILDREN_WIDGET_META,
|
ReduxActionTypes.RESET_CHILDREN_WIDGET_META,
|
||||||
resetChildrenMetaSaga,
|
resetChildrenMetaSaga,
|
||||||
),
|
),
|
||||||
|
takeEvery(
|
||||||
|
ReduxActionTypes.BATCH_UPDATE_WIDGET_PROPERTY,
|
||||||
|
batchUpdateWidgetPropertySaga,
|
||||||
|
),
|
||||||
|
takeEvery(
|
||||||
|
ReduxActionTypes.DELETE_WIDGET_PROPERTY,
|
||||||
|
deleteWidgetPropertySaga,
|
||||||
|
),
|
||||||
takeLatest(ReduxActionTypes.UPDATE_CANVAS_SIZE, updateCanvasSize),
|
takeLatest(ReduxActionTypes.UPDATE_CANVAS_SIZE, updateCanvasSize),
|
||||||
takeLatest(ReduxActionTypes.COPY_SELECTED_WIDGET_INIT, copyWidgetSaga),
|
takeLatest(ReduxActionTypes.COPY_SELECTED_WIDGET_INIT, copyWidgetSaga),
|
||||||
takeEvery(ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, pasteWidgetSaga),
|
takeEvery(ReduxActionTypes.PASTE_COPIED_WIDGET_INIT, pasteWidgetSaga),
|
||||||
takeEvery(ReduxActionTypes.UNDO_DELETE_WIDGET, undoDeleteSaga),
|
takeEvery(ReduxActionTypes.UNDO_DELETE_WIDGET, undoDeleteSaga),
|
||||||
takeEvery(ReduxActionTypes.CUT_SELECTED_WIDGET, cutWidgetSaga),
|
takeEvery(ReduxActionTypes.CUT_SELECTED_WIDGET, cutWidgetSaga),
|
||||||
takeEvery(ReduxActionTypes.WIDGET_ADD_CHILDREN, addChildrenSaga),
|
takeEvery(ReduxActionTypes.WIDGET_ADD_CHILDREN, addChildrenSaga),
|
||||||
takeLatest(ReduxActionTypes.SELECT_WIDGET, selectedWidgetAncestorySaga),
|
takeLatest(ReduxActionTypes.SELECT_WIDGET, selectedWidgetAncestrySaga),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,33 @@
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
|
import {
|
||||||
|
AUTH_LOGIN_URL,
|
||||||
|
SIGN_UP_URL,
|
||||||
|
RESET_PASSWORD_URL,
|
||||||
|
FORGOT_PASSWORD_URL,
|
||||||
|
} from "constants/routes";
|
||||||
|
import { theme, dark } from "constants/DefaultTheme";
|
||||||
|
|
||||||
|
const enforceDarkThemeRoutes = [
|
||||||
|
AUTH_LOGIN_URL,
|
||||||
|
SIGN_UP_URL,
|
||||||
|
RESET_PASSWORD_URL,
|
||||||
|
FORGOT_PASSWORD_URL,
|
||||||
|
];
|
||||||
|
const getShouldEnforceDarkTheme = () => {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
return enforceDarkThemeRoutes.some(
|
||||||
|
(path: string) => currentPath.indexOf(path) !== -1,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getThemeDetails = (state: AppState) => {
|
export const getThemeDetails = (state: AppState) => {
|
||||||
|
if (getShouldEnforceDarkTheme()) {
|
||||||
|
return {
|
||||||
|
mode: state.ui.theme.mode,
|
||||||
|
theme: { ...theme, colors: { ...theme.colors, ...dark } },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: state.ui.theme.theme,
|
theme: state.ui.theme.theme,
|
||||||
mode: state.ui.theme.mode,
|
mode: state.ui.theme.mode,
|
||||||
|
|
|
||||||
|
|
@ -249,3 +249,12 @@ export const unsafeFunctionForEval = [
|
||||||
"setInterval",
|
"setInterval",
|
||||||
"Promise",
|
"Promise",
|
||||||
];
|
];
|
||||||
|
export const isChildPropertyPath = (
|
||||||
|
parentPropertyPath: string,
|
||||||
|
childPropertyPath: string,
|
||||||
|
): boolean => {
|
||||||
|
const regexTest = new RegExp(
|
||||||
|
`^${parentPropertyPath.replace(".", "\\.")}(\\.\\S+)?$`,
|
||||||
|
);
|
||||||
|
return regexTest.test(childPropertyPath);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { getDynamicStringSegments } from "./DynamicBindingUtils";
|
import {
|
||||||
|
getDynamicStringSegments,
|
||||||
|
isChildPropertyPath,
|
||||||
|
} from "./DynamicBindingUtils";
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
["{{A}}", ["{{A}}"]],
|
["{{A}}", ["{{A}}"]],
|
||||||
|
|
@ -30,3 +33,20 @@ describe.each([
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isChildPropertyPath function", () => {
|
||||||
|
it("works", () => {
|
||||||
|
const cases: Array<[string, string, boolean]> = [
|
||||||
|
["Table1.selectedRow", "Table1.selectedRow", true],
|
||||||
|
["Table1.selectedRow", "Table1.selectedRows", false],
|
||||||
|
["Table1.selectedRow", "Table1.selectedRow.email", true],
|
||||||
|
["Table1.selectedRow", "1Table1.selectedRow", false],
|
||||||
|
["Table1.selectedRow", "Table11selectedRow", false],
|
||||||
|
["Table1.selectedRow", "Table1.selectedRow", true],
|
||||||
|
];
|
||||||
|
cases.forEach((testCase) => {
|
||||||
|
const result = isChildPropertyPath(testCase[0], testCase[1]);
|
||||||
|
expect(result).toBe(testCase[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ export const GLOBAL_DEFS = {
|
||||||
export const GLOBAL_FUNCTIONS = {
|
export const GLOBAL_FUNCTIONS = {
|
||||||
navigateTo: {
|
navigateTo: {
|
||||||
"!doc": "Action to navigate the user to another page or url",
|
"!doc": "Action to navigate the user to another page or url",
|
||||||
"!type": "fn(pageNameOrUrl: string, params: {}) -> void",
|
"!type": "fn(pageNameOrUrl: string, params: {}, target?: string) -> void",
|
||||||
},
|
},
|
||||||
showAlert: {
|
showAlert: {
|
||||||
"!doc": "Show a temporary notification style message to the user",
|
"!doc": "Show a temporary notification style message to the user",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
||||||
static getTriggerPropertyMap(): TriggerPropertiesMap {
|
static getTriggerPropertyMap(): TriggerPropertiesMap {
|
||||||
return {
|
return {
|
||||||
onTextChanged: true,
|
onTextChanged: true,
|
||||||
|
onSubmit: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +137,22 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
||||||
this.props.updateWidgetMetaProperty("isFocused", focusState);
|
this.props.updateWidgetMetaProperty("isFocused", focusState);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleKeyDown = (
|
||||||
|
e:
|
||||||
|
| React.KeyboardEvent<HTMLTextAreaElement>
|
||||||
|
| React.KeyboardEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
|
const isEnterKey = e.key === "Enter" || e.keyCode === 13;
|
||||||
|
if (isEnterKey && this.props.onSubmit) {
|
||||||
|
super.executeAction({
|
||||||
|
dynamicString: this.props.onSubmit,
|
||||||
|
event: {
|
||||||
|
type: EventType.ON_SUBMIT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getPageView() {
|
getPageView() {
|
||||||
const value = this.props.text || "";
|
const value = this.props.text || "";
|
||||||
const isInvalid =
|
const isInvalid =
|
||||||
|
|
@ -149,6 +166,7 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
||||||
if (this.props.maxChars) conditionalProps.maxChars = this.props.maxChars;
|
if (this.props.maxChars) conditionalProps.maxChars = this.props.maxChars;
|
||||||
if (this.props.maxNum) conditionalProps.maxNum = this.props.maxNum;
|
if (this.props.maxNum) conditionalProps.maxNum = this.props.maxNum;
|
||||||
if (this.props.minNum) conditionalProps.minNum = this.props.minNum;
|
if (this.props.minNum) conditionalProps.minNum = this.props.minNum;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputComponent
|
<InputComponent
|
||||||
value={value}
|
value={value}
|
||||||
|
|
@ -168,6 +186,8 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
|
||||||
stepSize={1}
|
stepSize={1}
|
||||||
onFocusChange={this.handleFocusChange}
|
onFocusChange={this.handleFocusChange}
|
||||||
showError={!!this.props.isFocused}
|
showError={!!this.props.isFocused}
|
||||||
|
disableNewLineOnPressEnterKey={!!this.props.onSubmit}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
{...conditionalProps}
|
{...conditionalProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -215,6 +235,7 @@ export interface InputWidgetProps extends WidgetProps, WithMeta {
|
||||||
isRequired?: boolean;
|
isRequired?: boolean;
|
||||||
isFocused?: boolean;
|
isFocused?: boolean;
|
||||||
isDirty?: boolean;
|
isDirty?: boolean;
|
||||||
|
onSubmit?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InputWidget;
|
export default InputWidget;
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,16 @@ class MapWidget extends BaseWidget<MapWidgetProps, WidgetState> {
|
||||||
return this.props.center || this.props.mapCenter || DefaultCenter;
|
return this.props.center || this.props.mapCenter || DefaultCenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: MapWidgetProps) {
|
||||||
|
//remove selectedMarker when map initial location is updated
|
||||||
|
if (
|
||||||
|
JSON.stringify(prevProps.center) !== JSON.stringify(this.props.center) &&
|
||||||
|
this.props.selectedMarker
|
||||||
|
) {
|
||||||
|
this.unselectMarker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getPageView() {
|
getPageView() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
searchText: VALIDATION_TYPES.TEXT,
|
searchText: VALIDATION_TYPES.TEXT,
|
||||||
defaultSearchText: VALIDATION_TYPES.TEXT,
|
defaultSearchText: VALIDATION_TYPES.TEXT,
|
||||||
defaultSelectedRow: VALIDATION_TYPES.DEFAULT_SELECTED_ROW,
|
defaultSelectedRow: VALIDATION_TYPES.DEFAULT_SELECTED_ROW,
|
||||||
|
pageSize: VALIDATION_TYPES.NUMBER,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getMetaPropertiesMap(): Record<string, any> {
|
static getMetaPropertiesMap(): Record<string, any> {
|
||||||
return {
|
return {
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: undefined,
|
|
||||||
selectedRowIndex: undefined,
|
selectedRowIndex: undefined,
|
||||||
selectedRowIndices: undefined,
|
selectedRowIndices: undefined,
|
||||||
searchText: undefined,
|
searchText: undefined,
|
||||||
|
|
@ -120,10 +120,54 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
onRowSelected: true,
|
onRowSelected: true,
|
||||||
onPageChange: true,
|
onPageChange: true,
|
||||||
onSearchTextChanged: true,
|
onSearchTextChanged: true,
|
||||||
|
onPageSizeChange: true,
|
||||||
columnActions: true,
|
columnActions: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedPropertiesMap() {
|
||||||
|
return {
|
||||||
|
pageSize: `{{function() {
|
||||||
|
const TABLE_SIZES = {
|
||||||
|
${CompactModeTypes.DEFAULT}: {
|
||||||
|
COLUMN_HEADER_HEIGHT: 38,
|
||||||
|
TABLE_HEADER_HEIGHT: 42,
|
||||||
|
ROW_HEIGHT: 40,
|
||||||
|
ROW_FONT_SIZE: 14,
|
||||||
|
},
|
||||||
|
${CompactModeTypes.SHORT}: {
|
||||||
|
COLUMN_HEADER_HEIGHT: 38,
|
||||||
|
TABLE_HEADER_HEIGHT: 42,
|
||||||
|
ROW_HEIGHT: 20,
|
||||||
|
ROW_FONT_SIZE: 12,
|
||||||
|
},
|
||||||
|
${CompactModeTypes.TALL}: {
|
||||||
|
COLUMN_HEADER_HEIGHT: 38,
|
||||||
|
TABLE_HEADER_HEIGHT: 42,
|
||||||
|
ROW_HEIGHT: 60,
|
||||||
|
ROW_FONT_SIZE: 18,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const compactMode = this.compactMode || "${CompactModeTypes.DEFAULT}";
|
||||||
|
const componentHeight = (this.bottomRow - this.topRow) * this.parentRowSpace;
|
||||||
|
const tableSizes = TABLE_SIZES[compactMode];
|
||||||
|
let pageSize= Math.floor((componentHeight - tableSizes.TABLE_HEADER_HEIGHT - tableSizes.COLUMN_HEADER_HEIGHT) / tableSizes.ROW_HEIGHT);
|
||||||
|
if (
|
||||||
|
componentHeight -
|
||||||
|
(tableSizes.TABLE_HEADER_HEIGHT +
|
||||||
|
tableSizes.COLUMN_HEADER_HEIGHT +
|
||||||
|
tableSizes.ROW_HEIGHT * pageSize) >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
pageSize += 1;
|
||||||
|
}
|
||||||
|
return pageSize;
|
||||||
|
}()
|
||||||
|
}}`,
|
||||||
|
triggerRowSelection: "{{!!this.onRowSelected}}",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getTableColumns = (tableData: Array<Record<string, unknown>>) => {
|
getTableColumns = (tableData: Array<Record<string, unknown>>) => {
|
||||||
let columns: ReactTableColumnProps[] = [];
|
let columns: ReactTableColumnProps[] = [];
|
||||||
const hiddenColumns: ReactTableColumnProps[] = [];
|
const hiddenColumns: ReactTableColumnProps[] = [];
|
||||||
|
|
@ -135,6 +179,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (tableData.length) {
|
if (tableData.length) {
|
||||||
const columnKeys: string[] = getAllTableColumnKeys(tableData);
|
const columnKeys: string[] = getAllTableColumnKeys(tableData);
|
||||||
|
const { componentWidth } = this.getComponentDimensions();
|
||||||
const sortedColumn = this.props.sortedColumn;
|
const sortedColumn = this.props.sortedColumn;
|
||||||
for (let index = 0; index < columnKeys.length; index++) {
|
for (let index = 0; index < columnKeys.length; index++) {
|
||||||
const i = columnKeys[index];
|
const i = columnKeys[index];
|
||||||
|
|
@ -170,7 +215,12 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
inputFormat: columnType.inputFormat,
|
inputFormat: columnType.inputFormat,
|
||||||
},
|
},
|
||||||
Cell: (props: any) => {
|
Cell: (props: any) => {
|
||||||
return renderCell(props.cell.value, columnType.type, isHidden);
|
return renderCell(
|
||||||
|
props.cell.value,
|
||||||
|
columnType.type,
|
||||||
|
isHidden,
|
||||||
|
componentWidth,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
|
|
@ -361,7 +411,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
const columnKeys: string[] = getAllTableColumnKeys(this.props.tableData);
|
const columnKeys: string[] = getAllTableColumnKeys(this.props.tableData);
|
||||||
const selectedRow: { [key: string]: any } = {};
|
const selectedRow: { [key: string]: any } = {};
|
||||||
for (let i = 0; i < columnKeys.length; i++) {
|
for (let i = 0; i < columnKeys.length; i++) {
|
||||||
selectedRow[columnKeys[i]] = undefined;
|
selectedRow[columnKeys[i]] = "";
|
||||||
}
|
}
|
||||||
return selectedRow;
|
return selectedRow;
|
||||||
}
|
}
|
||||||
|
|
@ -493,6 +543,14 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.props.pageSize !== prevProps.pageSize) {
|
||||||
|
super.executeAction({
|
||||||
|
dynamicString: this.props.onPageSizeChange,
|
||||||
|
event: {
|
||||||
|
type: EventType.ON_PAGE_SIZE_CHANGE,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedRowIndexes = (selectedRowIndices: string) => {
|
getSelectedRowIndexes = (selectedRowIndices: string) => {
|
||||||
|
|
@ -502,7 +560,12 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getPageView() {
|
getPageView() {
|
||||||
const { tableData, hiddenColumns, filteredTableData } = this.props;
|
const {
|
||||||
|
tableData,
|
||||||
|
hiddenColumns,
|
||||||
|
filteredTableData,
|
||||||
|
pageSize,
|
||||||
|
} = this.props;
|
||||||
const computedSelectedRowIndices = Array.isArray(
|
const computedSelectedRowIndices = Array.isArray(
|
||||||
this.props.selectedRowIndices,
|
this.props.selectedRowIndices,
|
||||||
)
|
)
|
||||||
|
|
@ -524,28 +587,6 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
this.props.updateWidgetMetaProperty("pageNo", pageNo);
|
this.props.updateWidgetMetaProperty("pageNo", pageNo);
|
||||||
}
|
}
|
||||||
const { componentWidth, componentHeight } = this.getComponentDimensions();
|
const { componentWidth, componentHeight } = this.getComponentDimensions();
|
||||||
const tableSizes =
|
|
||||||
TABLE_SIZES[this.props.compactMode || CompactModeTypes.DEFAULT];
|
|
||||||
let pageSize = Math.floor(
|
|
||||||
(componentHeight -
|
|
||||||
tableSizes.TABLE_HEADER_HEIGHT -
|
|
||||||
tableSizes.COLUMN_HEADER_HEIGHT) /
|
|
||||||
tableSizes.ROW_HEIGHT,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
componentHeight -
|
|
||||||
(tableSizes.TABLE_HEADER_HEIGHT +
|
|
||||||
tableSizes.COLUMN_HEADER_HEIGHT +
|
|
||||||
tableSizes.ROW_HEIGHT * pageSize) >
|
|
||||||
0
|
|
||||||
)
|
|
||||||
pageSize += 1;
|
|
||||||
|
|
||||||
if (pageSize !== this.props.pageSize) {
|
|
||||||
this.props.updateWidgetMetaProperty("pageSize", pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Skeleton />}>
|
<Suspense fallback={<Skeleton />}>
|
||||||
<ReactTableComponent
|
<ReactTableComponent
|
||||||
|
|
@ -563,6 +604,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
columnNameMap={this.props.columnNameMap}
|
columnNameMap={this.props.columnNameMap}
|
||||||
columnTypeMap={this.props.columnTypeMap}
|
columnTypeMap={this.props.columnTypeMap}
|
||||||
columnOrder={this.props.columnOrder}
|
columnOrder={this.props.columnOrder}
|
||||||
|
triggerRowSelection={this.props.triggerRowSelection}
|
||||||
pageSize={Math.max(1, pageSize)}
|
pageSize={Math.max(1, pageSize)}
|
||||||
onCommandClick={this.onCommandClick}
|
onCommandClick={this.onCommandClick}
|
||||||
selectedRowIndex={
|
selectedRowIndex={
|
||||||
|
|
@ -590,7 +632,14 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
super.updateWidgetProperty("columnNameMap", columnNameMap);
|
super.updateWidgetProperty("columnNameMap", columnNameMap);
|
||||||
}}
|
}}
|
||||||
handleResizeColumn={(columnSizeMap: { [key: string]: number }) => {
|
handleResizeColumn={(columnSizeMap: { [key: string]: number }) => {
|
||||||
super.updateWidgetProperty("columnSizeMap", columnSizeMap);
|
if (this.props.renderMode === RenderModes.CANVAS) {
|
||||||
|
super.updateWidgetProperty("columnSizeMap", columnSizeMap);
|
||||||
|
} else {
|
||||||
|
this.props.updateWidgetMetaProperty(
|
||||||
|
"columnSizeMap",
|
||||||
|
columnSizeMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
handleReorderColumn={(columnOrder: string[]) => {
|
handleReorderColumn={(columnOrder: string[]) => {
|
||||||
super.updateWidgetProperty("columnOrder", columnOrder);
|
super.updateWidgetProperty("columnOrder", columnOrder);
|
||||||
|
|
@ -607,11 +656,7 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
||||||
}}
|
}}
|
||||||
compactMode={this.props.compactMode || CompactModeTypes.DEFAULT}
|
compactMode={this.props.compactMode || CompactModeTypes.DEFAULT}
|
||||||
updateCompactMode={(compactMode: CompactMode) => {
|
updateCompactMode={(compactMode: CompactMode) => {
|
||||||
if (this.props.renderMode === RenderModes.CANVAS) {
|
this.props.updateWidgetMetaProperty("compactMode", compactMode);
|
||||||
this.props.updateWidgetMetaProperty("compactMode", compactMode);
|
|
||||||
} else {
|
|
||||||
this.props.updateWidgetMetaProperty("compactMode", compactMode);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
sortTableColumn={this.handleColumnSorting}
|
sortTableColumn={this.handleColumnSorting}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
extraLibraries,
|
extraLibraries,
|
||||||
getDynamicBindings,
|
getDynamicBindings,
|
||||||
getEntityDynamicBindingPathList,
|
getEntityDynamicBindingPathList,
|
||||||
|
isChildPropertyPath,
|
||||||
isPathADynamicBinding,
|
isPathADynamicBinding,
|
||||||
isPathADynamicTrigger,
|
isPathADynamicTrigger,
|
||||||
unsafeFunctionForEval,
|
unsafeFunctionForEval,
|
||||||
|
|
@ -33,7 +34,6 @@ import {
|
||||||
CrashingError,
|
CrashingError,
|
||||||
DataTreeDiffEvent,
|
DataTreeDiffEvent,
|
||||||
getValidatedTree,
|
getValidatedTree,
|
||||||
isChildPropertyPath,
|
|
||||||
makeParentsDependOnChildren,
|
makeParentsDependOnChildren,
|
||||||
removeFunctions,
|
removeFunctions,
|
||||||
removeFunctionsFromDataTree,
|
removeFunctionsFromDataTree,
|
||||||
|
|
@ -148,9 +148,10 @@ ctx.addEventListener(
|
||||||
true,
|
true,
|
||||||
callbackData,
|
callbackData,
|
||||||
);
|
);
|
||||||
|
const cleanTriggers = removeFunctions(triggers);
|
||||||
const errors = dataTreeEvaluator.errors;
|
const errors = dataTreeEvaluator.errors;
|
||||||
dataTreeEvaluator.clearErrors();
|
dataTreeEvaluator.clearErrors();
|
||||||
return { triggers, errors };
|
return { triggers: cleanTriggers, errors };
|
||||||
}
|
}
|
||||||
case EVAL_WORKER_ACTIONS.CLEAR_CACHE: {
|
case EVAL_WORKER_ACTIONS.CLEAR_CACHE: {
|
||||||
dataTreeEvaluator = undefined;
|
dataTreeEvaluator = undefined;
|
||||||
|
|
@ -180,12 +181,14 @@ ctx.addEventListener(
|
||||||
value,
|
value,
|
||||||
props,
|
props,
|
||||||
} = requestData;
|
} = requestData;
|
||||||
return validateWidgetProperty(
|
return removeFunctions(
|
||||||
widgetTypeConfigMap,
|
validateWidgetProperty(
|
||||||
widgetType,
|
widgetTypeConfigMap,
|
||||||
property,
|
widgetType,
|
||||||
value,
|
property,
|
||||||
props,
|
value,
|
||||||
|
props,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
@ -1373,10 +1376,11 @@ const addFunctions = (dataTree: Readonly<DataTree>): DataTree => {
|
||||||
withFunction.navigateTo = function(
|
withFunction.navigateTo = function(
|
||||||
pageNameOrUrl: string,
|
pageNameOrUrl: string,
|
||||||
params: Record<string, string>,
|
params: Record<string, string>,
|
||||||
|
target?: string,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
type: "NAVIGATE_TO",
|
type: "NAVIGATE_TO",
|
||||||
payload: { pageNameOrUrl, params },
|
payload: { pageNameOrUrl, params, target },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
withFunction.actionPaths.push("navigateTo");
|
withFunction.actionPaths.push("navigateTo");
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { isChildPropertyPath } from "./evaluationUtils";
|
|
||||||
|
|
||||||
describe("isChildPropertyPath function", () => {
|
|
||||||
it("works", () => {
|
|
||||||
const cases: Array<[string, string, boolean]> = [
|
|
||||||
["Table1.selectedRow", "Table1.selectedRows", false],
|
|
||||||
["Table1.selectedRow", "Table1.selectedRow.email", true],
|
|
||||||
["Table1.selectedRow", "1Table1.selectedRow", false],
|
|
||||||
["Table1.selectedRow", "Table11selectedRow", false],
|
|
||||||
["Table1.selectedRow", "Table1.selectedRow", true],
|
|
||||||
];
|
|
||||||
cases.forEach((testCase) => {
|
|
||||||
const result = isChildPropertyPath(testCase[0], testCase[1]);
|
|
||||||
expect(result).toBe(testCase[2]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user