Merge branch 'release' into 'master'
Release See merge request theappsmith/internal-tools-client!510
|
|
@ -75,6 +75,33 @@ cypress-test-release:
|
|||
- release
|
||||
- merge_requests
|
||||
|
||||
# cypress-test-fix:
|
||||
# stage: build
|
||||
# script:
|
||||
# - *set_env_variables
|
||||
# - yarn install
|
||||
# # show where the Cypress test runner binaries are cached
|
||||
# - $(npm bin)/cypress cache path
|
||||
# # show all installed versions of Cypress binary
|
||||
# - $(npm bin)/cypress cache list
|
||||
# - $(npm bin)/cypress verify
|
||||
# - curl https://s3.ap-south-1.amazonaws.com/dev.public.appsmith/react-build/build1.tar.gz --output build.tar.gz
|
||||
# - tar xvf build.tar.gz
|
||||
# - yarn global add serve
|
||||
# - serve -s build -p 3000 &
|
||||
# # This is required in order to ensure that all the test cases pass
|
||||
# - echo "127.0.0.1 dev.appsmith.com" >> /etc/hosts
|
||||
# - yarn test
|
||||
# artifacts:
|
||||
# when: always
|
||||
# expire_in: 1 week
|
||||
# paths:
|
||||
# - cypress/screenshots
|
||||
# - cypress/videos
|
||||
# only:
|
||||
# # We don't test on master right now because of changing environment variables for REACT_APP_BASE_URL. Need to figure out a way to configure that.
|
||||
# - fix/test-revert
|
||||
|
||||
docker-package-release:
|
||||
image: docker:dind
|
||||
services:
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./craco.common.config.js');
|
||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
const merge = require("webpack-merge");
|
||||
const common = require("./craco.common.config.js");
|
||||
|
||||
const env = process.env.REACT_APP_ENVIRONMENT
|
||||
const env = process.env.REACT_APP_ENVIRONMENT;
|
||||
|
||||
const plugins = []
|
||||
const plugins = [];
|
||||
|
||||
if(env === "PRODUCTION" || env === "STAGING") {
|
||||
plugins.push(new SentryWebpackPlugin({
|
||||
include: 'build',
|
||||
ignore: ['node_modules', 'webpack.config.js'],
|
||||
release: process.env.REACT_APP_SENTRY_RELEASE
|
||||
}))
|
||||
if (env === "PRODUCTION" || env === "STAGING") {
|
||||
plugins.push(
|
||||
new SentryWebpackPlugin({
|
||||
include: "build",
|
||||
ignore: ["node_modules", "webpack.config.js"],
|
||||
release: process.env.REACT_APP_SENTRY_RELEASE,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = merge(common, {
|
||||
webpack: {
|
||||
plugins: plugins
|
||||
plugins: plugins,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"baseUrl":"http://dev.appsmith.com:3000/",
|
||||
"defaultCommandTimeout": 10000,
|
||||
"defaultCommandTimeout": 20000,
|
||||
"requestTimeout": 21000,
|
||||
"pageLoadTimeout": 20000,
|
||||
|
||||
"video": false,
|
||||
"reporter": "mochawesome",
|
||||
"reporterOptions": {
|
||||
"reportDir": "results",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,14 @@
|
|||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Button Widget Functionality", function() {
|
||||
it("Button Widget Functionality", function() {
|
||||
cy.NavigateToCommonWidgets();
|
||||
cy.get(".t--nav-link-widgets-editor").click();
|
||||
cy.get(widgetsPage.buttonWidget).click({ force: true });
|
||||
|
||||
//Changing the text on the Button
|
||||
cy.get(".CodeMirror textarea")
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("Test Button Text", { force: true })
|
||||
.wait(5000);
|
||||
|
||||
// TODO instead of testing the textarea, test the actual widget
|
||||
// cy.get(".CodeMirror textarea")
|
||||
// .first()
|
||||
// .should("have.value", "Test Button Text");
|
||||
cy.testCodeMirror("Test Button Text");
|
||||
|
||||
//Select and verify the Show Modal from the onClick dropdown
|
||||
cy.get(widgetsPage.buttonOnClick)
|
||||
|
|
@ -34,6 +23,6 @@ context("Cypress test", function() {
|
|||
.find(".bp3-button-text")
|
||||
.should("have.text", "Show Modal");
|
||||
//Verify Modal Widget
|
||||
cy.CreateModal();
|
||||
// cy.CreateModal();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Container Widget Functionality", function() {
|
||||
it("Container Widget Functionality", function() {
|
||||
cy.NavigateToCommonWidgets();
|
||||
cy.get(widgetsPage.containerWidget)
|
||||
|
|
@ -12,16 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click();
|
||||
//Checking the edit props for container changing the background color of container
|
||||
cy.get(".CodeMirror textarea")
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("#C0C0C0", { force: true })
|
||||
.wait(5000);
|
||||
|
||||
// TODO instead of testing the textarea, test the actual widget
|
||||
// cy.get(".CodeMirror textarea").should("have.value", "#C0C0C0");
|
||||
cy.testCodeMirror("#C0C0C0");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Input Widget Functionality", function() {
|
||||
it("Input Widget Functionality", function() {
|
||||
cy.NavigateToCommonWidgets();
|
||||
cy.get(widgetsPage.inputWidget)
|
||||
|
|
@ -12,14 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click();
|
||||
//Checking the edit props for container and changing the Input label name
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("Test Input Label", { force: true })
|
||||
.wait(5000);
|
||||
cy.testCodeMirror("Test Input Label");
|
||||
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Table Widget Functionality", function() {
|
||||
it("Table Widget Functionality", function() {
|
||||
cy.NavigateToCommonWidgets();
|
||||
cy.get(widgetsPage.tableWidget)
|
||||
|
|
@ -12,18 +12,6 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click();
|
||||
//Checking the edit props for Table Widget and also the properties of Table widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.should("be.empty")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("{{UsersApi.data}}", {
|
||||
parseSpecialCharSequences: false,
|
||||
force: true,
|
||||
})
|
||||
.wait(5000);
|
||||
|
||||
cy.get(widgetsPage.tableOnRowSelected)
|
||||
.get(commonlocators.dropdownSelectButton)
|
||||
|
|
@ -31,13 +19,14 @@ context("Cypress test", function() {
|
|||
.click({ force: true })
|
||||
.get("ul.bp3-menu")
|
||||
.children()
|
||||
.contains("Navigate to URL")
|
||||
.contains("Navigate To")
|
||||
.click();
|
||||
cy.wait("@updateLayout");
|
||||
cy.get(widgetsPage.tableOnRowSelected)
|
||||
.get(commonlocators.dropdownSelectButton)
|
||||
.first()
|
||||
.find("> .bp3-button-text")
|
||||
.should("have.text", "Navigate to URL");
|
||||
.should("have.text", "Navigate To");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Text Widget Functionality", function() {
|
||||
it("Text Widget Functionality", function() {
|
||||
cy.NavigateToCommonWidgets();
|
||||
cy.get(widgetsPage.textWidget)
|
||||
|
|
@ -12,19 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click();
|
||||
//Changing the text on the text widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("Test text", { force: true })
|
||||
.wait(5000);
|
||||
|
||||
// TODO instead of testing the textarea, test the actual widget
|
||||
// cy.get(".CodeMirror textarea")
|
||||
// .first()
|
||||
// .should("have.value", "Test text");
|
||||
cy.testCodeMirror("Test text");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Checkbox Widget Functionality", function() {
|
||||
it("Checkbox Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.checkboxWidget)
|
||||
|
|
@ -12,16 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Checkbox and also the properties of Checkbox widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.should("be.empty")
|
||||
.clear({ force: true })
|
||||
.type("Test Checkbox");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "Test Checkbox");
|
||||
cy.testCodeMirror("Test Checkbox");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("DatePicker Widget Functionality", function() {
|
||||
it("DatePicker Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.datepickerWidget)
|
||||
|
|
@ -12,16 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for DatePicker and also the properties of DatePicker widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.should("be.empty")
|
||||
.clear({ force: true })
|
||||
.type("From Date");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "From Date");
|
||||
cy.testCodeMirror("From Date");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Dropdown Widget Functionality", function() {
|
||||
it("Dropdown Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.dropdownWidget)
|
||||
|
|
@ -12,16 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Dropdown and also the properties of Dropdown widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("Test Dropdown");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "Test Dropdown");
|
||||
cy.testCodeMirror("Test Dropdown");
|
||||
|
||||
cy.get(formWidgetsPage.dropdownSelectionType)
|
||||
.find(".bp3-button")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("FilePicker Widget Functionality", function() {
|
||||
it("FilePicker Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.filepickerWidget)
|
||||
|
|
@ -12,16 +12,8 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for FilePicker and also the properties of FilePicker widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.should("be.empty")
|
||||
.clear({ force: true })
|
||||
.type("Upload Files");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "Upload Files");
|
||||
|
||||
cy.testCodeMirror("Upload Files");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Form Widget Functionality", function() {
|
||||
it("Form Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.formWidget)
|
||||
|
|
@ -12,16 +12,8 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Form and also the properties of Form widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.should("be.empty")
|
||||
.clear({ force: true })
|
||||
.type("Gray");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "Gray");
|
||||
cy.testCodeMirror("Gray");
|
||||
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Radio Widget Functionality", function() {
|
||||
it("Radio Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.radioWidget)
|
||||
|
|
@ -12,27 +12,18 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Radio Widget and also the properties of Radio widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("Test Radio");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "Test Radio");
|
||||
cy.testCodeMirror("Test Radio");
|
||||
cy.get(formWidgetsPage.radioOnSelectionChangeDropdown)
|
||||
.get(commonlocators.dropdownSelectButton)
|
||||
.click({ force: true })
|
||||
.get("ul.bp3-menu")
|
||||
.children()
|
||||
.contains("Navigate to URL")
|
||||
.contains("Navigate To")
|
||||
.click();
|
||||
cy.get(formWidgetsPage.radioOnSelectionChangeDropdown)
|
||||
.get(commonlocators.dropdownSelectButton)
|
||||
.find("> .bp3-button-text")
|
||||
.should("have.text", "Navigate to URL");
|
||||
.should("have.text", "Navigate To");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("RichTextEditor Widget Functionality", function() {
|
||||
it("RichTextEditor Widget Functionality", function() {
|
||||
cy.NavigateToFormWidgets();
|
||||
cy.get(formWidgetsPage.richTextEditorWidget)
|
||||
|
|
@ -17,13 +17,13 @@ context("Cypress test", function() {
|
|||
.click({ force: true })
|
||||
.get("ul.bp3-menu")
|
||||
.children()
|
||||
.contains("Navigate to URL")
|
||||
.contains("Navigate To")
|
||||
.click();
|
||||
cy.get(formWidgetsPage.richEditorOnTextChange)
|
||||
.get(commonlocators.dropdownSelectButton)
|
||||
.find("> span")
|
||||
.eq(0)
|
||||
.should("have.text", "Navigate to URL");
|
||||
.should("have.text", "Navigate To");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const viewWidgetsPage = require("../../../locators/ViewWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Chart Widget Functionality", function() {
|
||||
it("Chart Widget Functionality", function() {
|
||||
cy.NavigateToViewWidgets();
|
||||
cy.get(viewWidgetsPage.chartWidget)
|
||||
|
|
@ -12,16 +12,7 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Chart and also the properties of Chart widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type("App Sign Up");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", "App Sign Up");
|
||||
cy.testCodeMirror("App Sign Up");
|
||||
cy.get(viewWidgetsPage.chartSelectChartType)
|
||||
.find(".bp3-button")
|
||||
.click({ force: true })
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const viewWidgetsPage = require("../../../locators/ViewWidgets.json");
|
||||
|
||||
context("Cypress test", function() {
|
||||
describe("Image Widget Functionality", function() {
|
||||
it("Image Widget Functionality", function() {
|
||||
cy.NavigateToViewWidgets();
|
||||
cy.get(viewWidgetsPage.imageWidget)
|
||||
|
|
@ -12,21 +12,9 @@ context("Cypress test", function() {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
//Checking the edit props for Image and also the properties of Image widget
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.clear({ force: true })
|
||||
.should("be.empty")
|
||||
.type(
|
||||
"https://images.pexels.com/photos/60597/dahlia-red-blossom-bloom-60597.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
|
||||
);
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should(
|
||||
"have.value",
|
||||
"https://images.pexels.com/photos/60597/dahlia-red-blossom-bloom-60597.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
|
||||
);
|
||||
cy.testCodeMirror(
|
||||
"https://images.pexels.com/photos/60597/dahlia-red-blossom-bloom-60597.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
|
||||
);
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"editIcon":".t--widget-propertypane-toggle",
|
||||
"editPropCrossButton":"span[icon='cross']",
|
||||
"editPropCrossButton":".t--property-pane-close-btn",
|
||||
"deleteWidgetIcon":".t--widget-delete-control",
|
||||
"dropdownSelectButton":".t--open-dropdown-Select"
|
||||
"dropdownSelectButton":".t--open-dropdown-Select-Action"
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -10,6 +10,11 @@ Cypress.Commands.add("LogintoApp", (uname, pword) => {
|
|||
cy.get(loginPage.username).type(uname);
|
||||
cy.get(loginPage.password).type(pword);
|
||||
cy.get(loginPage.submitBtn).click();
|
||||
cy.wait("@applications").should(
|
||||
"have.nested.property",
|
||||
"response.body.responseMeta.status",
|
||||
200,
|
||||
);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("SearchApp", appname => {
|
||||
|
|
@ -19,6 +24,7 @@ Cypress.Commands.add("SearchApp", appname => {
|
|||
.first()
|
||||
.click({ force: true });
|
||||
cy.get("#loading").should("not.exist");
|
||||
// Wait added because after opening the application editor, sometimes it takes a little time.
|
||||
});
|
||||
|
||||
Cypress.Commands.add("NavigateToCommonWidgets", () => {
|
||||
|
|
@ -28,6 +34,7 @@ Cypress.Commands.add("NavigateToCommonWidgets", () => {
|
|||
.click({ force: true });
|
||||
cy.get("#loading").should("not.exist");
|
||||
cy.get(pages.widgetsEditor).click();
|
||||
cy.wait("@getPage");
|
||||
cy.get("#loading").should("not.exist");
|
||||
});
|
||||
|
||||
|
|
@ -51,30 +58,65 @@ Cypress.Commands.add("NavigateToViewWidgets", () => {
|
|||
cy.get("#loading").should("not.exist");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("CreateModal", () => {
|
||||
cy.get(modalWidgetPage.selectModal).click();
|
||||
cy.get(modalWidgetPage.createModalButton).click({ force: true });
|
||||
cy.get(modalWidgetPage.controlModalType)
|
||||
.find(".bp3-button")
|
||||
.click({ force: true })
|
||||
.get("ul.bp3-menu")
|
||||
.children()
|
||||
.contains("Alert Modal")
|
||||
.click();
|
||||
cy.get(modalWidgetPage.controlModalType)
|
||||
.find(".bp3-button > .bp3-button-text")
|
||||
.should("have.text", "Alert Modal");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
cy.get(modalWidgetPage.modalWidget)
|
||||
.get(commonlocators.deleteWidgetIcon)
|
||||
.first()
|
||||
.click({ force: true });
|
||||
});
|
||||
// Cypress.Commands.add("CreateModal", () => {
|
||||
// cy.get(modalWidgetPage.selectModal).click();
|
||||
// cy.get(modalWidgetPage.createModalButton).click({ force: true });
|
||||
// cy.get(modalWidgetPage.controlModalType)
|
||||
// .find(".bp3-button")
|
||||
// .click({ force: true })
|
||||
// .get("ul.bp3-menu")
|
||||
// .children()
|
||||
// .contains("Alert Modal")
|
||||
// .click();
|
||||
// cy.get(modalWidgetPage.controlModalType)
|
||||
// .find(".bp3-button > .bp3-button-text")
|
||||
// .should("have.text", "Alert Modal");
|
||||
// cy.get(commonlocators.editPropCrossButton).click();
|
||||
// cy.get(modalWidgetPage.modalWidget)
|
||||
// .get(commonlocators.deleteWidgetIcon)
|
||||
// .first()
|
||||
// .click({ force: true });
|
||||
// });
|
||||
|
||||
Cypress.Commands.add("PublishtheApp", () => {
|
||||
cy.xpath(homePage.homePageID).contains("All changes saved");
|
||||
cy.get(homePage.publishButton).click();
|
||||
// cy.window().then(win => {
|
||||
// cy.get(homePage.publishCrossButton).click();
|
||||
// });
|
||||
cy.wait("@publishApp");
|
||||
cy.get(homePage.publishCrossButton).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("getCodeMirror", () => {
|
||||
return cy
|
||||
.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("testCodeMirror", value => {
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("{ctrl}{shift}{downarrow}")
|
||||
.then($cm => {
|
||||
if ($cm.val() !== "") {
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.clear({
|
||||
force: true,
|
||||
});
|
||||
cy.wait("@updateLayout");
|
||||
}
|
||||
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.type(value, {
|
||||
force: true,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
cy.wait("@updateLayout");
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.should("have.value", value);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,22 @@ const inputData = require("../fixtures/inputdata.json");
|
|||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands";
|
||||
before(function() {
|
||||
cy.server();
|
||||
cy.route("GET", "/api/v1/applications").as("applications");
|
||||
cy.route("GET", "/api/v1/users/profile").as("getUser");
|
||||
cy.route("GET", "/api/v1/plugins").as("getPlugins");
|
||||
|
||||
cy.route("GET", "/api/v1/configs/name/propertyPane").as("getPropertyPane");
|
||||
cy.route("GET", "/api/v1/datasources").as("getDataSources");
|
||||
cy.route("GET", "/api/v1/pages/application/*").as("getPagesForApp");
|
||||
cy.route("GET", "/api/v1/pages/*").as("getPage");
|
||||
cy.route("GET", "/api/v1/actions*").as("getActions");
|
||||
|
||||
cy.route("GET", "/api/v1/organizations").as("organizations");
|
||||
cy.route("POST", "/api/v1/actions/execute").as("executeAction");
|
||||
cy.route("POST", "/api/v1/applications/publish/*").as("publishApp");
|
||||
cy.route("PUT", "/api/v1/layouts/*/pages/*").as("updateLayout");
|
||||
|
||||
cy.LogintoApp(loginData.username, loginData.password);
|
||||
cy.SearchApp(inputData.appname);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
[[headers]]
|
||||
for = "/static/*"
|
||||
[header.values]
|
||||
cache-control = "max-age=604800"
|
||||
|
||||
[context.production]
|
||||
[context.production.environment]
|
||||
REACT_APP_ENVIRONMENT = "PRODUCTION"
|
||||
|
|
|
|||
|
|
@ -73,7 +73,11 @@
|
|||
"react-dnd-html5-backend": "^9.3.4",
|
||||
"react-dnd-touch-backend": "^9.4.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-google-maps": "^9.4.5",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-infinite-scroller": "^1.2.4",
|
||||
"react-json-view": "^1.19.1",
|
||||
"react-paginating": "^1.4.0",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
|
|
@ -89,10 +93,8 @@
|
|||
"redux-saga": "^1.1.3",
|
||||
"reselect": "^4.0.0",
|
||||
"shallowequal": "^1.1.0",
|
||||
"source-map-explorer": "^2.1.1",
|
||||
"styled-components": "^4.1.3",
|
||||
"tinycolor2": "^1.4.1",
|
||||
"tinymce": "^5.2.0",
|
||||
"toposort": "^2.0.2",
|
||||
"ts-loader": "^6.0.4",
|
||||
"typescript": "^3.6.3",
|
||||
|
|
@ -103,6 +105,8 @@
|
|||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "REACT_APP_BASE_URL=https://release-api.appsmith.com REACT_APP_ENVIRONMENT=DEVELOPMENT HOST=dev.appsmith.com craco start",
|
||||
"build": "./build.sh",
|
||||
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
|
||||
"build-staging": "REACT_APP_BASE_URL=https://release-api.appsmith.com REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
|
||||
"test": "cypress/test.sh",
|
||||
"eject": "react-scripts eject",
|
||||
"start-prod": "REACT_APP_BASE_URL=https://api.appsmith.com REACT_APP_ENVIRONMENT=PRODUCTION craco start",
|
||||
|
|
@ -160,6 +164,7 @@
|
|||
"react-test-renderer": "^16.11.0",
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"source-map-explorer": "^2.4.2",
|
||||
"storybook-addon-designs": "^5.1.1",
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,15 +2,12 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<script type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
|
||||
<script type="text/javascript" src="/tinymce/tinymce.min.js"></script>
|
||||
<script rel="prefetch" type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=DM+Sans:400,500,700&display=swap" rel="stylesheet" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=DM+Sans:400,500,700&display=swap" rel="stylesheet" />
|
||||
<title>Appsmith</title>
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,3 +13,10 @@ export const initApiPane = (urlId?: string): ReduxAction<{ id?: string }> => {
|
|||
payload: { id: urlId },
|
||||
};
|
||||
};
|
||||
|
||||
export const createNewApiAction = (
|
||||
pageId: string,
|
||||
): ReduxAction<{ pageId: string }> => ({
|
||||
type: ReduxActionTypes.CREATE_NEW_API_ACTION,
|
||||
payload: { pageId },
|
||||
});
|
||||
|
|
|
|||
7
app/client/src/actions/collectionAction.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
export const fetchImportedCollections = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_IMPORTED_COLLECTIONS_INIT,
|
||||
};
|
||||
};
|
||||
9
app/client/src/actions/importActions.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { curlImportFormValues } from "pages/Editor/APIEditor/helpers";
|
||||
|
||||
export const submitCurlImportForm = (payload: curlImportFormValues) => {
|
||||
return {
|
||||
type: ReduxActionTypes.SUBMIT_CURL_FORM_INIT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
|
@ -101,6 +101,7 @@ export type WidgetAddChild = {
|
|||
parentRowSpace: number;
|
||||
parentColumnSpace: number;
|
||||
newWidgetId: string;
|
||||
tabId: string;
|
||||
props?: Record<string, any>;
|
||||
};
|
||||
|
||||
|
|
|
|||
46
app/client/src/actions/providerActions.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
import {
|
||||
AddApiToPageRequest,
|
||||
FetchProviderWithCategoryRequest,
|
||||
} from "api/ProvidersApi";
|
||||
|
||||
export const fetchProviders = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PROVIDERS_INIT,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchProviderCategories = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PROVIDERS_CATEGORIES_INIT,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchProviderTemplates = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT,
|
||||
};
|
||||
};
|
||||
|
||||
export const addApiToPage = (payload: AddApiToPageRequest) => {
|
||||
return {
|
||||
type: ReduxActionTypes.ADD_API_TO_PAGE_INIT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchProvidersWithCategory = (
|
||||
payload: FetchProviderWithCategoryRequest,
|
||||
) => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const clearProviders = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.CLEAR_PROVIDERS,
|
||||
};
|
||||
};
|
||||
|
|
@ -8,15 +8,15 @@ import {
|
|||
ExecuteErrorPayload,
|
||||
PageAction,
|
||||
} from "constants/ActionConstants";
|
||||
import { BatchAction, batchAction } from "actions/batchActions";
|
||||
|
||||
export const executeAction = (
|
||||
payload: ExecuteActionPayload,
|
||||
): ReduxAction<ExecuteActionPayload> => {
|
||||
return {
|
||||
): BatchAction<ExecuteActionPayload> =>
|
||||
batchAction({
|
||||
type: ReduxActionTypes.EXECUTE_ACTION,
|
||||
payload,
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
export const executeActionError = (
|
||||
executeErrorPayload: ExecuteErrorPayload,
|
||||
|
|
@ -27,10 +27,11 @@ export const executeActionError = (
|
|||
|
||||
export const executePageLoadActions = (
|
||||
payload: PageAction[][],
|
||||
): ReduxAction<PageAction[][]> => ({
|
||||
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
payload,
|
||||
});
|
||||
): BatchAction<PageAction[][]> =>
|
||||
batchAction({
|
||||
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const disableDragAction = (
|
||||
disable: boolean,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,15 @@ export interface Property {
|
|||
value: string;
|
||||
}
|
||||
|
||||
export interface BodyFormData {
|
||||
editable: boolean;
|
||||
mandatory: boolean;
|
||||
description: string;
|
||||
key: string;
|
||||
value?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface APIConfigRequest {
|
||||
headers: Property[];
|
||||
httpMethod: string;
|
||||
|
|
@ -40,6 +49,7 @@ export interface APIConfigRequest {
|
|||
body: JSON | string | Record<string, any> | null;
|
||||
queryParameters: Property[];
|
||||
paginationType: PaginationType;
|
||||
bodyFormData: BodyFormData[];
|
||||
}
|
||||
|
||||
export interface QueryConfig {
|
||||
|
|
@ -54,12 +64,40 @@ export interface ActionCreateUpdateResponse extends ApiResponse {
|
|||
export interface RestAction {
|
||||
id: string;
|
||||
name: string;
|
||||
datasource: Pick<Datasource, "id"> | Omit<Datasource, "id">;
|
||||
datasource:
|
||||
| Pick<Datasource, "id">
|
||||
| Omit<Datasource, "id">
|
||||
| Partial<Datasource>;
|
||||
pluginType?: string;
|
||||
pageId: string;
|
||||
actionConfiguration: Partial<APIConfigRequest>;
|
||||
jsonPathKeys: string[];
|
||||
cacheResponse?: string;
|
||||
pluginId: string;
|
||||
}
|
||||
|
||||
export interface RapidApiAction {
|
||||
id: string;
|
||||
name: string;
|
||||
datasource: Pick<Datasource, "id"> | Omit<Datasource, "id">;
|
||||
pluginType: string;
|
||||
pageId: string;
|
||||
actionConfiguration: Partial<APIConfigRequest>;
|
||||
jsonPathKeys: string[];
|
||||
cacheResponse?: string;
|
||||
templateId: string;
|
||||
proverId: string;
|
||||
provider: ProviderInfo;
|
||||
pluginId: string;
|
||||
documentation: { text: string };
|
||||
}
|
||||
|
||||
export interface ProviderInfo {
|
||||
name: string;
|
||||
imageUrl: string;
|
||||
url: string;
|
||||
description: string;
|
||||
credentialSteps: string;
|
||||
}
|
||||
|
||||
export type PaginationField = "PREV" | "NEXT";
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
import { ActionApiResponse } from "./ActionAPI";
|
||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||
import { setRouteBeforeLogin } from "utils/storage";
|
||||
import history from "utils/history";
|
||||
const { apiUrl, baseUrl } = getAppsmithConfigs();
|
||||
|
||||
//TODO(abhinav): Refactor this to make more composable.
|
||||
|
|
@ -58,8 +59,12 @@ axiosInstance.interceptors.response.use(
|
|||
// console.log(error.response.headers);
|
||||
if (error.response.status === 401) {
|
||||
setRouteBeforeLogin(window.location.pathname);
|
||||
window.location.href = AUTH_LOGIN_URL;
|
||||
return;
|
||||
history.push(AUTH_LOGIN_URL);
|
||||
return Promise.reject({
|
||||
code: 401,
|
||||
message: "Unauthorized. Redirecting to login page...",
|
||||
show: false,
|
||||
});
|
||||
}
|
||||
if (error.response.data.responseMeta) {
|
||||
return Promise.resolve(error.response.data);
|
||||
|
|
|
|||
12
app/client/src/api/CollectionApi.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { AxiosPromise } from "axios";
|
||||
import Api from "./Api";
|
||||
import { ImportedCollections } from "constants/collectionsConstants";
|
||||
|
||||
class ImportedCollectionsApi extends Api {
|
||||
static importedCollectionsURL = "v1/import/templateCollections";
|
||||
static fetchImportedCollections(): AxiosPromise<ImportedCollections> {
|
||||
return Api.get(ImportedCollectionsApi.importedCollectionsURL);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportedCollectionsApi;
|
||||
25
app/client/src/api/ImportApi.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { AxiosPromise } from "axios";
|
||||
import Api from "./Api";
|
||||
import { ApiResponse } from "./ApiResponses";
|
||||
|
||||
export interface CurlImportRequest {
|
||||
type: string;
|
||||
pageId: string;
|
||||
name: string;
|
||||
curl: string;
|
||||
}
|
||||
|
||||
class CurlImportApi extends Api {
|
||||
static curlImportURL = `v1/import`;
|
||||
|
||||
static curlImport(request: CurlImportRequest): AxiosPromise<ApiResponse> {
|
||||
const { pageId, name, curl } = request;
|
||||
return Api.post(CurlImportApi.curlImportURL, curl, {
|
||||
type: "CURL",
|
||||
pageId,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CurlImportApi;
|
||||
|
|
@ -6,6 +6,8 @@ export interface Plugin {
|
|||
id: string;
|
||||
name: string;
|
||||
type: "API" | "DB";
|
||||
packageName: string;
|
||||
uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm";
|
||||
}
|
||||
|
||||
class PluginsApi extends Api {
|
||||
|
|
|
|||
78
app/client/src/api/ProvidersApi.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { AxiosPromise } from "axios";
|
||||
import Api from "./Api";
|
||||
import { ApiResponse } from "./ApiResponses";
|
||||
import { Providers, ProviderTemplates } from "constants/providerConstants";
|
||||
|
||||
export interface FetchProvidersResponse extends ApiResponse {
|
||||
data: Providers;
|
||||
}
|
||||
|
||||
export interface FetchProviderCategoriesResponse extends ApiResponse {
|
||||
data: string[];
|
||||
}
|
||||
|
||||
export interface FetchProviderTemplateResponse extends ApiResponse {
|
||||
data: ProviderTemplates[];
|
||||
}
|
||||
|
||||
export interface FetchProviderTemplatesRequest {
|
||||
providerId: string;
|
||||
}
|
||||
|
||||
export interface FetchProviderWithCategoryRequest {
|
||||
category: string;
|
||||
page: number;
|
||||
}
|
||||
|
||||
export interface AddApiToPageRequest {
|
||||
name: string;
|
||||
pageId: string;
|
||||
marketplaceElement: any;
|
||||
}
|
||||
|
||||
export class ProvidersApi extends Api {
|
||||
static providersURL = "v1/providers";
|
||||
static providerCategoriesURL = "v1/providers/categories";
|
||||
|
||||
static providerTemplateURL = (providerId: string) => {
|
||||
return `v1/marketplace/templates?providerId=${providerId}`;
|
||||
};
|
||||
|
||||
static providersWithCategoryURL = (category: string, page: number) => {
|
||||
return `v1/marketplace/providers?category=${category}&page=${page}&size=50`;
|
||||
};
|
||||
|
||||
static addApiToPageURL = `v1/items/addToPage`;
|
||||
|
||||
static fetchProviders(): AxiosPromise<FetchProvidersResponse> {
|
||||
return Api.get(ProvidersApi.providersURL);
|
||||
}
|
||||
|
||||
static fetchProviderTemplates(
|
||||
request: FetchProviderTemplatesRequest,
|
||||
): AxiosPromise<FetchProviderTemplateResponse> {
|
||||
const { providerId } = request;
|
||||
return Api.get(ProvidersApi.providerTemplateURL(providerId));
|
||||
}
|
||||
|
||||
static addApiToPage(request: AddApiToPageRequest): AxiosPromise<ApiResponse> {
|
||||
return Api.post(ProvidersApi.addApiToPageURL, request);
|
||||
}
|
||||
|
||||
static fetchProvidersCategories(): AxiosPromise<
|
||||
FetchProviderCategoriesResponse
|
||||
> {
|
||||
return Api.get(ProvidersApi.providerCategoriesURL);
|
||||
}
|
||||
|
||||
static fetchProvidersWithCategory(
|
||||
request: FetchProviderWithCategoryRequest,
|
||||
): AxiosPromise<FetchProvidersResponse> {
|
||||
const { page } = request;
|
||||
return Api.get(
|
||||
ProvidersApi.providersWithCategoryURL(request.category, page),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ProvidersApi;
|
||||
3
app/client/src/assets/icons/control/decrease.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="2" viewBox="0 0 12 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 2H0.75C0.551088 2 0.360322 1.89464 0.21967 1.70711C0.0790178 1.51957 0 1.26522 0 1C0 0.734784 0.0790178 0.48043 0.21967 0.292893C0.360322 0.105357 0.551088 0 0.75 0H11.25C11.4489 0 11.6397 0.105357 11.7803 0.292893C11.921 0.48043 12 0.734784 12 1C12 1.26522 11.921 1.51957 11.7803 1.70711C11.6397 1.89464 11.4489 2 11.25 2Z" fill="#768896"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 459 B |
4
app/client/src/assets/icons/control/draggable.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.2611 9H5.73889C5.33081 9 5 9.42533 5 9.95V10.05C5 10.5747 5.33081 11 5.73889 11H18.2611C18.6692 11 19 10.5747 19 10.05V9.95C19 9.42533 18.6692 9 18.2611 9Z" fill="#A3B3BF"/>
|
||||
<path d="M18.2611 13H5.73889C5.33081 13 5 13.4253 5 13.95V14.05C5 14.5747 5.33081 15 5.73889 15H18.2611C18.6692 15 19 14.5747 19 14.05V13.95C19 13.4253 18.6692 13 18.2611 13Z" fill="#A3B3BF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
3
app/client/src/assets/icons/control/increase.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 5.25H6.75V0.75C6.75 0.551088 6.67098 0.360322 6.53033 0.21967C6.38968 0.0790178 6.19891 0 6 0C5.80109 0 5.61032 0.0790178 5.46967 0.21967C5.32902 0.360322 5.25 0.551088 5.25 0.75V5.25H0.75C0.551088 5.25 0.360322 5.32902 0.21967 5.46967C0.0790178 5.61032 0 5.80109 0 6C0 6.19891 0.0790178 6.38968 0.21967 6.53033C0.360322 6.67098 0.551088 6.75 0.75 6.75H5.25V11.25C5.25 11.4489 5.32902 11.6397 5.46967 11.7803C5.61032 11.921 5.80109 12 6 12C6.19891 12 6.38968 11.921 6.53033 11.7803C6.67098 11.6397 6.75 11.4489 6.75 11.25V6.75H11.25C11.4489 6.75 11.6397 6.67098 11.7803 6.53033C11.921 6.38968 12 6.19891 12 6C12 5.80109 11.921 5.61032 11.7803 5.46967C11.6397 5.32902 11.4489 5.25 11.25 5.25Z" fill="#768896"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 829 B |
1
app/client/src/assets/icons/control/pick-my-location.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="crosshairs" class="svg-inline--fa fa-crosshairs fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M500 224h-30.364C455.724 130.325 381.675 56.276 288 42.364V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v30.364C130.325 56.276 56.276 130.325 42.364 224H12c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h30.364C56.276 381.675 130.325 455.724 224 469.636V500c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12v-30.364C381.675 455.724 455.724 381.675 469.636 288H500c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12zM288 404.634V364c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40.634C165.826 392.232 119.783 346.243 107.366 288H148c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40.634C119.768 165.826 165.757 119.783 224 107.366V148c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12v-40.634C346.174 119.768 392.217 165.757 404.634 224H364c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40.634C392.232 346.174 346.243 392.217 288 404.634zM288 256c0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 14.327-32 32-32s32 14.327 32 32z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
13
app/client/src/assets/icons/form/info-outline.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon/Outline/info">
|
||||
<path id="Mask" fill-rule="evenodd" clip-rule="evenodd" d="M11 8.31554C11 7.75751 11.448 7.30461 12 7.30461C12.552 7.30461 13 7.75751 13 8.31554C13 8.87357 12.552 9.32646 12 9.32646C11.448 9.32646 11 8.87357 11 8.31554ZM11 11.3483C11 10.7903 11.448 10.3374 12 10.3374C12.552 10.3374 13 10.7903 13 11.3483V16.4029C13 16.9609 12.552 17.4138 12 17.4138C11.448 17.4138 11 16.9609 11 16.4029V11.3483ZM12 20.4466C7.589 20.4466 4 16.8184 4 12.3592C4 7.90005 7.589 4.27185 12 4.27185C16.411 4.27185 20 7.90005 20 12.3592C20 16.8184 16.411 20.4466 12 20.4466ZM12 2.25C6.477 2.25 2 6.7759 2 12.3592C2 17.9426 6.477 22.4685 12 22.4685C17.522 22.4685 22 17.9426 22 12.3592C22 6.7759 17.522 2.25 12 2.25Z" fill="#A3B3BF"/>
|
||||
<mask id="mask25" mask-type="alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="21">
|
||||
<path id="Mask_2" fill-rule="evenodd" clip-rule="evenodd" d="M11 8.31554C11 7.75751 11.448 7.30461 12 7.30461C12.552 7.30461 13 7.75751 13 8.31554C13 8.87357 12.552 9.32646 12 9.32646C11.448 9.32646 11 8.87357 11 8.31554ZM11 11.3483C11 10.7903 11.448 10.3374 12 10.3374C12.552 10.3374 13 10.7903 13 11.3483V16.4029C13 16.9609 12.552 17.4138 12 17.4138C11.448 17.4138 11 16.9609 11 16.4029V11.3483ZM12 20.4466C7.589 20.4466 4 16.8184 4 12.3592C4 7.90005 7.589 4.27185 12 4.27185C16.411 4.27185 20 7.90005 20 12.3592C20 16.8184 16.411 20.4466 12 20.4466ZM12 2.25C6.477 2.25 2 6.7759 2 12.3592C2 17.9426 6.477 22.4685 12 22.4685C17.522 22.4685 22 17.9426 22 12.3592C22 6.7759 17.522 2.25 12 2.25Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask25)">
|
||||
<g id="🎨 Color">
|
||||
<rect id="Base" y="0.228027" width="24" height="24.2621" fill="#A3B3BF"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
3
app/client/src/assets/icons/widget/map.svg
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="29" viewBox="0 0 22 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.9997 0.664001C15.4157 0.664001 18.9997 4.216 18.9997 8.59733C18.9997 14.548 10.9997 23.3307 10.9997 23.3307C10.9997 23.3307 2.99967 14.548 2.99967 8.59733C2.99967 4.216 6.58101 0.664001 10.9997 0.664001ZM10.9997 6C10.2924 6 9.61415 6.28095 9.11406 6.78105C8.61396 7.28115 8.33301 7.95942 8.33301 8.66667C8.33301 9.37391 8.61396 10.0522 9.11406 10.5523C9.61415 11.0524 10.2924 11.3333 10.9997 11.3333C11.7069 11.3333 12.3852 11.0524 12.8853 10.5523C13.3854 10.0522 13.6663 9.37391 13.6663 8.66667C13.6663 7.95942 13.3854 7.28115 12.8853 6.78105C12.3852 6.28095 11.7069 6 10.9997 6ZM21.6663 23.3333C21.6663 26.28 16.8903 28.6667 10.9997 28.6667C5.10901 28.6667 0.333008 26.28 0.333008 23.3333C0.333008 21.6147 1.95967 20.0867 4.48367 19.1107L5.32767 20.316C3.88767 20.92 2.99967 21.7493 2.99967 22.6667C2.99967 24.5067 6.58101 26 10.9997 26C15.4183 26 18.9997 24.5067 18.9997 22.6667C18.9997 21.7493 18.1103 20.92 16.6717 20.316L17.5157 19.1107C20.041 20.0867 21.6663 21.6147 21.6663 23.3333Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
10
app/client/src/assets/images/Curl-logo.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="53" height="52" viewBox="0 0 53 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M48.8083 14.9576C48.4317 14.9576 48.127 14.6581 48.127 14.2891C48.127 13.9196 48.4317 13.6207 48.8083 13.6207C49.1843 13.6207 49.4896 13.9196 49.4896 14.2891C49.4896 14.6581 49.1843 14.9576 48.8083 14.9576ZM42.0422 26.3782C41.6656 26.3782 41.3609 26.0787 41.3609 25.7098C41.3609 25.3403 41.6656 25.0413 42.0422 25.0413C42.4182 25.0413 42.7229 25.3403 42.7229 25.7098C42.7229 26.0787 42.4182 26.3782 42.0422 26.3782ZM48.8083 13.0654C48.1195 13.0654 47.5611 13.6133 47.5611 14.2891C47.5611 14.4333 47.5981 14.5675 47.6448 14.696L41.7938 24.535C41.2284 24.6513 40.7949 25.1203 40.7949 25.7098C40.7949 26.3856 41.3534 26.9335 42.0422 26.9335C42.7304 26.9335 43.2888 26.3856 43.2888 25.7098C43.2888 25.574 43.2518 25.4498 43.2105 25.3277L49.091 15.4565C49.6392 15.3276 50.0555 14.8665 50.0555 14.2891C50.0555 13.6133 49.4971 13.0654 48.8083 13.0654Z" fill="#0C544C"/>
|
||||
<path d="M43.7068 14.9576C43.3302 14.9576 43.0255 14.6581 43.0255 14.2891C43.0255 13.9196 43.3302 13.6207 43.7068 13.6207C44.0828 13.6207 44.3875 13.9196 44.3875 14.2891C44.3875 14.6581 44.0828 14.9576 43.7068 14.9576ZM36.9401 26.3782C36.5641 26.3782 36.2588 26.0787 36.2588 25.7098C36.2588 25.3403 36.5641 25.0413 36.9401 25.0413C37.3167 25.0413 37.6214 25.3403 37.6214 25.7098C37.6214 26.0787 37.3167 26.3782 36.9401 26.3782ZM43.7068 13.0654C43.0175 13.0654 42.4596 13.6133 42.4596 14.2891C42.4596 14.4333 42.4966 14.5675 42.5432 14.696L36.6923 24.535C36.1263 24.6513 35.6929 25.1203 35.6929 25.7098C35.6929 26.3856 36.2513 26.9335 36.9401 26.9335C37.6289 26.9335 38.1873 26.3856 38.1873 25.7098C38.1873 25.574 38.1503 25.4498 38.109 25.3277L43.9895 15.4565C44.5377 15.3276 44.954 14.8665 44.954 14.2891C44.954 13.6133 44.395 13.0654 43.7068 13.0654Z" fill="#073551"/>
|
||||
<path d="M34.3881 17.0034C34.7641 17.0034 35.0694 17.3029 35.0694 17.6718C35.0694 18.0408 34.7641 18.3402 34.3881 18.3402C34.0115 18.3402 33.7068 18.0408 33.7068 17.6718C33.7068 17.3029 34.0115 17.0034 34.3881 17.0034ZM34.3881 18.8955C35.0769 18.8955 35.6353 18.3476 35.6353 17.6718C35.6353 17.5366 35.5978 17.4118 35.5565 17.2897C35.3912 16.8039 34.9385 16.4481 34.3881 16.4481C34.3001 16.4481 34.2229 16.4802 34.1397 16.4976C33.5743 16.6134 33.1409 17.0823 33.1409 17.6718C33.1409 18.3476 33.6993 18.8955 34.3881 18.8955Z" fill="#073551"/>
|
||||
<path d="M33.7068 21.9475C33.7068 21.578 34.012 21.279 34.3881 21.279C34.7641 21.279 35.0694 21.578 35.0694 21.9475C35.0694 22.3164 34.7641 22.6154 34.3881 22.6154C34.012 22.6154 33.7068 22.3164 33.7068 21.9475ZM35.6353 21.9475C35.6353 21.8117 35.5978 21.6874 35.5565 21.5653C35.3912 21.0795 34.939 20.7238 34.3881 20.7238C34.3001 20.7238 34.2229 20.7559 34.1397 20.7727C33.5743 20.889 33.1409 21.358 33.1409 21.9475C33.1409 22.6227 33.6993 23.1711 34.3881 23.1711C35.0769 23.1711 35.6353 22.6227 35.6353 21.9475Z" fill="#073551"/>
|
||||
<path d="M2.94458 23.3204C2.94458 24.2762 3.12697 24.7456 3.88657 25.3604L5.05816 26.3183C5.8006 26.9162 6.1804 26.933 7.10523 26.933H9.61578C10.1436 26.933 10.7058 26.8193 10.7058 26.1562C10.7058 25.4741 10.0793 25.3772 9.61578 25.3772H6.82628C6.51085 25.3772 6.19757 25.2151 5.91647 24.9878L4.95946 24.1941C4.71055 24.0004 4.5303 23.8383 4.5303 23.5309V21.6025C4.5303 21.2951 4.71055 21.133 4.95946 20.9372L5.91647 20.1435C6.19757 19.9182 6.51085 19.7561 6.82628 19.7561H9.61578C10.0793 19.7561 10.7058 19.6572 10.7058 18.9772C10.7058 18.314 10.1436 18.2003 9.61578 18.2003H7.10523C6.1804 18.2003 5.8006 18.2172 5.05816 18.8151L3.88657 19.7709C3.12697 20.3877 2.94458 20.8572 2.94458 21.813V23.3204Z" fill="#073551"/>
|
||||
<path d="M19.2927 19.2698C19.2927 18.7351 19.1769 18.2003 18.5009 18.2003C17.8229 18.2003 17.707 18.7351 17.707 19.2698V23.7898L15.3788 25.232C15.2308 25.3288 15.0656 25.3772 14.8832 25.3772H14.3553C13.9755 25.3772 13.5635 25.2804 13.4305 25.053C13.2974 24.8277 13.2824 24.4046 13.2653 24.0488L13.1 19.2698C13.0829 18.7666 13.0013 18.2003 12.3233 18.2003C11.598 18.2003 11.5143 18.8003 11.5315 19.3666L11.6967 24.2593C11.7289 25.1014 11.7139 25.7667 12.4241 26.3667C12.952 26.8025 13.4477 26.933 13.894 26.933H15.0656C15.4947 26.933 15.9239 26.7393 16.4367 26.4151L17.7242 25.5878V25.8467C17.7242 26.3814 17.84 26.933 18.516 26.933C19.3592 26.933 19.2927 26.1393 19.2927 25.7835V19.2698Z" fill="#073551"/>
|
||||
<path d="M20.1194 25.8635C20.1194 26.3983 20.2353 26.933 20.9112 26.933C21.5892 26.933 21.7051 26.3983 21.7051 25.8635V21.9267L23.9174 20.1288C24.1985 19.9014 24.4131 19.7561 24.7092 19.7561H25.1727C25.5525 19.7561 25.9323 19.7561 26.1297 20.0319C26.2949 20.2572 26.2949 20.453 26.2949 20.7119C26.2949 21.213 26.5267 21.6677 27.1039 21.6677C27.7819 21.6677 27.8806 21.1161 27.8806 20.5814C27.8806 19.8698 27.6982 19.3351 27.1704 18.8151C26.7412 18.394 26.2305 18.2003 25.619 18.2003H24.3465C23.8509 18.2003 23.3895 18.5077 22.9432 18.8635L21.7051 19.8698V19.2361C21.7051 18.7182 21.5399 18.2003 20.9112 18.2003C20.1859 18.2003 20.1194 18.7835 20.1194 19.3498V25.8635Z" fill="#073551"/>
|
||||
<path d="M30.7868 16.0781C30.7868 15.1539 30.5229 14.9918 29.6152 14.9918H27.948C27.418 14.9918 26.8579 15.1055 26.8579 15.7687C26.8579 16.4508 27.4845 16.5476 27.948 16.5476H29.2032V25.3772H27.948C27.418 25.3772 26.8579 25.4909 26.8579 26.1561C26.8579 26.8361 27.4845 26.933 27.948 26.933H31.9756C32.4391 26.933 33.0656 26.8361 33.0656 26.1561C33.0656 25.4909 32.5056 25.3772 31.9756 25.3772H30.7868V16.0781Z" fill="#073551"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
BIN
app/client/src/assets/images/Curl.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
27
app/client/src/assets/images/Postman-logo.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<svg width="53" height="52" viewBox="0 0 53 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M11.7954 42.0293L3.52979 42.3105L7.76694 38.0839L11.7958 42.0296L11.7954 42.0293Z" fill="#F15A24"/>
|
||||
<path d="M49.2895 24.0654C47.7539 35.8129 36.8016 44.1127 24.8302 42.6043C12.8566 41.0974 4.39721 30.3521 5.93296 18.6055C7.46961 6.85816 18.4195 -1.4425 30.394 0.0660409C42.3669 1.57246 50.8283 12.3188 49.2897 24.0651" fill="#F15A24"/>
|
||||
<path d="M33.1847 12.2389C34.6209 13.7369 33.2034 17.2786 33.2034 17.2786C33.2034 17.2786 31.8025 22.2858 23.781 28.7792L22.2837 27.3372L32.4663 17.1264L32.6631 16.8658C32.934 16.4326 33.1583 12.2098 33.1847 12.2389Z" fill="#999999"/>
|
||||
<path d="M33.1847 12.2389C34.6209 13.7369 33.2034 17.2786 33.2034 17.2786C33.2034 17.2786 31.8025 22.2858 23.781 28.7792L22.2837 27.3372L32.4663 17.1264L32.6631 16.8658C32.934 16.4326 33.1583 12.2098 33.1847 12.2389V12.2389Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20.0847 25.2145L33.1636 12.2021C33.8274 12.9755 33.9686 13.8087 33.776 14.996C33.6462 15.8183 32.1987 17.4616 32.1987 17.4616L22.2839 27.3374" fill="#666666"/>
|
||||
<path d="M20.0847 25.2145L33.1636 12.2021C33.8274 12.9755 33.9686 13.8087 33.776 14.996C33.6462 15.8183 32.1987 17.4616 32.1987 17.4616L22.2839 27.3374" stroke="white" stroke-width="0.150125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M33.1842 12.2478L20.0838 25.2146L20.1102 25.1894L20.1053 25.1913L16.855 22.049C23.6328 13.7771 28.0152 12.2349 28.0152 12.2349C28.0152 12.2349 30.8681 11.1422 32.9034 11.9901C33.0598 12.0723 33.1277 12.1196 33.1838 12.2481" fill="#FF931E"/>
|
||||
<path d="M33.1842 12.2478L20.0838 25.2146L20.1102 25.1894L20.1053 25.1913L16.855 22.049C23.6328 13.7771 28.0152 12.2349 28.0152 12.2349C28.0152 12.2349 30.8681 11.1422 32.9034 11.9901C33.0598 12.0723 33.1277 12.1196 33.1838 12.2481L33.1842 12.2478V12.2478Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M23.7798 28.7788C20.6544 31.3042 16.6545 35.2715 11.2176 38.0385L12.4269 43.1108L7.30127 38.1575L20.083 25.2144" fill="#999999"/>
|
||||
<path d="M23.7798 28.7788C20.6544 31.3042 16.6545 35.2715 11.2176 38.0385L12.4269 43.1108L7.30127 38.1575L20.083 25.2144" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.9429 22.0915L20.1181 25.1947L12.7827 26.7537L16.8727 22.0356L16.9433 22.0917L16.9429 22.0915Z" fill="#F15A24"/>
|
||||
<path d="M16.9429 22.0915L20.1181 25.1947L12.7827 26.7537L16.8727 22.0356L16.9433 22.0917L16.9429 22.0915Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.935 8.48492C31.6235 11.2433 33.6476 13.7263 36.4588 14.0339C39.2679 14.3402 41.801 12.3527 42.1145 9.59542C42.4269 6.83812 40.3997 4.3524 37.5896 4.0459C34.778 3.74116 32.2468 5.72603 31.935 8.48492Z" fill="#999999"/>
|
||||
<path d="M31.935 8.48492C31.6235 11.2433 33.6476 13.7263 36.4588 14.0339C39.2679 14.3402 41.801 12.3527 42.1145 9.59542C42.4269 6.83812 40.3997 4.3524 37.5896 4.0459C34.778 3.74116 32.2468 5.72603 31.935 8.48492V8.48492Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M41.3785 11.8166C42.3232 10.4624 42.2743 8.43642 42.0847 7.83629C41.8519 7.10124 41.3314 5.90609 40.4233 5.28479L35.6216 9.62417C35.6216 9.62417 39.3118 13.5144 39.4079 13.5272C39.4478 13.5318 40.517 13.0514 41.3785 11.8166Z" fill="#666666"/>
|
||||
<path d="M41.3785 11.8166C42.3232 10.4624 42.2743 8.43642 42.0847 7.83629C41.8519 7.10124 41.3314 5.90609 40.4233 5.28479L35.6216 9.62417C35.6216 9.62417 39.3118 13.5144 39.4079 13.5272C39.4478 13.5318 40.517 13.0514 41.3785 11.8166V11.8166ZM40.4097 8.41737C40.4097 8.41737 40.8714 9.52964 40.2856 10.395L40.4097 8.41737Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.3969 27.339L17.0977 28.2643L20.2964 25.0035" fill="#666666"/>
|
||||
<path d="M17.0654 22.1143L23.8023 28.335M22.396 27.3391L17.0967 28.2644L20.2955 25.0036L22.396 27.3391Z" stroke="white" stroke-width="0.23975" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="53" height="52" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/client/src/assets/images/Postman.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/client/src/assets/images/no_image.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { ChartType, ChartData } from "widgets/ChartWidget";
|
||||
import { ChartType, ChartData, ChartDataPoint } from "widgets/ChartWidget";
|
||||
import styled from "styled-components";
|
||||
import { invisible } from "constants/DefaultTheme";
|
||||
import _ from "lodash";
|
||||
|
|
@ -18,6 +18,7 @@ export interface ChartComponentProps {
|
|||
chartName: string;
|
||||
widgetId: string;
|
||||
isVisible?: boolean;
|
||||
allowHorizontalScroll: boolean;
|
||||
}
|
||||
|
||||
const CanvasContainer = styled.div<ChartComponentProps>`
|
||||
|
|
@ -29,29 +30,49 @@ const CanvasContainer = styled.div<ChartComponentProps>`
|
|||
box-shadow: 0 1px 1px 0 rgba(60,75,100,.14),0 2px 1px -1px rgba(60,75,100,.12),0 1px 3px 0 rgba(60,75,100,.2);
|
||||
position: relative;
|
||||
${props => (!props.isVisible ? invisible : "")};
|
||||
padding: 10px 0 0 0;
|
||||
}`;
|
||||
|
||||
class ChartComponent extends React.Component<ChartComponentProps> {
|
||||
chartInstance = new FusionCharts();
|
||||
getChartType = (chartType: ChartType) => {
|
||||
getChartType = () => {
|
||||
const { chartType, allowHorizontalScroll, chartData } = this.props;
|
||||
const isMSChart = chartData.length > 1;
|
||||
switch (chartType) {
|
||||
case "LINE_CHART":
|
||||
return "line";
|
||||
case "BAR_CHART":
|
||||
return "bar2d";
|
||||
case "PIE_CHART":
|
||||
return "pie2d";
|
||||
case "LINE_CHART":
|
||||
return allowHorizontalScroll
|
||||
? "scrollline2d"
|
||||
: isMSChart
|
||||
? "msline"
|
||||
: "line";
|
||||
case "BAR_CHART":
|
||||
return allowHorizontalScroll
|
||||
? "scrollBar2D"
|
||||
: isMSChart
|
||||
? "msbar2d"
|
||||
: "bar2d";
|
||||
case "COLUMN_CHART":
|
||||
return "column2d";
|
||||
return allowHorizontalScroll
|
||||
? "scrollColumn2D"
|
||||
: isMSChart
|
||||
? "mscolumn2d"
|
||||
: "column2d";
|
||||
case "AREA_CHART":
|
||||
return "area2d";
|
||||
return allowHorizontalScroll
|
||||
? "scrollarea2d"
|
||||
: isMSChart
|
||||
? "msarea"
|
||||
: "area2d";
|
||||
default:
|
||||
return "column2d";
|
||||
return allowHorizontalScroll ? "scrollColumn2D" : "mscolumn2d";
|
||||
}
|
||||
};
|
||||
|
||||
getChartData = (chartData: ChartData[]) => {
|
||||
return chartData.map(item => {
|
||||
const data: ChartDataPoint[] = chartData[0].data;
|
||||
return data.map(item => {
|
||||
return {
|
||||
label: item.x,
|
||||
value: item.y,
|
||||
|
|
@ -59,25 +80,121 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
});
|
||||
};
|
||||
|
||||
getChartCategoriesMutliSeries = (chartData: ChartData[]) => {
|
||||
const categories: string[] = [];
|
||||
for (let index = 0; index < chartData.length; index++) {
|
||||
const data: ChartDataPoint[] = chartData[index].data;
|
||||
for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
|
||||
const category = data[dataIndex].x;
|
||||
if (!categories.includes(category)) {
|
||||
categories.push(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
return categories;
|
||||
};
|
||||
|
||||
getChartCategories = (chartData: ChartData[]) => {
|
||||
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
||||
return categories.map(item => {
|
||||
return {
|
||||
label: item,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
getSeriesChartData = (data: ChartDataPoint[], categories: string[]) => {
|
||||
const dataMap: { [key: string]: string } = {};
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
const item: ChartDataPoint = data[index];
|
||||
dataMap[item.x] = item.y;
|
||||
}
|
||||
return categories.map((category: string) => {
|
||||
return {
|
||||
value: dataMap[category] ? dataMap[category] : null,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
getChartDataset = (chartData: ChartData[]) => {
|
||||
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
||||
return chartData.map((item: ChartData) => {
|
||||
const seriesChartData: object[] = this.getSeriesChartData(
|
||||
item.data,
|
||||
categories,
|
||||
);
|
||||
return {
|
||||
seriesName: item.seriesName,
|
||||
data: seriesChartData,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
getChartConfig = () => {
|
||||
return {
|
||||
caption: this.props.chartName,
|
||||
xAxisName: this.props.xAxisName,
|
||||
yAxisName: this.props.yAxisName,
|
||||
theme: "fusion",
|
||||
captionAlignment: "left",
|
||||
captionHorizontalPadding: 10,
|
||||
alignCaptionWithCanvas: 0,
|
||||
};
|
||||
};
|
||||
|
||||
getChartDataSource = () => {
|
||||
if (
|
||||
this.props.chartData.length === 1 ||
|
||||
this.props.chartType === "PIE_CHART"
|
||||
) {
|
||||
return {
|
||||
chart: this.getChartConfig(),
|
||||
data: this.getChartData(this.props.chartData),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
chart: this.getChartConfig(),
|
||||
categories: [
|
||||
{
|
||||
category: this.getChartCategories(this.props.chartData),
|
||||
},
|
||||
],
|
||||
dataset: this.getChartDataset(this.props.chartData),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
getScrollChartDataSource = () => {
|
||||
const chartConfig = this.getChartConfig();
|
||||
return {
|
||||
chart: {
|
||||
...chartConfig,
|
||||
scrollheight: "10",
|
||||
showvalues: "1",
|
||||
numVisiblePlot: "5",
|
||||
flatScrollBars: "1",
|
||||
},
|
||||
categories: [
|
||||
{
|
||||
category: this.getChartCategories(this.props.chartData),
|
||||
},
|
||||
],
|
||||
dataset: this.getChartDataset(this.props.chartData),
|
||||
};
|
||||
};
|
||||
|
||||
createGraph = () => {
|
||||
const dataSource =
|
||||
this.props.allowHorizontalScroll && this.props.chartType !== "PIE_CHART"
|
||||
? this.getScrollChartDataSource()
|
||||
: this.getChartDataSource();
|
||||
const chartConfig = {
|
||||
type: this.getChartType(this.props.chartType),
|
||||
type: this.getChartType(),
|
||||
renderAt: this.props.widgetId + "chart-container",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
dataFormat: "json",
|
||||
dataSource: {
|
||||
chart: {
|
||||
caption: this.props.chartName,
|
||||
xAxisName: this.props.xAxisName,
|
||||
yAxisName: this.props.yAxisName,
|
||||
theme: "fusion",
|
||||
captionAlignment: "left",
|
||||
captionHorizontalPadding: 10,
|
||||
alignCaptionWithCanvas: 0,
|
||||
},
|
||||
data: this.getChartData(this.props.chartData),
|
||||
},
|
||||
dataSource: dataSource,
|
||||
};
|
||||
this.chartInstance = new FusionCharts(chartConfig);
|
||||
};
|
||||
|
|
@ -91,19 +208,15 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
|
||||
componentDidUpdate(prevProps: ChartComponentProps) {
|
||||
if (!_.isEqual(prevProps, this.props)) {
|
||||
if (prevProps.chartType !== this.props.chartType) {
|
||||
const chartType = this.getChartType(this.props.chartType);
|
||||
this.chartInstance.chartType(chartType);
|
||||
const chartType = this.getChartType();
|
||||
this.chartInstance.chartType(chartType);
|
||||
if (
|
||||
this.props.allowHorizontalScroll &&
|
||||
this.props.chartType !== "PIE_CHART"
|
||||
) {
|
||||
this.chartInstance.setChartData(this.getScrollChartDataSource());
|
||||
} else {
|
||||
this.chartInstance.setChartData({
|
||||
chart: {
|
||||
caption: this.props.chartName,
|
||||
xAxisName: this.props.xAxisName,
|
||||
yAxisName: this.props.yAxisName,
|
||||
theme: "fusion",
|
||||
},
|
||||
data: this.getChartData(this.props.chartData),
|
||||
});
|
||||
this.chartInstance.setChartData(this.getChartDataSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
import React from "react";
|
||||
import { ControlWrapper } from "../../propertyControls/StyledControls";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { Droppable, Draggable } from "react-beautiful-dnd";
|
||||
|
||||
const StyledListWrapper = styled(ControlWrapper)`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding-right: 16px;
|
||||
margin: 8px 0 0 0;
|
||||
`;
|
||||
|
||||
type RenderComponentProps = {
|
||||
index: number;
|
||||
item: {
|
||||
label: string;
|
||||
};
|
||||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, value: string) => void;
|
||||
};
|
||||
|
||||
type DraggableComponentProps = {
|
||||
index: number;
|
||||
draggableId: string;
|
||||
item: {
|
||||
label: string;
|
||||
};
|
||||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, value: string) => void;
|
||||
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
||||
};
|
||||
|
||||
export const DraggableComponent = (props: DraggableComponentProps) => {
|
||||
const {
|
||||
deleteOption,
|
||||
updateOption,
|
||||
item,
|
||||
index,
|
||||
draggableId,
|
||||
renderComponent,
|
||||
} = props;
|
||||
return (
|
||||
<Draggable draggableId={draggableId} key={draggableId} index={index}>
|
||||
{({ innerRef, draggableProps, dragHandleProps }) => (
|
||||
<StyledListWrapper
|
||||
orientation={"HORIZONTAL"}
|
||||
{...draggableProps}
|
||||
{...dragHandleProps}
|
||||
ref={innerRef as React.Ref<HTMLDivElement>}
|
||||
style={{
|
||||
...draggableProps.style,
|
||||
userSelect: "none",
|
||||
position: "static",
|
||||
}}
|
||||
>
|
||||
{renderComponent({ deleteOption, updateOption, item, index })}
|
||||
</StyledListWrapper>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
type DroppableComponentProps = {
|
||||
items: object[];
|
||||
renderComponent: (props: RenderComponentProps) => JSX.Element;
|
||||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, value: string) => void;
|
||||
};
|
||||
|
||||
export const DroppableComponent = (props: DroppableComponentProps) => {
|
||||
const { items } = props;
|
||||
return (
|
||||
<Droppable droppableId="0">
|
||||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
<div ref={innerRef as React.Ref<HTMLDivElement>} {...droppableProps}>
|
||||
{items.map((item: { id: string } & any, index: number) => {
|
||||
return (
|
||||
<DraggableComponent
|
||||
key={index}
|
||||
index={index}
|
||||
item={item}
|
||||
draggableId={item.id}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
};
|
||||
|
|
@ -12,6 +12,8 @@ type DropdownProps = {
|
|||
input: WrappedFieldInputProps;
|
||||
placeholder: string;
|
||||
width?: number;
|
||||
isSearchable?: boolean;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const selectStyles = {
|
||||
|
|
@ -56,6 +58,8 @@ export const BaseDropdown = (props: DropdownProps) => {
|
|||
{...input}
|
||||
width={props.width}
|
||||
onChange={value => input.onChange(value)}
|
||||
isSearchable={props.isSearchable}
|
||||
isDisabled={props.isDisabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import "@uppy/core/dist/style.css";
|
|||
import "@uppy/dashboard/dist/style.css";
|
||||
import "@uppy/webcam/dist/style.css";
|
||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||
// import { DashboardModal } from "@uppy/react";
|
||||
|
||||
class FilePickerComponent extends React.Component<
|
||||
FilePickerComponentProps,
|
||||
|
|
@ -18,7 +17,6 @@ class FilePickerComponent extends React.Component<
|
|||
}
|
||||
|
||||
openModal = () => {
|
||||
// this.setState({ isOpen: true });
|
||||
this.props.uppy.getPlugin("Dashboard").openModal();
|
||||
};
|
||||
|
||||
|
|
@ -36,19 +34,11 @@ class FilePickerComponent extends React.Component<
|
|||
text={label}
|
||||
onClick={this.openModal}
|
||||
/>
|
||||
{/* <DashboardModal
|
||||
open={this.state.isOpen}
|
||||
closeModalOnClickOutside={true}
|
||||
// plugins={["GoogleDrive", "Url", "OneDrive", "Webcam"]}
|
||||
onRequestClose={this.closeModal}
|
||||
uppy={this.props.uppy}
|
||||
/>**/}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
public closeModal() {
|
||||
// this.setState({ isOpen: false });
|
||||
this.props.uppy.getPlugin("Dashboard").closeModal();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import { Icon, Intent } from "@blueprintjs/core";
|
||||
import { IconName } from "@blueprintjs/icons";
|
||||
import { noop } from "utils/AppsmithUtils";
|
||||
export type IconType = IconName | string;
|
||||
|
||||
class IconComponent extends React.Component<IconComponentProps> {
|
||||
render() {
|
||||
return (
|
||||
<Icon
|
||||
style={{
|
||||
cursor:
|
||||
this.props.onClick && !this.props.disabled ? "pointer" : "auto",
|
||||
}}
|
||||
icon={this.props.iconName as IconName}
|
||||
iconSize={this.props.iconSize}
|
||||
intent={this.props.intent}
|
||||
onClick={this.props.disabled ? noop : this.props.onClick}
|
||||
color={this.props.color}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IconComponentProps {
|
||||
iconSize?: number;
|
||||
iconName?: IconType;
|
||||
intent?: Intent;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export default IconComponent;
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
import React from "react";
|
||||
import {
|
||||
withScriptjs,
|
||||
withGoogleMap,
|
||||
GoogleMap,
|
||||
Marker,
|
||||
} from "react-google-maps";
|
||||
import SearchBox from "react-google-maps/lib/components/places/SearchBox";
|
||||
import { MarkerProps } from "widgets/MapWidget";
|
||||
import { ControlIcons } from "icons/ControlIcons";
|
||||
import styled, { AnyStyledComponent } from "styled-components";
|
||||
|
||||
interface MapComponentProps {
|
||||
widgetId: string;
|
||||
isDisabled?: boolean;
|
||||
isVisible?: boolean;
|
||||
enableSearch: boolean;
|
||||
zoomLevel: number;
|
||||
enablePickLocation: boolean;
|
||||
allowZoom: boolean;
|
||||
center: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
markers?: Array<MarkerProps>;
|
||||
enableCreateMarker: boolean;
|
||||
updateCenter: (lat: number, lng: number) => void;
|
||||
saveMarker: (lat: number, lng: number) => void;
|
||||
selectMarker: (lat: number, lng: number, title: string) => void;
|
||||
}
|
||||
|
||||
const StyledInput = styled.input`
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
width: 240px;
|
||||
height: 32px;
|
||||
margin-top: 27px;
|
||||
padding: 0 12px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
text-overflow: ellipses;
|
||||
`;
|
||||
type PickMyLocationProps = {
|
||||
allowZoom: boolean;
|
||||
};
|
||||
|
||||
const PickMyLocationWrapper = styled.div<PickMyLocationProps>`
|
||||
position: absolute;
|
||||
bottom: ${props => (props.allowZoom ? 110 : 20)}px;
|
||||
right: -95px;
|
||||
width: 140px;
|
||||
`;
|
||||
|
||||
const StyledPickMyLocationIcon = styled(
|
||||
ControlIcons.PICK_MY_LOCATION_CONTROL as AnyStyledComponent,
|
||||
)`
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const MyMapComponent = withScriptjs(
|
||||
withGoogleMap((props: any) => (
|
||||
<GoogleMap
|
||||
options={{
|
||||
zoomControl: props.allowZoom,
|
||||
fullscreenControl: false,
|
||||
mapTypeControl: false,
|
||||
scrollwheel: false,
|
||||
}}
|
||||
zoom={props.zoom}
|
||||
center={props.center}
|
||||
onClick={e => {
|
||||
if (props.enableCreateMarker) {
|
||||
props.saveMarker(e.latLng.lat(), e.latLng.lng());
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.enableSearch && (
|
||||
<SearchBox
|
||||
controlPosition={2}
|
||||
onPlacesChanged={props.onPlacesChanged}
|
||||
ref={props.onSearchBoxMounted}
|
||||
>
|
||||
<StyledInput type="text" placeholder="Enter location to search" />
|
||||
</SearchBox>
|
||||
)}
|
||||
{props.markers.map((marker: any, index: number) => (
|
||||
<Marker
|
||||
key={index}
|
||||
title={marker.title}
|
||||
position={{ lat: marker.lat, lng: marker.lng }}
|
||||
clickable
|
||||
onClick={e => {
|
||||
props.selectMarker(marker.lat, marker.lng, marker.title);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{props.enablePickLocation && (
|
||||
<PickMyLocationWrapper
|
||||
onClick={props.getUserLocation}
|
||||
title="Pick My Location"
|
||||
allowZoom={props.allowZoom}
|
||||
>
|
||||
<StyledPickMyLocationIcon />
|
||||
</PickMyLocationWrapper>
|
||||
)}
|
||||
</GoogleMap>
|
||||
)),
|
||||
);
|
||||
|
||||
class MapComponent extends React.Component<MapComponentProps> {
|
||||
private searchBox = React.createRef<SearchBox>();
|
||||
|
||||
onSearchBoxMounted = (ref: any) => {
|
||||
this.searchBox = ref;
|
||||
};
|
||||
onPlacesChanged = () => {
|
||||
const node: any = this.searchBox;
|
||||
if (node) {
|
||||
const places: any = node.getPlaces();
|
||||
const location = places[0].geometry.location;
|
||||
const lat = location.lat();
|
||||
const lng = location.lng();
|
||||
this.props.updateCenter(lat, lng);
|
||||
}
|
||||
};
|
||||
|
||||
getUserLocation = () => {
|
||||
if ("geolocation" in navigator) {
|
||||
return navigator.geolocation.getCurrentPosition(data => {
|
||||
const {
|
||||
coords: { latitude: lat, longitude: lng },
|
||||
} = data;
|
||||
this.props.saveMarker(lat, lng);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const zoom = Math.floor(this.props.zoomLevel / 5);
|
||||
return (
|
||||
<MyMapComponent
|
||||
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyC2H_twoNbEKMm9Q0nYAh7715Dplg2asCI&v=3.exp&libraries=geometry,drawing,places"
|
||||
loadingElement={<div style={{ height: `100%` }} />}
|
||||
containerElement={<div style={{ height: `100%` }} />}
|
||||
mapElement={<div style={{ height: `100%` }} />}
|
||||
{...this.props}
|
||||
zoom={zoom}
|
||||
onPlacesChanged={this.onPlacesChanged}
|
||||
onSearchBoxMounted={this.onSearchBoxMounted}
|
||||
getUserLocation={this.getUserLocation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MapComponent;
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import React from "react";
|
||||
import { Editor } from "@tinymce/tinymce-react";
|
||||
import styled from "styled-components";
|
||||
require("tinymce/tinymce");
|
||||
require("tinymce/themes/silver");
|
||||
|
||||
const StyledRTEditor = styled.div`
|
||||
&& {
|
||||
width: 100%;
|
||||
|
|
@ -28,6 +25,7 @@ export const RichtextEditorComponent = (
|
|||
return (
|
||||
<StyledRTEditor>
|
||||
<Editor
|
||||
apiKey="joninoujfadq0q38n0us4tu89d2pawl1jxk4q09vcvxyfqrl"
|
||||
value={props.defaultValue}
|
||||
disabled={props.isDisabled}
|
||||
init={{
|
||||
|
|
@ -41,7 +39,7 @@ export const RichtextEditorComponent = (
|
|||
"insertdatetime media table paste code help",
|
||||
],
|
||||
toolbar:
|
||||
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
|
||||
"undo redo | formatselect | bold italic backcolor forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
|
||||
}}
|
||||
onEditorChange={(content: any) => props.onValueChange(content)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import React from "react";
|
||||
import { ControlIcons } from "icons/ControlIcons";
|
||||
import { AnyStyledComponent } from "styled-components";
|
||||
import styled from "constants/DefaultTheme";
|
||||
|
||||
const StyledIncreaseIcon = styled(
|
||||
ControlIcons.INCREASE_CONTROL as AnyStyledComponent,
|
||||
)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
svg {
|
||||
path {
|
||||
fill: ${props => props.theme.colors.paneSectionLabel};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDecreaseIcon = styled(
|
||||
ControlIcons.DECREASE_CONTROL as AnyStyledComponent,
|
||||
)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
svg {
|
||||
path {
|
||||
fill: ${props => props.theme.colors.paneSectionLabel};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StepWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background: #121518;
|
||||
border-radius: 4px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
width: calc(100% - 80px);
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background: #23292e;
|
||||
font-size: 14px;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
text-align: center;
|
||||
letter-spacing: 1.44px;
|
||||
`;
|
||||
|
||||
interface StepComponentProps {
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
steps: number;
|
||||
displayFormat: (value: number) => string;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export const StepComponent = (props: StepComponentProps) => {
|
||||
function decrease() {
|
||||
if (props.value < props.min) {
|
||||
return;
|
||||
}
|
||||
const value = props.value - props.steps;
|
||||
props.onChange(value);
|
||||
}
|
||||
function increase() {
|
||||
if (props.value > props.max) {
|
||||
return;
|
||||
}
|
||||
const value = props.value + props.steps;
|
||||
props.onChange(value);
|
||||
}
|
||||
return (
|
||||
<StepWrapper>
|
||||
<StyledDecreaseIcon height={2} width={12} onClick={decrease} />
|
||||
<InputWrapper>{props.displayFormat(props.value)}</InputWrapper>
|
||||
<StyledIncreaseIcon height={12} width={12} onClick={increase} />
|
||||
</StepWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepComponent;
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
interface TabsComponentProps {
|
||||
isVisible?: boolean;
|
||||
tabs?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}>;
|
||||
selectedTabId?: string;
|
||||
onTabChange: (tabId: string) => void;
|
||||
}
|
||||
|
||||
const TabsContainer = styled.div`
|
||||
&& {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
type TabProps = {
|
||||
selected?: boolean;
|
||||
};
|
||||
|
||||
const StyledTab = styled.div`
|
||||
height: 32px;
|
||||
border-bottom: 1px solid;
|
||||
border-color: ${props => props.theme.colors.bodyBG};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledText = styled.div<TabProps>`
|
||||
white-space: nowrap;
|
||||
background: ${props => props.theme.colors.builderBodyBG};
|
||||
color: ${props => props.theme.colors.menuIconColorInactive};
|
||||
font-size: ${props => props.theme.fontSizes[3]}px;
|
||||
line-height: 32px;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: ${props => (props.selected ? props.theme.shadows[2] : "")};
|
||||
border-bottom: ${props => (props.selected ? "none" : "1px solid")};
|
||||
border-color: ${props => props.theme.colors.bodyBG};
|
||||
&:hover {
|
||||
background: ${props =>
|
||||
props.selected
|
||||
? props.theme.colors.textOnDarkBG
|
||||
: props.theme.colors.hover};
|
||||
box-shadow: ${props => (props.selected ? "" : props.theme.shadows[3])};
|
||||
}
|
||||
`;
|
||||
|
||||
class TabsComponent extends React.Component<TabsComponentProps> {
|
||||
selectTab = (tab: { id: string; label: string }) => {
|
||||
this.props.onTabChange(tab.id);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<TabsContainer>
|
||||
{this.props.tabs &&
|
||||
this.props.tabs.map((tab, index) => (
|
||||
<StyledText
|
||||
onClick={this.selectTab.bind(this, tab)}
|
||||
selected={this.props.selectedTabId === tab.id}
|
||||
key={index}
|
||||
>
|
||||
{tab.label}
|
||||
</StyledText>
|
||||
))}
|
||||
<StyledTab></StyledTab>
|
||||
</TabsContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TabsComponent;
|
||||
|
|
@ -45,7 +45,6 @@ const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
|
|||
: props.theme.colors.secondary};
|
||||
border-radius: 4px;
|
||||
font-weight: ${props => props.theme.fontWeights[2]};
|
||||
font-family: "DM Sans";
|
||||
outline: none;
|
||||
&.bp3-button {
|
||||
padding: 0px 10px;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ type CloseButtonProps = {
|
|||
color: Color;
|
||||
size: number;
|
||||
onClick: React.MouseEventHandler;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledButton = styled(Button)<CloseButtonProps>`
|
||||
|
|
@ -22,5 +23,12 @@ const StyledButton = styled(Button)<CloseButtonProps>`
|
|||
`;
|
||||
|
||||
export const CloseButton = (props: CloseButtonProps) => {
|
||||
return <StyledButton {...props} rightIcon="cross" minimal />;
|
||||
return (
|
||||
<StyledButton
|
||||
className={props.className}
|
||||
{...props}
|
||||
rightIcon="cross"
|
||||
minimal
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import moment from "moment-timezone";
|
|||
import "../../../../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime.css";
|
||||
import { DatePickerType } from "widgets/DatePickerWidget";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const StyledControlGroup = styled(ControlGroup)`
|
||||
|
|
@ -45,19 +46,34 @@ const StyledControlGroup = styled(ControlGroup)`
|
|||
}
|
||||
&&& {
|
||||
input {
|
||||
border: 1px solid #a1acb3;
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${Colors.HIT_GRAY};
|
||||
border-radius: ${props => props.theme.radii[1]}px;
|
||||
box-shadow: none;
|
||||
color: #2e3d49;
|
||||
font-size: 14px;
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
font-size: ${props => props.theme.fontSizes[3]}px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class DatePickerComponent extends React.Component<DatePickerComponentProps> {
|
||||
render() {
|
||||
const now = moment();
|
||||
const year = now.get("year");
|
||||
const month = now.get("month");
|
||||
const date = now.get("date");
|
||||
const minDate = now.clone().set({ month, date: date - 1, year: year - 20 });
|
||||
const maxDate = now.clone().set({ month, date: date + 1, year: year + 20 });
|
||||
const selectedDate = new Date(
|
||||
new Date(this.props.selectedDate || "").getTime() +
|
||||
this.getOffset(new Date(this.props.selectedDate || "")),
|
||||
);
|
||||
return (
|
||||
<StyledControlGroup fill>
|
||||
<StyledControlGroup
|
||||
fill
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{this.props.label && (
|
||||
<Label
|
||||
className={
|
||||
|
|
@ -77,18 +93,14 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
|
|||
placeholder={this.props.dateFormat}
|
||||
disabled={this.props.isDisabled}
|
||||
showActionsBar={true}
|
||||
timePickerProps={
|
||||
this.props.enableTimePicker
|
||||
? {
|
||||
useAmPm: true,
|
||||
value: this.props.selectedDate,
|
||||
showArrowButtons: true,
|
||||
}
|
||||
: undefined
|
||||
timePrecision={
|
||||
this.props.enableTimePicker ? TimePrecision.MINUTE : undefined
|
||||
}
|
||||
closeOnSelection={true}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
value={this.props.selectedDate}
|
||||
value={selectedDate}
|
||||
maxDate={maxDate.toDate()}
|
||||
minDate={minDate.toDate()}
|
||||
/>
|
||||
) : (
|
||||
<DateRangeInput
|
||||
|
|
@ -106,30 +118,28 @@ class DatePickerComponent extends React.Component<DatePickerComponentProps> {
|
|||
);
|
||||
}
|
||||
|
||||
getOffset = (date: Date) => {
|
||||
const timezone = this.props.timezone || moment.tz.guess();
|
||||
return moment.tz.zone(timezone)!.utcOffset(date.getTime()) * 60 * 1000;
|
||||
};
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
let dateFormat = "DD/MM/YYYY";
|
||||
if (this.props.enableTimePicker) {
|
||||
dateFormat = "DD/MM/YYYY HH:mm";
|
||||
}
|
||||
if (this.props.timezone) {
|
||||
return moment(date)
|
||||
.tz(this.props.timezone)
|
||||
.format(dateFormat);
|
||||
}
|
||||
return moment(date).format(dateFormat);
|
||||
};
|
||||
|
||||
parseDate = (dateStr: string): Date => {
|
||||
if (this.props.timezone) {
|
||||
return moment(dateStr)
|
||||
.tz(this.props.timezone)
|
||||
.toDate();
|
||||
}
|
||||
return moment(dateStr).toDate();
|
||||
};
|
||||
|
||||
onDateSelected = (selectedDate: Date) => {
|
||||
this.props.onDateSelected(selectedDate);
|
||||
const dateValue = new Date(
|
||||
selectedDate.getTime() - this.getOffset(selectedDate),
|
||||
).getTime();
|
||||
this.props.onDateSelected(new Date(dateValue));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
|
||||
import { Icon, Intent } from "@blueprintjs/core";
|
||||
import { IconName } from "@blueprintjs/icons";
|
||||
class IconComponent extends React.Component<IconComponentProps> {
|
||||
render() {
|
||||
return (
|
||||
<Icon
|
||||
icon={this.props.icon}
|
||||
iconSize={this.props.iconSize}
|
||||
intent={this.props.intent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IconComponentProps extends ComponentProps {
|
||||
iconSize?: number;
|
||||
icon?: IconName;
|
||||
intent?: Intent;
|
||||
ellipsize?: boolean;
|
||||
}
|
||||
|
||||
export default IconComponent;
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import React, { ReactNode, RefObject, useRef, useEffect } from "react";
|
||||
import { Overlay, Classes } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
import { ControlIcons } from "icons/ControlIcons";
|
||||
import { getCanvasClassName } from "utils/generators";
|
||||
const CloseModalControl = ControlIcons.CLOSE_CONTROL;
|
||||
const Container = styled.div<{
|
||||
width: number;
|
||||
height: number;
|
||||
|
|
@ -44,13 +42,6 @@ const Content = styled.div<{
|
|||
height: ${props => props.height}px;
|
||||
`;
|
||||
|
||||
const CloseModalTrigger = styled(CloseModalControl)`
|
||||
position: absolute;
|
||||
right: -30px;
|
||||
top: 0px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export type ModalComponentProps = {
|
||||
isOpen: boolean;
|
||||
onClose: (e: any) => void;
|
||||
|
|
@ -84,10 +75,6 @@ export const ModalComponent = (props: ModalComponentProps) => {
|
|||
enforceFocus={false}
|
||||
>
|
||||
<div>
|
||||
<CloseModalTrigger
|
||||
onClick={props.onClose}
|
||||
className="t--modal-close"
|
||||
/>
|
||||
<Content
|
||||
scroll={props.scrollContents}
|
||||
className={`${getCanvasClassName()} ${props.className}`}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,14 @@ import {
|
|||
ColumnMenuItemModel,
|
||||
PageSettingsModel,
|
||||
PagerComponent,
|
||||
Toolbar,
|
||||
PdfExport,
|
||||
ExcelExport,
|
||||
Search,
|
||||
RowDataBoundEventArgs,
|
||||
} from "@syncfusion/ej2-react-grids";
|
||||
import { getValue } from "@syncfusion/ej2-base";
|
||||
import { ClickEventArgs } from "@syncfusion/ej2-navigations";
|
||||
import React, { useRef, MutableRefObject, useEffect, memo } from "react";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
|
||||
|
|
@ -28,6 +35,16 @@ import {
|
|||
AUTOFIT_COLUMN,
|
||||
} from "constants/messages";
|
||||
|
||||
import "@syncfusion/ej2-base/styles/material.css";
|
||||
import "@syncfusion/ej2-buttons/styles/material.css";
|
||||
import "@syncfusion/ej2-calendars/styles/material.css";
|
||||
import "@syncfusion/ej2-dropdowns/styles/material.css";
|
||||
import "@syncfusion/ej2-inputs/styles/material.css";
|
||||
import "@syncfusion/ej2-navigations/styles/material.css";
|
||||
import "@syncfusion/ej2-popups/styles/material.css";
|
||||
import "@syncfusion/ej2-splitbuttons/styles/material.css";
|
||||
import "@syncfusion/ej2-react-grids/styles/material.css";
|
||||
|
||||
export interface TableComponentProps {
|
||||
data: object[];
|
||||
columns: ColumnModel[];
|
||||
|
|
@ -47,6 +64,10 @@ export interface TableComponentProps {
|
|||
updateHiddenColumns: Function;
|
||||
resetSelectedRowIndex: Function;
|
||||
selectedRowIndex: number;
|
||||
id: string;
|
||||
exportCsv?: boolean;
|
||||
exportExcel?: boolean;
|
||||
exportPDF?: boolean;
|
||||
}
|
||||
|
||||
const StyledGridComponent = styled(GridComponent)`
|
||||
|
|
@ -156,7 +177,6 @@ const TableComponent = memo(
|
|||
index =
|
||||
index + (pageSettings.currentPage - 1) * pageSettings.pageSize;
|
||||
}
|
||||
|
||||
props.onRowClick(selectedrecords[0], index);
|
||||
}
|
||||
}
|
||||
|
|
@ -185,6 +205,13 @@ const TableComponent = memo(
|
|||
}
|
||||
}
|
||||
|
||||
function rowDataBound(args: RowDataBoundEventArgs) {
|
||||
const color = getValue("_color", args.data);
|
||||
if (args.row && color) {
|
||||
(args.row as any).style.backgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
function columnMenuOpen(args: ColumnMenuOpenEventArgs) {
|
||||
for (const item of args.items) {
|
||||
if (item.text) {
|
||||
|
|
@ -206,17 +233,35 @@ const TableComponent = memo(
|
|||
);
|
||||
}
|
||||
|
||||
const handleToolbarClick = (args: ClickEventArgs) => {
|
||||
if (grid.current && args.item.id === `${props.id}_excelexport`) {
|
||||
grid.current.excelExport({ fileName: `${props.id}.xlsx` });
|
||||
}
|
||||
if (grid.current && args.item.id === `${props.id}_csvexport`) {
|
||||
grid.current.csvExport({ fileName: `${props.id}.csv` });
|
||||
}
|
||||
if (grid.current && args.item.id === `${props.id}_pdfexport`) {
|
||||
grid.current.pdfExport({ fileName: `${props.id}.pdf` });
|
||||
}
|
||||
};
|
||||
const handleResizeStart = (args: any) => {
|
||||
args.e.stopPropagation();
|
||||
};
|
||||
|
||||
const toolbarOptions: string[] = [];
|
||||
if (!!props.exportCsv) toolbarOptions.push("CsvExport");
|
||||
if (!!props.exportExcel) toolbarOptions.push("ExcelExport");
|
||||
if (!!props.exportPDF) toolbarOptions.push("PdfExport");
|
||||
|
||||
return (
|
||||
<TableContainer className={props.isLoading ? Classes.SKELETON : ""}>
|
||||
<StyledGridComponent
|
||||
toolbarClick={handleToolbarClick}
|
||||
created={handleCreated}
|
||||
destroy={handleDestroy}
|
||||
selectionSettings={settings}
|
||||
dataSource={props.data}
|
||||
id={props.id}
|
||||
columnMenuClick={columnMenuClick}
|
||||
dataBound={() => {
|
||||
if (pager.current) {
|
||||
|
|
@ -237,9 +282,25 @@ const TableComponent = memo(
|
|||
showColumnMenu={true}
|
||||
commandClick={onCommandClick}
|
||||
columnMenuOpen={columnMenuOpen}
|
||||
rowDataBound={rowDataBound}
|
||||
toolbar={!!toolbarOptions.length && toolbarOptions}
|
||||
allowPdfExport
|
||||
allowExcelExport
|
||||
// enableVirtualization
|
||||
>
|
||||
<Inject
|
||||
services={[Resize, Page, Reorder, ColumnMenu, CommandColumn]}
|
||||
services={[
|
||||
Resize,
|
||||
Page,
|
||||
Reorder,
|
||||
ColumnMenu,
|
||||
CommandColumn,
|
||||
Toolbar,
|
||||
ExcelExport,
|
||||
PdfExport,
|
||||
Search,
|
||||
// VirtualScroll,
|
||||
]}
|
||||
/>
|
||||
<ColumnsDirective>
|
||||
{props.columns.map(col => {
|
||||
|
|
@ -302,7 +363,10 @@ const TableComponent = memo(
|
|||
JSON.stringify(prevProps.columnActions) ||
|
||||
nextProps.serverSidePaginationEnabled !==
|
||||
prevProps.serverSidePaginationEnabled ||
|
||||
nextProps.pageNo !== prevProps.pageNo;
|
||||
nextProps.pageNo !== prevProps.pageNo ||
|
||||
nextProps.exportCsv !== prevProps.exportCsv ||
|
||||
nextProps.exportExcel !== prevProps.exportExcel ||
|
||||
nextProps.exportPDF !== prevProps.exportPDF;
|
||||
|
||||
return !propsNotEqual;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
|||
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
||||
import CodeEditor from "components/editorComponents/CodeEditor";
|
||||
import { getActionResponses } from "selectors/entitiesSelector";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const ResponseWrapper = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -30,7 +31,7 @@ const ResponseMetaInfo = styled.div`
|
|||
|
||||
const StatusCodeText = styled(BaseText)<{ code: string }>`
|
||||
color: ${props =>
|
||||
props.code.match(/2\d\d/) ? props.theme.colors.primary : "red"};
|
||||
props.code.match(/2\d\d/) ? props.theme.colors.primary : Colors.RED};
|
||||
`;
|
||||
|
||||
const TableWrapper = styled.div`
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import styled from "styled-components";
|
|||
import { useDrop, XYCoord, DropTargetMonitor } from "react-dnd";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import {
|
||||
widgetOperationParams,
|
||||
noCollision,
|
||||
|
|
@ -20,6 +19,7 @@ import { EditorContext } from "components/editorComponents/EditorContextProvider
|
|||
import {
|
||||
MAIN_CONTAINER_WIDGET_ID,
|
||||
GridDefaults,
|
||||
WidgetTypes,
|
||||
} from "constants/WidgetConstants";
|
||||
import { calculateDropTargetRows } from "./DropTargetUtils";
|
||||
import DragLayerComponent from "./DragLayerComponent";
|
||||
|
|
@ -147,7 +147,7 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
|
|||
const isChildResizing = !!isResizing && isChildFocused;
|
||||
// Make this component a drop target
|
||||
const [{ isExactlyOver }, drop] = useDrop({
|
||||
accept: Object.values(WidgetFactory.getWidgetTypes()),
|
||||
accept: Object.values(WidgetTypes),
|
||||
options: {
|
||||
arePropsEqual: () => {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,633 +0,0 @@
|
|||
import React, { ChangeEvent } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import {
|
||||
ReduxActionWithoutPayload,
|
||||
ReduxAction,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import _ from "lodash";
|
||||
import { ControlWrapper } from "components/propertyControls/StyledControls";
|
||||
import { InputText } from "components/propertyControls/InputTextControl";
|
||||
import StyledDropdown from "components/editorComponents/DynamicActionSelectorDropdown";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import {
|
||||
getModalDropdownList,
|
||||
getNextModalName,
|
||||
} from "selectors/widgetSelectors";
|
||||
import { getActionsForCurrentPage } from "selectors/entitiesSelector";
|
||||
import { KeyValueComponent } from "components/propertyControls/KeyValueComponent";
|
||||
import { createModalAction } from "actions/widgetActions";
|
||||
|
||||
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
|
||||
const ACTION_ANONYMOUS_FUNC_REGEX = /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
|
||||
|
||||
const ALERT_STYLE_OPTIONS = [
|
||||
{ label: "Info", value: "'info'", id: "info" },
|
||||
{ label: "Success", value: "'success'", id: "success" },
|
||||
{ label: "Error", value: "'error'", id: "error" },
|
||||
{ label: "Warning", value: "'warning'", id: "warning" },
|
||||
];
|
||||
|
||||
type ValueType = string | DropdownOption[];
|
||||
|
||||
type ValueChangeHandler = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
) => string;
|
||||
type ActionCreatorArgumentConfig = {
|
||||
label: string;
|
||||
field: string;
|
||||
create?: {
|
||||
text: string;
|
||||
action: (...args: any) => ReduxAction<any>;
|
||||
};
|
||||
valueChangeHandler: ValueChangeHandler;
|
||||
getSelectedValue: (value: string, returnArguments: boolean) => ValueType;
|
||||
};
|
||||
|
||||
interface ActionCreatorDropdownOption extends DropdownOption {
|
||||
arguments: ActionCreatorArgumentConfig[];
|
||||
}
|
||||
|
||||
const handleTopLevelFuncUpdate: ValueChangeHandler = (
|
||||
value: ValueType,
|
||||
): string => {
|
||||
return value === "none" ? "" : `{{${value}()}}`;
|
||||
};
|
||||
|
||||
const handleApiArgSelect = (
|
||||
changeValue: string,
|
||||
currentValue: string,
|
||||
label: "onSuccess" | "onError",
|
||||
): string => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = [...matches[0][2].matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
|
||||
let successArg = args[0] ? args[0][0] : "() => {}";
|
||||
let errorArg = args[1] ? args[1][0] : "() => {}";
|
||||
if (label === "onSuccess") {
|
||||
successArg = changeValue.endsWith(")")
|
||||
? `() => ${changeValue}`
|
||||
: `() => ${changeValue}()`;
|
||||
}
|
||||
if (label === "onError") {
|
||||
errorArg = changeValue.endsWith(")")
|
||||
? `() => ${changeValue}`
|
||||
: `() => ${changeValue}()`;
|
||||
}
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${successArg}, ${errorArg})}}`,
|
||||
);
|
||||
};
|
||||
|
||||
const handlePageNameArgSelect = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
) => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[0] = `${changeValue}`;
|
||||
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
};
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const handlePageParamsArgSelect = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
) => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",").slice(0, 2);
|
||||
const paramsObject: Record<string, string> = {};
|
||||
(changeValue as DropdownOption[]).forEach(pageParam => {
|
||||
paramsObject[pageParam.label] = pageParam.value;
|
||||
});
|
||||
args[1] = JSON.stringify(paramsObject);
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const handleTextArgChange = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
): string => {
|
||||
return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1('${changeValue}')}}`);
|
||||
};
|
||||
|
||||
const handleAlertTextChange = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
): string => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[0] = `'${changeValue}'`;
|
||||
const result = currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const handleAlertTypeChange = (
|
||||
changeValue: ValueType,
|
||||
currentValue: string,
|
||||
): string => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[1] = changeValue as string;
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
};
|
||||
|
||||
const getApiArgumentValue = (
|
||||
value: string,
|
||||
label: "onSuccess" | "onError",
|
||||
returnSubArguments = false,
|
||||
): string => {
|
||||
let selectedValue = "none";
|
||||
let selectedValueArgs = "";
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const argIndex = label === "onSuccess" ? 0 : 1;
|
||||
const args = [...funcArgs.matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
|
||||
const selectedArg = args[argIndex];
|
||||
if (selectedArg && selectedArg.length) {
|
||||
selectedValue = selectedArg[1];
|
||||
selectedValueArgs = selectedArg[2];
|
||||
}
|
||||
}
|
||||
if (returnSubArguments) return selectedValueArgs;
|
||||
return selectedValue;
|
||||
};
|
||||
|
||||
const getPageNameSelectedValue = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
return matches.length ? matches[0][2].split(",")[0] : "none";
|
||||
};
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const getPageParamsSelectedValue = (value: ValueType) => {
|
||||
const match = getPageSelectedParamsObject(value as string);
|
||||
const keyPairs: DropdownOption[] = [];
|
||||
Object.keys(match).forEach((key: string) => {
|
||||
keyPairs.push({
|
||||
label: key,
|
||||
value: match[key],
|
||||
});
|
||||
});
|
||||
return keyPairs;
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const getPageSelectedParamsObject = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
let match: Record<string, string> = {};
|
||||
|
||||
if (matches.length) {
|
||||
try {
|
||||
match = JSON.parse(
|
||||
matches[0][2].substring(
|
||||
matches[0][2].indexOf(",") + 1,
|
||||
matches[0][2].length,
|
||||
),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
return match;
|
||||
};
|
||||
|
||||
export const getTextArgValue = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const stringValue = matches[0][2];
|
||||
return stringValue.substring(1, stringValue.length - 1);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const getAlertTextValue = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const arg = funcArgs.split(",")[0];
|
||||
return arg.substring(1, arg.length - 1);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const getAlertTypeValue = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const arg = funcArgs.split(",")[1];
|
||||
return arg ? arg.trim() : "'primary'";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
export const PropertyPaneActionDropdownOptions: ActionCreatorDropdownOption[] = [
|
||||
{
|
||||
label: "No action",
|
||||
value: "none",
|
||||
id: "none",
|
||||
arguments: [],
|
||||
},
|
||||
{
|
||||
label: "Call API",
|
||||
value: "api",
|
||||
id: "api",
|
||||
arguments: [
|
||||
{
|
||||
label: "onSuccess",
|
||||
field: "ACTION_SELECTOR_FIELD",
|
||||
valueChangeHandler: (changeValue, currentValue) =>
|
||||
handleApiArgSelect(changeValue as string, currentValue, "onSuccess"),
|
||||
getSelectedValue: (value: string, returnArgs = false) =>
|
||||
getApiArgumentValue(value, "onSuccess", returnArgs),
|
||||
},
|
||||
{
|
||||
label: "onError",
|
||||
field: "ACTION_SELECTOR_FIELD",
|
||||
valueChangeHandler: (changeValue, currentValue) =>
|
||||
handleApiArgSelect(changeValue as string, currentValue, "onError"),
|
||||
getSelectedValue: (value: string, returnArgs = false) =>
|
||||
getApiArgumentValue(value, "onError", returnArgs),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Show Modal",
|
||||
value: "showModal",
|
||||
id: "showModal",
|
||||
arguments: [
|
||||
{
|
||||
label: "Modal Name",
|
||||
field: "MODAL_SELECTOR_FIELD",
|
||||
create: {
|
||||
text: "Modal",
|
||||
action: createModalAction,
|
||||
},
|
||||
valueChangeHandler: handlePageNameArgSelect,
|
||||
getSelectedValue: getPageNameSelectedValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Close Modal",
|
||||
value: "closeModal",
|
||||
id: "closeModal",
|
||||
arguments: [
|
||||
{
|
||||
label: "Modal Name",
|
||||
field: "MODAL_SELECTOR_FIELD",
|
||||
valueChangeHandler: handlePageNameArgSelect,
|
||||
getSelectedValue: getPageNameSelectedValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Navigate to Page",
|
||||
value: "navigateTo",
|
||||
id: "navigateTo",
|
||||
arguments: [
|
||||
{
|
||||
label: "pageName",
|
||||
field: "PAGE_SELECTOR_FIELD",
|
||||
valueChangeHandler: handlePageNameArgSelect,
|
||||
getSelectedValue: getPageNameSelectedValue,
|
||||
},
|
||||
// {
|
||||
// label: "params",
|
||||
// field: "KEY_VALUE_FIELD",
|
||||
// valueChangeHandler: handlePageParamsArgSelect,
|
||||
// getSelectedValue: getPageParamsSelectedValue,
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Navigate to URL",
|
||||
value: "navigateToUrl",
|
||||
id: "navigateToUrl",
|
||||
arguments: [
|
||||
{
|
||||
label: "URL",
|
||||
field: "TEXT_FIELD",
|
||||
valueChangeHandler: handleTextArgChange,
|
||||
getSelectedValue: getTextArgValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Show Alert",
|
||||
value: "showAlert",
|
||||
id: "showAlert",
|
||||
arguments: [
|
||||
{
|
||||
label: "text",
|
||||
field: "TEXT_FIELD",
|
||||
valueChangeHandler: handleAlertTextChange,
|
||||
getSelectedValue: getAlertTextValue,
|
||||
},
|
||||
{
|
||||
label: "type",
|
||||
field: "ALERT_TYPE_SELECTOR_FIELD",
|
||||
valueChangeHandler: handleAlertTypeChange,
|
||||
getSelectedValue: getAlertTypeValue,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
type ReduxStateProps = {
|
||||
actions: ActionDataState;
|
||||
pageNameDropdown: DropdownOption[];
|
||||
modalDropdown?: DropdownOption[];
|
||||
nextModalName: string;
|
||||
dispatchAction: (payload: ReduxActionWithoutPayload) => void;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
onValueChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
class DynamicActionCreator extends React.Component<Props & ReduxStateProps> {
|
||||
getTopLevelFuncValue = (value: string) => {
|
||||
let matches: any[] = [];
|
||||
if (value) {
|
||||
matches = value ? [...value.matchAll(ACTION_TRIGGER_REGEX)] : [];
|
||||
}
|
||||
let mainFuncSelectedValue = "none";
|
||||
if (matches.length) {
|
||||
mainFuncSelectedValue = matches[0][1] || "none";
|
||||
}
|
||||
return mainFuncSelectedValue;
|
||||
};
|
||||
|
||||
handleValueUpdate = (
|
||||
updateValueOrEvent: ValueType | ChangeEvent<HTMLTextAreaElement>,
|
||||
valueUpdateHandler: ValueChangeHandler,
|
||||
) => {
|
||||
const { value, onValueChange } = this.props;
|
||||
let updateValue = updateValueOrEvent;
|
||||
if (
|
||||
typeof updateValueOrEvent !== "string" &&
|
||||
(updateValueOrEvent as any).target
|
||||
) {
|
||||
updateValue = (updateValueOrEvent as any).target.value;
|
||||
}
|
||||
const newValue = valueUpdateHandler(updateValue as ValueType, value);
|
||||
onValueChange(newValue);
|
||||
};
|
||||
|
||||
renderSubArgumentFields = (
|
||||
argValue: string,
|
||||
allOptions: ActionCreatorDropdownOption[],
|
||||
parentChangeHandler: (
|
||||
updateValueOrEvent: ValueType | ChangeEvent<HTMLTextAreaElement>,
|
||||
valueUpdateHandler: ValueChangeHandler,
|
||||
) => void,
|
||||
argumentConfig: ActionCreatorArgumentConfig,
|
||||
) => {
|
||||
const subArgValue = argumentConfig.getSelectedValue(argValue, false);
|
||||
const subArguments = argumentConfig.getSelectedValue(argValue, true);
|
||||
let selectedOption = allOptions[0];
|
||||
allOptions
|
||||
.filter(o => o.value !== "api")
|
||||
.forEach(o => {
|
||||
if (o.value === subArgValue) {
|
||||
selectedOption = o;
|
||||
}
|
||||
});
|
||||
const handleValueUpdate = (
|
||||
updateValueOrEvent: ValueType | ChangeEvent<HTMLTextAreaElement>,
|
||||
valueUpdateHandler: ValueChangeHandler,
|
||||
) => {
|
||||
let updateValue = updateValueOrEvent;
|
||||
if (
|
||||
typeof updateValueOrEvent !== "string" &&
|
||||
(updateValueOrEvent as any).target
|
||||
) {
|
||||
updateValue = (updateValueOrEvent as any).target.value;
|
||||
}
|
||||
const tempArg = `{{${subArgValue}${subArguments}}}`;
|
||||
const newValue = valueUpdateHandler(updateValue as ValueType, tempArg);
|
||||
const newArgValue = newValue.substring(2, newValue.length - 2);
|
||||
parentChangeHandler(newArgValue, argumentConfig.valueChangeHandler);
|
||||
};
|
||||
const subFunctionCall = `{{${subArgValue}${subArguments}}}`;
|
||||
return this.renderActionArgumentFields(
|
||||
subFunctionCall,
|
||||
selectedOption,
|
||||
allOptions,
|
||||
handleValueUpdate,
|
||||
);
|
||||
};
|
||||
|
||||
renderActionArgumentFields = (
|
||||
value: string,
|
||||
selectedOption: ActionCreatorDropdownOption,
|
||||
allOptions: ActionCreatorDropdownOption[],
|
||||
handleUpdate: (
|
||||
updateValueOrEvent: ValueType | ChangeEvent<HTMLTextAreaElement>,
|
||||
valueUpdateHandler: ValueChangeHandler,
|
||||
) => void,
|
||||
) => {
|
||||
return (
|
||||
<div>
|
||||
{selectedOption.arguments.map(arg => {
|
||||
switch (arg.field) {
|
||||
case "ACTION_SELECTOR_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<label>{arg.label}</label>
|
||||
<StyledDropdown
|
||||
options={allOptions}
|
||||
selectedValue={arg.getSelectedValue(value, false) as string}
|
||||
defaultText={"Select Action"}
|
||||
onSelect={value =>
|
||||
handleUpdate(value, arg.valueChangeHandler)
|
||||
}
|
||||
/>
|
||||
{this.renderSubArgumentFields(
|
||||
value,
|
||||
allOptions,
|
||||
handleUpdate,
|
||||
arg,
|
||||
)}
|
||||
</ControlWrapper>
|
||||
);
|
||||
case "PAGE_SELECTOR_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<label>{arg.label}</label>
|
||||
<StyledDropdown
|
||||
options={this.props.pageNameDropdown}
|
||||
selectedValue={arg.getSelectedValue(value, false) as string}
|
||||
defaultText={"Select Page"}
|
||||
onSelect={newValue => {
|
||||
handleUpdate(newValue, arg.valueChangeHandler);
|
||||
}}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
case "KEY_VALUE_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<KeyValueComponent
|
||||
pairs={
|
||||
arg.getSelectedValue(value, false) as DropdownOption[]
|
||||
}
|
||||
addLabel={"QueryParam"}
|
||||
updatePairs={(pageParams: DropdownOption[]) => {
|
||||
handleUpdate(pageParams as any, arg.valueChangeHandler);
|
||||
}}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
case "MODAL_SELECTOR_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<label>{arg.label}</label>
|
||||
<StyledDropdown
|
||||
options={this.props.modalDropdown || []}
|
||||
selectedValue={arg.getSelectedValue(value, false) as string}
|
||||
defaultText="Select Modal"
|
||||
createButton={
|
||||
arg.create && {
|
||||
text: arg.create.text,
|
||||
args: [this.props.nextModalName],
|
||||
onClick: (...args: any) => {
|
||||
arg.create &&
|
||||
this.props.dispatchAction(
|
||||
arg.create.action(...args),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
onSelect={value => {
|
||||
handleUpdate(value, arg.valueChangeHandler);
|
||||
}}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
case "TEXT_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<label>{arg.label}</label>
|
||||
<InputText
|
||||
label={arg.label}
|
||||
value={arg.getSelectedValue(value, false) as string}
|
||||
onChange={e => handleUpdate(e, arg.valueChangeHandler)}
|
||||
isValid={this.props.isValid}
|
||||
validationMessage={this.props.validationMessage}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
case "ALERT_TYPE_SELECTOR_FIELD":
|
||||
return (
|
||||
<ControlWrapper key={arg.label}>
|
||||
<label>{arg.label}</label>
|
||||
<StyledDropdown
|
||||
options={ALERT_STYLE_OPTIONS}
|
||||
defaultText={"Select type"}
|
||||
selectedValue={arg.getSelectedValue(value, false) as string}
|
||||
onSelect={value =>
|
||||
handleUpdate(value, arg.valueChangeHandler)
|
||||
}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { actions, value } = this.props;
|
||||
const stringValue = typeof value === "string" ? value : "";
|
||||
const topLevelFuncValue = this.getTopLevelFuncValue(stringValue);
|
||||
const actionOptions = PropertyPaneActionDropdownOptions.map(o => {
|
||||
if (o.id === "api") {
|
||||
return {
|
||||
...o,
|
||||
children: actions.map(a => ({
|
||||
...o,
|
||||
label: a.config.name,
|
||||
id: a.config.id,
|
||||
value: `${a.config.name}.run`,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
return o;
|
||||
}
|
||||
});
|
||||
|
||||
let selectedOption = actionOptions[0];
|
||||
actionOptions.forEach(o => {
|
||||
if (
|
||||
o.value === topLevelFuncValue ||
|
||||
_.some(o.children, {
|
||||
value: topLevelFuncValue,
|
||||
})
|
||||
) {
|
||||
selectedOption = o;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StyledDropdown
|
||||
options={actionOptions}
|
||||
selectedValue={topLevelFuncValue}
|
||||
defaultText={"Select"}
|
||||
onSelect={value =>
|
||||
this.handleValueUpdate(value, handleTopLevelFuncUpdate)
|
||||
}
|
||||
/>
|
||||
{this.renderActionArgumentFields(
|
||||
value,
|
||||
selectedOption,
|
||||
actionOptions,
|
||||
this.handleValueUpdate,
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
actions: getActionsForCurrentPage(state),
|
||||
pageNameDropdown: state.entities.pageList.pages.map(p => ({
|
||||
label: p.pageName,
|
||||
id: p.pageId,
|
||||
value: `'${p.pageName}'`,
|
||||
})),
|
||||
modalDropdown: getModalDropdownList(state),
|
||||
nextModalName: getNextModalName(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => {
|
||||
return {
|
||||
dispatchAction: (payload: ReduxActionWithoutPayload) => dispatch(payload),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(DynamicActionCreator);
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import {
|
||||
StyledPopover,
|
||||
StyledDropDownContainer,
|
||||
StyledMenuItem,
|
||||
} from "components/propertyControls/StyledControls";
|
||||
import {
|
||||
Button as BlueprintButton,
|
||||
Menu,
|
||||
PopoverInteractionKind,
|
||||
PopoverPosition,
|
||||
} from "@blueprintjs/core";
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
|
||||
type ActionTypeDropdownProps = {
|
||||
options: DropdownOption[];
|
||||
selectedValue: string;
|
||||
defaultText: string;
|
||||
onSelect: (value: string) => void;
|
||||
createButton?: {
|
||||
text: string;
|
||||
args: string[];
|
||||
onClick: (...args: any) => void;
|
||||
};
|
||||
};
|
||||
|
||||
class StyledDropdown extends React.Component<ActionTypeDropdownProps> {
|
||||
handleSelect = (option: DropdownOption) => {
|
||||
this.props.onSelect(option.value);
|
||||
};
|
||||
renderItem = (option: DropdownOption) => {
|
||||
const isSelected = this.isOptionSelected(option);
|
||||
return (
|
||||
<StyledMenuItem
|
||||
className="single-select"
|
||||
active={isSelected}
|
||||
key={option.value}
|
||||
onClick={option.children ? _.noop : () => this.handleSelect(option)}
|
||||
text={option.label}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
interactionKind: PopoverInteractionKind.CLICK,
|
||||
position: PopoverPosition.RIGHT,
|
||||
}}
|
||||
>
|
||||
{option.children && option.children.map(this.renderItem)}
|
||||
</StyledMenuItem>
|
||||
);
|
||||
};
|
||||
isOptionSelected = (currentOption: DropdownOption) => {
|
||||
if (currentOption.children) {
|
||||
return _.some(currentOption.children, {
|
||||
value: this.props.selectedValue,
|
||||
});
|
||||
} else {
|
||||
return currentOption.value === this.props.selectedValue;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedValue } = this.props;
|
||||
let selectedOption = {
|
||||
label: this.props.defaultText,
|
||||
};
|
||||
this.props.options.length > 0 &&
|
||||
this.props.options.forEach(o => {
|
||||
if (o.value === selectedValue) {
|
||||
selectedOption = o;
|
||||
} else {
|
||||
const childOption = _.find(o.children, {
|
||||
value: this.props.selectedValue,
|
||||
});
|
||||
if (childOption) selectedOption = childOption;
|
||||
}
|
||||
});
|
||||
const list =
|
||||
this.props.options.length > 0 && this.props.options.map(this.renderItem);
|
||||
let createBtn: React.ReactNode;
|
||||
if (this.props.createButton) {
|
||||
const btnClick = () => {
|
||||
this.props.createButton?.onClick(...this.props.createButton.args);
|
||||
const optionVal = this.props.createButton?.args.join(", ");
|
||||
optionVal &&
|
||||
this.handleSelect({
|
||||
id: optionVal,
|
||||
label: optionVal,
|
||||
value: `'${optionVal}'`,
|
||||
});
|
||||
};
|
||||
createBtn = (
|
||||
<StyledMenuItem
|
||||
onClick={btnClick}
|
||||
icon="plus"
|
||||
text={`Create ${this.props.createButton.text}`}
|
||||
className="t--create-modal-btn"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const popoverContent = (
|
||||
<Menu>
|
||||
{createBtn}
|
||||
{list}
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<StyledDropDownContainer>
|
||||
<StyledPopover usePortal={true} minimal={true} content={popoverContent}>
|
||||
<BlueprintButton
|
||||
rightIcon={IconNames.CHEVRON_DOWN}
|
||||
text={selectedOption.label}
|
||||
className={`t--open-dropdown-${this.props.defaultText
|
||||
.split(" ")
|
||||
.join("-")}`}
|
||||
/>
|
||||
</StyledPopover>
|
||||
</StyledDropDownContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StyledDropdown;
|
||||
|
|
@ -13,6 +13,7 @@ import "codemirror/addon/display/autorefresh";
|
|||
import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
|
||||
import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants";
|
||||
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
|
||||
import HelperTooltip from "components/editorComponents/HelperTooltip";
|
||||
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
||||
import _ from "lodash";
|
||||
import { parseDynamicString } from "utils/DynamicBindingUtils";
|
||||
|
|
@ -27,6 +28,7 @@ const getBorderStyle = (
|
|||
hasError: boolean;
|
||||
singleLine: boolean;
|
||||
isFocused: boolean;
|
||||
disabled?: boolean;
|
||||
},
|
||||
) => {
|
||||
if (props.hasError) return props.theme.colors.error;
|
||||
|
|
@ -79,6 +81,7 @@ const Wrapper = styled.div<{
|
|||
hasError: boolean;
|
||||
singleLine: boolean;
|
||||
isFocused: boolean;
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
${props =>
|
||||
props.singleLine && props.isFocused
|
||||
|
|
@ -91,7 +94,8 @@ const Wrapper = styled.div<{
|
|||
`
|
||||
: `z-index: 0; position: relative`}
|
||||
background-color: ${props =>
|
||||
props.editorTheme === THEMES.DARK ? "#272822" : "#fff"}
|
||||
props.editorTheme === THEMES.DARK ? "#272822" : "#fff"};
|
||||
background-color: ${props => props.disabled && "#eef2f5"};
|
||||
border: 1px solid;
|
||||
border-color: ${getBorderStyle};
|
||||
border-radius: 4px;
|
||||
|
|
@ -115,6 +119,13 @@ const Wrapper = styled.div<{
|
|||
border-radius: 4px;
|
||||
height: auto;
|
||||
}
|
||||
${props =>
|
||||
props.disabled &&
|
||||
`
|
||||
.CodeMirror-cursor {
|
||||
display: none !important;
|
||||
}
|
||||
`}
|
||||
.CodeMirror pre.CodeMirror-placeholder {
|
||||
color: #a3b3bf;
|
||||
}
|
||||
|
|
@ -129,6 +140,25 @@ const Wrapper = styled.div<{
|
|||
}
|
||||
`}
|
||||
}
|
||||
&& {
|
||||
.CodeMirror-lines {
|
||||
background-color: ${props => props.disabled && "#eef2f5"};
|
||||
cursor: ${props => (props.disabled ? "not-allowed" : "text")}
|
||||
}
|
||||
}
|
||||
.bp3-popover-target {
|
||||
padding-right: 10px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
.leftImageStyles {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
.linkStyles {
|
||||
margin: 5px;
|
||||
margin-right: 11px;
|
||||
}
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
|
|
@ -149,6 +179,9 @@ const IconContainer = styled.div`
|
|||
}
|
||||
}
|
||||
}
|
||||
.bp3-popover-target {
|
||||
padding-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const THEMES = {
|
||||
|
|
@ -165,12 +198,17 @@ interface ReduxStateProps {
|
|||
export type DynamicAutocompleteInputProps = {
|
||||
placeholder?: string;
|
||||
leftIcon?: Function;
|
||||
rightIcon?: Function;
|
||||
description?: string;
|
||||
height?: number;
|
||||
theme?: THEME;
|
||||
meta?: Partial<WrappedFieldMetaProps>;
|
||||
showLineNumbers?: boolean;
|
||||
allowTabIndent?: boolean;
|
||||
singleLine: boolean;
|
||||
disabled?: boolean;
|
||||
leftImage?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
type Props = ReduxStateProps &
|
||||
|
|
@ -199,7 +237,10 @@ class DynamicAutocompleteInput extends Component<Props, State> {
|
|||
if (this.textArea.current) {
|
||||
const options: EditorConfiguration = {};
|
||||
if (this.props.theme === "DARK") options.theme = "monokai";
|
||||
if (!this.props.input.onChange) options.readOnly = true;
|
||||
if (!this.props.input.onChange || this.props.disabled) {
|
||||
options.readOnly = true;
|
||||
options.scrollbarStyle = "null";
|
||||
}
|
||||
if (this.props.showLineNumbers) options.lineNumbers = true;
|
||||
const extraKeys: Record<string, any> = {
|
||||
"Ctrl-Space": "autocomplete",
|
||||
|
|
@ -350,7 +391,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { input, meta, theme, singleLine } = this.props;
|
||||
const { input, meta, theme, singleLine, disabled } = this.props;
|
||||
const hasError = !!(meta && meta.error);
|
||||
let showError = false;
|
||||
if (this.editor) {
|
||||
|
|
@ -364,17 +405,45 @@ class DynamicAutocompleteInput extends Component<Props, State> {
|
|||
hasError={hasError}
|
||||
singleLine={singleLine}
|
||||
isFocused={this.state.isFocused}
|
||||
disabled={disabled}
|
||||
>
|
||||
<HintStyles />
|
||||
<IconContainer>
|
||||
{this.props.leftIcon && <this.props.leftIcon />}
|
||||
</IconContainer>
|
||||
|
||||
{this.props.leftImage && (
|
||||
<img
|
||||
src={this.props.leftImage}
|
||||
alt="img"
|
||||
className="leftImageStyles"
|
||||
/>
|
||||
)}
|
||||
|
||||
<textarea
|
||||
ref={this.textArea}
|
||||
{..._.omit(this.props.input, ["onChange", "value"])}
|
||||
defaultValue={input.value}
|
||||
placeholder={this.props.placeholder}
|
||||
/>
|
||||
{this.props.link && (
|
||||
<React.Fragment>
|
||||
<a
|
||||
href={this.props.link}
|
||||
target="_blank"
|
||||
className="linkStyles"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
API documentation
|
||||
</a>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{this.props.rightIcon && (
|
||||
<HelperTooltip
|
||||
description={this.props.description}
|
||||
rightIcon={this.props.rightIcon}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
</ErrorTooltip>
|
||||
);
|
||||
|
|
|
|||
83
app/client/src/components/editorComponents/HelperTooltip.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React from "react";
|
||||
import { Popover, PopoverInteractionKind } from "@blueprintjs/core";
|
||||
import styled, { createGlobalStyle } from "styled-components";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
const TooltipStyles = createGlobalStyle`
|
||||
.helper-tooltip{
|
||||
.bp3-popover {
|
||||
box-shadow: none;
|
||||
max-width: 258px;
|
||||
.bp3-popover-arrow {
|
||||
display: block;
|
||||
fill: none;
|
||||
}
|
||||
.bp3-popover-arrow-fill {
|
||||
fill: ${Colors.BLUE_CHARCOAL};
|
||||
}
|
||||
.bp3-popover-content {
|
||||
padding: 15px;
|
||||
background-color: ${Colors.BLUE_CHARCOAL};
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
text-transform: initial;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
.bp3-icon {
|
||||
border-radius: 4px 0 0 4px;
|
||||
margin: 0;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #eef2f5;
|
||||
svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
path {
|
||||
fill: #979797;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bp3-popover-target {
|
||||
padding-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
description?: string;
|
||||
rightIcon: Function;
|
||||
}
|
||||
|
||||
const HelperTooltip = (props: Props) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TooltipStyles />
|
||||
<Popover
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
content={props.description}
|
||||
position="bottom"
|
||||
defaultIsOpen={false}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
usePortal
|
||||
portalClassName="helper-tooltip"
|
||||
>
|
||||
<IconContainer>
|
||||
<props.rightIcon width={22} height={22} />
|
||||
</IconContainer>
|
||||
</Popover>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelperTooltip;
|
||||
|
|
@ -6,6 +6,9 @@ import {
|
|||
BUILDER_URL,
|
||||
API_EDITOR_ID_URL,
|
||||
PAGE_LIST_EDITOR_URL,
|
||||
getCurlImportPageURL,
|
||||
API_EDITOR_URL_WITH_SELECTED_PAGE_ID,
|
||||
getProviderTemplatesURL,
|
||||
} from "constants/routes";
|
||||
import WidgetSidebar from "pages/Editor/WidgetSidebar";
|
||||
import ApiSidebar from "pages/Editor/ApiSidebar";
|
||||
|
|
@ -47,6 +50,25 @@ export const Sidebar = () => {
|
|||
component={PageListSidebar}
|
||||
name={"PageListSidebar"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={getCurlImportPageURL()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
<AppRoute
|
||||
exact
|
||||
path={getProviderTemplatesURL()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
|
||||
<AppRoute
|
||||
exact
|
||||
path={API_EDITOR_URL_WITH_SELECTED_PAGE_ID()}
|
||||
component={ApiSidebar}
|
||||
name={"ApiSidebar"}
|
||||
/>
|
||||
</Switch>
|
||||
</SidebarWrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,693 @@
|
|||
import React from "react";
|
||||
import { AppState } from "reducers";
|
||||
import { getActionsForCurrentPage } from "selectors/entitiesSelector";
|
||||
import {
|
||||
getModalDropdownList,
|
||||
getNextModalName,
|
||||
} from "selectors/widgetSelectors";
|
||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import TreeDropdown, { TreeDropdownOption } from "./TreeDropdown";
|
||||
import { ControlWrapper } from "components/propertyControls/StyledControls";
|
||||
import { KeyValueComponent } from "components/propertyControls/KeyValueComponent";
|
||||
import { InputText } from "components/propertyControls/InputTextControl";
|
||||
import { createModalAction } from "actions/widgetActions";
|
||||
import { createActionRequest } from "actions/actionActions";
|
||||
import { DEFAULT_API_ACTION } from "constants/ApiEditorConstants";
|
||||
import { createNewApiName } from "utils/AppsmithUtils";
|
||||
|
||||
const ALERT_STYLE_OPTIONS = [
|
||||
{ label: "Info", value: "'info'", id: "info" },
|
||||
{ label: "Success", value: "'success'", id: "success" },
|
||||
{ label: "Error", value: "'error'", id: "error" },
|
||||
{ label: "Warning", value: "'warning'", id: "warning" },
|
||||
];
|
||||
const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g;
|
||||
const ACTION_ANONYMOUS_FUNC_REGEX = /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g;
|
||||
|
||||
const modalSetter = (changeValue: any, currentValue: string) => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[0] = `'${changeValue}'`;
|
||||
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const modalGetter = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
let name = "none";
|
||||
if (matches.length) {
|
||||
const modalName = matches[0][2].split(",")[0];
|
||||
name = modalName.substring(1, modalName.length - 1);
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
// const urlSetter = (changeValue: any, currentValue: string): string => {
|
||||
// return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1('${changeValue}')}}`);
|
||||
// };
|
||||
|
||||
// export const textGetter = (value: string) => {
|
||||
// const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
// if (matches.length) {
|
||||
// const stringValue = matches[0][2];
|
||||
// return stringValue.substring(1, stringValue.length - 1);
|
||||
// }
|
||||
// return "";
|
||||
// };
|
||||
|
||||
const alertTextSetter = (changeValue: any, currentValue: string): string => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[0] = `'${changeValue}'`;
|
||||
const result = currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const alertTextGetter = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const arg = funcArgs.split(",")[0];
|
||||
return arg.substring(1, arg.length - 1);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const alertTypeSetter = (changeValue: any, currentValue: string): string => {
|
||||
const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = matches[0][2].split(",");
|
||||
args[1] = changeValue as string;
|
||||
return currentValue.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${args.join(",")})}}`,
|
||||
);
|
||||
};
|
||||
|
||||
const alertTypeGetter = (value: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const arg = funcArgs.split(",")[1];
|
||||
return arg ? arg.trim() : "'primary'";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
type ActionCreatorProps = {
|
||||
value: string;
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
onValueChange: (newValue: string) => void;
|
||||
};
|
||||
|
||||
const ActionType = {
|
||||
none: "none",
|
||||
api: "api",
|
||||
showModal: "showModal",
|
||||
closeModal: "closeModal",
|
||||
navigateTo: "navigateTo",
|
||||
showAlert: "showAlert",
|
||||
};
|
||||
type ActionType = typeof ActionType[keyof typeof ActionType];
|
||||
|
||||
const ViewTypes = {
|
||||
SELECTOR_VIEW: "SELECTOR_VIEW",
|
||||
KEY_VALUE_VIEW: "KEY_VALUE_VIEW",
|
||||
TEXT_VIEW: "TEXT_VIEW",
|
||||
};
|
||||
type ViewTypes = typeof ViewTypes[keyof typeof ViewTypes];
|
||||
|
||||
type ViewProps = {
|
||||
label: string;
|
||||
get: Function;
|
||||
set: Function;
|
||||
value: string;
|
||||
};
|
||||
type SelectorViewProps = ViewProps & {
|
||||
options: TreeDropdownOption[];
|
||||
defaultText: string;
|
||||
getDefaults?: Function;
|
||||
selectedLabelModifier?: (option: TreeDropdownOption) => string;
|
||||
};
|
||||
|
||||
type KeyValueViewProps = ViewProps;
|
||||
type TextViewProps = ViewProps & {
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
};
|
||||
|
||||
const views = {
|
||||
[ViewTypes.SELECTOR_VIEW]: function SelectorView(props: SelectorViewProps) {
|
||||
return (
|
||||
<ControlWrapper key={props.label}>
|
||||
<label>{props.label}</label>
|
||||
<TreeDropdown
|
||||
optionTree={props.options}
|
||||
selectedValue={props.get(props.value, false) as string}
|
||||
defaultText={props.defaultText}
|
||||
onSelect={(value, defaultValue?: string) => {
|
||||
props.set(value, defaultValue);
|
||||
}}
|
||||
getDefaults={props.getDefaults}
|
||||
selectedLabelModifier={props.selectedLabelModifier}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
},
|
||||
[ViewTypes.KEY_VALUE_VIEW]: function KeyValueView(props: KeyValueViewProps) {
|
||||
return (
|
||||
<ControlWrapper key={props.label}>
|
||||
<KeyValueComponent
|
||||
pairs={props.get(props.value, false) as DropdownOption[]}
|
||||
addLabel={"QueryParam"}
|
||||
updatePairs={(pageParams: DropdownOption[]) => props.set(pageParams)}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
},
|
||||
[ViewTypes.TEXT_VIEW]: function TextView(props: TextViewProps) {
|
||||
return (
|
||||
<ControlWrapper key={props.label}>
|
||||
<label>{props.label}</label>
|
||||
<InputText
|
||||
label={props.label}
|
||||
value={props.get(props.value, false) as string}
|
||||
onChange={(event: any) => {
|
||||
if (event.target) {
|
||||
props.set(event.target.value);
|
||||
} else {
|
||||
props.set(event);
|
||||
}
|
||||
}}
|
||||
isValid={props.isValid}
|
||||
validationMessage={props.validationMessage}
|
||||
/>
|
||||
</ControlWrapper>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const FieldType = {
|
||||
ACTION_SELECTOR_FIELD: "ACTION_SELECTOR_FIELD",
|
||||
ON_SUCCESS_FIELD: "ON_SUCCESS_FIELD",
|
||||
ON_ERROR_FIELD: "ON_ERROR_FIELD",
|
||||
SHOW_MODAL_FIELD: "SHOW_MODAL_FIELD",
|
||||
CLOSE_MODAL_FIELD: "CLOSE_MODAL_FIELD",
|
||||
PAGE_SELECTOR_FIELD: "PAGE_SELECTOR_FIELD",
|
||||
KEY_VALUE_FIELD: "KEY_VALUE_FIELD",
|
||||
URL_FIELD: "URL_FIELD",
|
||||
ALERT_TEXT_FIELD: "ALERT_TEXT_FIELD",
|
||||
ALERT_TYPE_SELECTOR_FIELD: "ALERT_TYPE_SELECTOR_FIELD",
|
||||
};
|
||||
type FieldType = typeof FieldType[keyof typeof FieldType];
|
||||
|
||||
type FieldConfig = {
|
||||
getter: Function;
|
||||
setter: Function;
|
||||
view: ViewTypes;
|
||||
};
|
||||
|
||||
type FieldConfigs = Record<FieldType, FieldConfig>;
|
||||
|
||||
const fieldConfigs: FieldConfigs = {
|
||||
[FieldType.ACTION_SELECTOR_FIELD]: {
|
||||
getter: (storedValue: string) => {
|
||||
let matches: any[] = [];
|
||||
if (storedValue) {
|
||||
matches = storedValue
|
||||
? [...storedValue.matchAll(ACTION_TRIGGER_REGEX)]
|
||||
: [];
|
||||
}
|
||||
let mainFuncSelectedValue = ActionType.none;
|
||||
if (matches.length) {
|
||||
mainFuncSelectedValue = matches[0][1] || ActionType.none;
|
||||
}
|
||||
const mainFuncSelectedValueSplit = mainFuncSelectedValue.split(".");
|
||||
if (mainFuncSelectedValueSplit[1] === "run") {
|
||||
return mainFuncSelectedValueSplit[0];
|
||||
}
|
||||
return mainFuncSelectedValue;
|
||||
},
|
||||
setter: (
|
||||
option: TreeDropdownOption,
|
||||
currentValue: string,
|
||||
defaultValue?: string,
|
||||
) => {
|
||||
const type: ActionType = option.type || option.value;
|
||||
let value = option.value;
|
||||
switch (type) {
|
||||
case ActionType.api:
|
||||
value = `${value}.run`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value === "none" ? "" : `{{${value}()}}`;
|
||||
},
|
||||
view: ViewTypes.SELECTOR_VIEW,
|
||||
},
|
||||
[FieldType.SHOW_MODAL_FIELD]: {
|
||||
getter: (value: any) => {
|
||||
return modalGetter(value);
|
||||
},
|
||||
setter: (option: any, currentValue: string) => {
|
||||
return modalSetter(option.value, currentValue);
|
||||
},
|
||||
view: ViewTypes.SELECTOR_VIEW,
|
||||
},
|
||||
[FieldType.CLOSE_MODAL_FIELD]: {
|
||||
getter: (value: any) => {
|
||||
return modalGetter(value);
|
||||
},
|
||||
setter: (option: any, currentValue: string) => {
|
||||
return modalSetter(option.value, currentValue);
|
||||
},
|
||||
view: ViewTypes.SELECTOR_VIEW,
|
||||
},
|
||||
[FieldType.PAGE_SELECTOR_FIELD]: {
|
||||
getter: (value: any) => {
|
||||
return modalGetter(value);
|
||||
},
|
||||
setter: (option: any, currentValue: string) => {
|
||||
return modalSetter(option.value, currentValue);
|
||||
},
|
||||
view: ViewTypes.SELECTOR_VIEW,
|
||||
},
|
||||
[FieldType.KEY_VALUE_FIELD]: {
|
||||
getter: (value: any) => {
|
||||
return value;
|
||||
},
|
||||
setter: (value: any) => {
|
||||
return value;
|
||||
},
|
||||
view: ViewTypes.KEY_VALUE_VIEW,
|
||||
},
|
||||
[FieldType.URL_FIELD]: {
|
||||
getter: (value: string) => {
|
||||
return modalGetter(value);
|
||||
},
|
||||
setter: (value: string, currentValue: string) => {
|
||||
return modalSetter(value, currentValue);
|
||||
},
|
||||
view: ViewTypes.TEXT_VIEW,
|
||||
},
|
||||
[FieldType.ALERT_TEXT_FIELD]: {
|
||||
getter: (value: string) => {
|
||||
return alertTextGetter(value);
|
||||
},
|
||||
setter: (value: string, currentValue: string) => {
|
||||
return alertTextSetter(value, currentValue);
|
||||
},
|
||||
view: ViewTypes.TEXT_VIEW,
|
||||
},
|
||||
[FieldType.ALERT_TYPE_SELECTOR_FIELD]: {
|
||||
getter: (value: any) => {
|
||||
return alertTypeGetter(value);
|
||||
},
|
||||
setter: (option: any, currentValue: string) => {
|
||||
return alertTypeSetter(option.value, currentValue);
|
||||
},
|
||||
view: ViewTypes.SELECTOR_VIEW,
|
||||
},
|
||||
};
|
||||
|
||||
const baseOptions: any = [
|
||||
{
|
||||
label: "No Action",
|
||||
value: ActionType.none,
|
||||
},
|
||||
{
|
||||
label: "Call API",
|
||||
value: ActionType.api,
|
||||
},
|
||||
{
|
||||
label: "Show Modal",
|
||||
value: ActionType.showModal,
|
||||
},
|
||||
{
|
||||
label: "Close Modal",
|
||||
value: ActionType.closeModal,
|
||||
},
|
||||
{
|
||||
label: "Navigate To",
|
||||
value: ActionType.navigateTo,
|
||||
},
|
||||
{
|
||||
label: "Show Alert",
|
||||
value: ActionType.showAlert,
|
||||
},
|
||||
];
|
||||
function getOptionsWithChildren(
|
||||
options: TreeDropdownOption[],
|
||||
actions: ActionDataState,
|
||||
createActionOption: TreeDropdownOption,
|
||||
) {
|
||||
const option = options.find(option => option.value === ActionType.api);
|
||||
if (option) {
|
||||
option.children = [createActionOption];
|
||||
actions.forEach(action => {
|
||||
(option.children as TreeDropdownOption[]).push({
|
||||
label: action.config.name,
|
||||
id: action.config.id,
|
||||
value: action.config.name,
|
||||
type: option.value,
|
||||
} as TreeDropdownOption);
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function getFieldFromValue(
|
||||
value: string | undefined,
|
||||
getParentValue?: Function,
|
||||
): any[] {
|
||||
const fields: any[] = [
|
||||
{
|
||||
field: FieldType.ACTION_SELECTOR_FIELD,
|
||||
getParentValue: getParentValue,
|
||||
value: value,
|
||||
},
|
||||
];
|
||||
if (!value) {
|
||||
return fields;
|
||||
}
|
||||
if (value.indexOf("run") !== -1) {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
if (matches.length) {
|
||||
const funcArgs = matches[0][2];
|
||||
const args = [...funcArgs.matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
|
||||
const successArg = args[0];
|
||||
const errorArg = args[1];
|
||||
let sucesssValue;
|
||||
if (successArg && successArg.length > 0) {
|
||||
sucesssValue = successArg[1] + successArg[2];
|
||||
}
|
||||
const successFields = getFieldFromValue(
|
||||
`{{${sucesssValue}}}`,
|
||||
(changeValue: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = [...matches[0][2].matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
|
||||
let successArg = args[0] ? args[0][0] : "() => {}";
|
||||
const errorArg = args[1] ? args[1][0] : "() => {}";
|
||||
successArg = changeValue.endsWith(")")
|
||||
? `() => ${changeValue}`
|
||||
: `() => ${changeValue}()`;
|
||||
|
||||
return value.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${successArg}, ${errorArg})}}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
successFields[0].label = "onSuccess";
|
||||
fields.push(successFields);
|
||||
|
||||
let errorValue;
|
||||
if (errorArg && errorArg.length > 0) {
|
||||
errorValue = errorArg[1] + errorArg[2];
|
||||
}
|
||||
const errorFields = getFieldFromValue(
|
||||
`{{${errorValue}}}`,
|
||||
(changeValue: string) => {
|
||||
const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)];
|
||||
const args = [...matches[0][2].matchAll(ACTION_ANONYMOUS_FUNC_REGEX)];
|
||||
const successArg = args[0] ? args[0][0] : "() => {}";
|
||||
let errorArg = args[1] ? args[1][0] : "() => {}";
|
||||
errorArg = changeValue.endsWith(")")
|
||||
? `() => ${changeValue}`
|
||||
: `() => ${changeValue}()`;
|
||||
return value.replace(
|
||||
ACTION_TRIGGER_REGEX,
|
||||
`{{$1(${successArg}, ${errorArg})}}`,
|
||||
);
|
||||
},
|
||||
);
|
||||
errorFields[0].label = "onError";
|
||||
fields.push(errorFields);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
if (value.indexOf("navigateTo") !== -1) {
|
||||
fields.push({
|
||||
field: FieldType.URL_FIELD,
|
||||
});
|
||||
}
|
||||
|
||||
if (value.indexOf("showModal") !== -1) {
|
||||
fields.push({
|
||||
field: FieldType.SHOW_MODAL_FIELD,
|
||||
});
|
||||
}
|
||||
if (value.indexOf("closeModal") !== -1) {
|
||||
fields.push({
|
||||
field: FieldType.CLOSE_MODAL_FIELD,
|
||||
});
|
||||
}
|
||||
if (value.indexOf("showAlert") !== -1) {
|
||||
fields.push(
|
||||
{
|
||||
field: FieldType.ALERT_TEXT_FIELD,
|
||||
},
|
||||
{
|
||||
field: FieldType.ALERT_TYPE_SELECTOR_FIELD,
|
||||
},
|
||||
);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
function getPageDropdownOptions(state: AppState) {
|
||||
return state.entities.pageList.pages.map(page => ({
|
||||
label: page.pageName,
|
||||
id: page.pageId,
|
||||
value: `'${page.pageName}'`,
|
||||
}));
|
||||
}
|
||||
|
||||
function Fields(props: {
|
||||
onValueChange: Function;
|
||||
value: string;
|
||||
fields: any;
|
||||
label?: string;
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
apiOptionTree: TreeDropdownOption[];
|
||||
modalDropdownList: TreeDropdownOption[];
|
||||
pageDropdownOptions: TreeDropdownOption[];
|
||||
}) {
|
||||
const ui = props.fields.map((field: any) => {
|
||||
if (Array.isArray(field)) {
|
||||
const selectorField = field[0];
|
||||
return (
|
||||
<Fields
|
||||
value={selectorField.value}
|
||||
fields={field}
|
||||
label={selectorField.label}
|
||||
isValid={props.isValid}
|
||||
validationMessage={props.validationMessage}
|
||||
apiOptionTree={props.apiOptionTree}
|
||||
modalDropdownList={props.modalDropdownList}
|
||||
pageDropdownOptions={props.pageDropdownOptions}
|
||||
onValueChange={(value: any) => {
|
||||
props.onValueChange(
|
||||
selectorField.getParentValue(
|
||||
value.substring(2, value.length - 2),
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const fieldType = field.field;
|
||||
const fieldConfig = fieldConfigs[fieldType];
|
||||
const view = views[fieldConfig.view];
|
||||
let viewElement: JSX.Element | null = null;
|
||||
|
||||
switch (fieldType) {
|
||||
case FieldType.ACTION_SELECTOR_FIELD:
|
||||
case FieldType.ON_SUCCESS_FIELD:
|
||||
case FieldType.ON_ERROR_FIELD:
|
||||
case FieldType.SHOW_MODAL_FIELD:
|
||||
case FieldType.CLOSE_MODAL_FIELD:
|
||||
case FieldType.PAGE_SELECTOR_FIELD:
|
||||
case FieldType.ALERT_TYPE_SELECTOR_FIELD:
|
||||
let label = "";
|
||||
let defaultText = "Select Action";
|
||||
let options = props.apiOptionTree;
|
||||
let selectedLabelModifier = undefined;
|
||||
let getDefaults = undefined;
|
||||
if (fieldType === FieldType.ACTION_SELECTOR_FIELD) {
|
||||
label = props.label || "";
|
||||
selectedLabelModifier = (option: TreeDropdownOption) => {
|
||||
if (option.type === ActionType.api) {
|
||||
return `Call ${option.label}`;
|
||||
}
|
||||
return option.label;
|
||||
};
|
||||
getDefaults = (value: string) => {
|
||||
return {
|
||||
[ActionType.navigateTo]: `'${props.pageDropdownOptions[0].label}'`,
|
||||
}[value];
|
||||
};
|
||||
}
|
||||
if (
|
||||
fieldType === FieldType.SHOW_MODAL_FIELD ||
|
||||
fieldType === FieldType.CLOSE_MODAL_FIELD
|
||||
) {
|
||||
label = "Modal Name";
|
||||
options = props.modalDropdownList;
|
||||
defaultText = "Select Modal";
|
||||
}
|
||||
if (fieldType === FieldType.PAGE_SELECTOR_FIELD) {
|
||||
label = "Page Name";
|
||||
options = props.pageDropdownOptions;
|
||||
defaultText = "Select Page";
|
||||
}
|
||||
if (fieldType === FieldType.ALERT_TYPE_SELECTOR_FIELD) {
|
||||
label = "type";
|
||||
options = ALERT_STYLE_OPTIONS;
|
||||
defaultText = "Select type";
|
||||
}
|
||||
viewElement = (view as (props: SelectorViewProps) => JSX.Element)({
|
||||
options: options,
|
||||
label: label,
|
||||
get: fieldConfig.getter,
|
||||
set: (value: string | DropdownOption, defaultValue?: string) => {
|
||||
const finalValueToSet = fieldConfig.setter(
|
||||
value,
|
||||
props.value,
|
||||
defaultValue,
|
||||
);
|
||||
props.onValueChange(finalValueToSet);
|
||||
},
|
||||
value: props.value,
|
||||
defaultText: defaultText,
|
||||
getDefaults: getDefaults,
|
||||
selectedLabelModifier: selectedLabelModifier,
|
||||
});
|
||||
break;
|
||||
case FieldType.KEY_VALUE_FIELD:
|
||||
viewElement = (view as (props: SelectorViewProps) => JSX.Element)({
|
||||
options: props.apiOptionTree,
|
||||
label: "",
|
||||
get: fieldConfig.getter,
|
||||
set: (value: string | DropdownOption) => {
|
||||
const finalValueToSet = fieldConfig.setter(value, props.value);
|
||||
props.onValueChange(finalValueToSet);
|
||||
},
|
||||
value: props.value,
|
||||
defaultText: "Select Action",
|
||||
});
|
||||
break;
|
||||
case FieldType.ALERT_TEXT_FIELD:
|
||||
case FieldType.URL_FIELD:
|
||||
viewElement = (view as (props: TextViewProps) => JSX.Element)({
|
||||
label: "",
|
||||
get: fieldConfig.getter,
|
||||
set: (value: string | DropdownOption) => {
|
||||
const finalValueToSet = fieldConfig.setter(value, props.value);
|
||||
props.onValueChange(finalValueToSet);
|
||||
},
|
||||
value: props.value,
|
||||
isValid: props.isValid,
|
||||
validationMessage: props.validationMessage,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return <div key={fieldType}>{viewElement}</div>;
|
||||
});
|
||||
|
||||
return <>{ui}</>;
|
||||
}
|
||||
|
||||
function useModalDropdownList() {
|
||||
const dispatch = useDispatch();
|
||||
const nextModalName = useSelector(getNextModalName);
|
||||
|
||||
let finalList: TreeDropdownOption[] = [
|
||||
{
|
||||
label: "Create Modal",
|
||||
value: "Modal",
|
||||
id: "create",
|
||||
className: "t--create-modal-btn",
|
||||
onSelect: (option: TreeDropdownOption, setter?: Function) => {
|
||||
const modalName = nextModalName;
|
||||
if (setter) {
|
||||
setter({
|
||||
value: `${modalName}`,
|
||||
});
|
||||
dispatch(createModalAction(nextModalName));
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
finalList = finalList.concat(
|
||||
(useSelector(getModalDropdownList) || []) as TreeDropdownOption[],
|
||||
);
|
||||
|
||||
return finalList;
|
||||
}
|
||||
|
||||
function useApiOptionTree() {
|
||||
const dispatch = useDispatch();
|
||||
const pageId = useSelector(getCurrentPageId) || "";
|
||||
|
||||
const actions = useSelector(getActionsForCurrentPage);
|
||||
const apiOptionTree = getOptionsWithChildren(baseOptions, actions, {
|
||||
label: "Create Api",
|
||||
value: "api",
|
||||
id: "create",
|
||||
className: "t--create-api-btn",
|
||||
onSelect: (option: TreeDropdownOption, setter?: Function) => {
|
||||
const apiName = createNewApiName(actions, pageId);
|
||||
if (setter) {
|
||||
setter({
|
||||
value: `${apiName}.run`,
|
||||
type: ActionType.api,
|
||||
});
|
||||
dispatch(
|
||||
createActionRequest({
|
||||
...DEFAULT_API_ACTION,
|
||||
name: apiName,
|
||||
pageId: pageId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
return apiOptionTree;
|
||||
}
|
||||
|
||||
export function ActionCreator(props: ActionCreatorProps) {
|
||||
const apiOptionTree = useApiOptionTree();
|
||||
const modalDropdownList = useModalDropdownList();
|
||||
const pageDropdownOptions = useSelector(getPageDropdownOptions);
|
||||
const fields = getFieldFromValue(props.value);
|
||||
|
||||
return (
|
||||
<Fields
|
||||
value={props.value}
|
||||
fields={fields}
|
||||
isValid={props.isValid}
|
||||
validationMessage={props.validationMessage}
|
||||
apiOptionTree={apiOptionTree}
|
||||
modalDropdownList={modalDropdownList}
|
||||
pageDropdownOptions={pageDropdownOptions}
|
||||
onValueChange={props.onValueChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import {
|
||||
StyledPopover,
|
||||
StyledDropDownContainer,
|
||||
StyledMenuItem,
|
||||
} from "components/propertyControls/StyledControls";
|
||||
import {
|
||||
Button as BlueprintButton,
|
||||
Menu,
|
||||
PopoverInteractionKind,
|
||||
PopoverPosition,
|
||||
} from "@blueprintjs/core";
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
|
||||
export type TreeDropdownOption = DropdownOption & {
|
||||
onSelect?: (value: TreeDropdownOption, setter?: Function) => void;
|
||||
children?: TreeDropdownOption[];
|
||||
className?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
type TreeDropdownProps = {
|
||||
optionTree: TreeDropdownOption[];
|
||||
selectedValue: string;
|
||||
getDefaults?: Function;
|
||||
defaultText: string;
|
||||
onSelect: (value: TreeDropdownOption, defaultVal?: string) => void;
|
||||
selectedLabelModifier?: (option: TreeDropdownOption) => string;
|
||||
toggle?: React.ReactNode;
|
||||
};
|
||||
|
||||
function getSelectedOption(
|
||||
selectedValue: string,
|
||||
defaultText: string,
|
||||
options: TreeDropdownOption[],
|
||||
) {
|
||||
let selectedOption = {
|
||||
label: defaultText,
|
||||
value: "",
|
||||
};
|
||||
options.length > 0 &&
|
||||
options.forEach(option => {
|
||||
// Find the selected option in the OptionsTree
|
||||
if (option.value === selectedValue) {
|
||||
selectedOption = option;
|
||||
} else {
|
||||
const childOption = _.find(option.children, {
|
||||
value: selectedValue,
|
||||
});
|
||||
if (childOption) {
|
||||
selectedOption = childOption;
|
||||
}
|
||||
}
|
||||
});
|
||||
return selectedOption;
|
||||
}
|
||||
|
||||
export default function TreeDropdown(props: TreeDropdownProps) {
|
||||
const {
|
||||
selectedValue,
|
||||
defaultText,
|
||||
optionTree,
|
||||
onSelect,
|
||||
getDefaults,
|
||||
selectedLabelModifier,
|
||||
toggle,
|
||||
} = props;
|
||||
const selectedOption = getSelectedOption(
|
||||
selectedValue,
|
||||
defaultText,
|
||||
optionTree,
|
||||
);
|
||||
|
||||
const handleSelect = (option: TreeDropdownOption) => {
|
||||
if (option.onSelect) {
|
||||
option.onSelect(option, props.onSelect);
|
||||
} else {
|
||||
const defaultVal = getDefaults ? getDefaults(option.value) : undefined;
|
||||
onSelect(option, defaultVal);
|
||||
}
|
||||
};
|
||||
|
||||
function renderTreeOption(option: TreeDropdownOption) {
|
||||
const isSelected = selectedOption.value === option.value;
|
||||
return (
|
||||
<StyledMenuItem
|
||||
className={option.className || "single-select"}
|
||||
active={isSelected}
|
||||
key={option.value}
|
||||
icon={option.id === "create" ? "plus" : undefined}
|
||||
onClick={option.children ? _.noop : () => handleSelect(option)}
|
||||
text={option.label}
|
||||
intent={option.intent}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
interactionKind: PopoverInteractionKind.CLICK,
|
||||
position: PopoverPosition.RIGHT,
|
||||
}}
|
||||
>
|
||||
{option.children && option.children.map(renderTreeOption)}
|
||||
</StyledMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const list = optionTree.map(renderTreeOption);
|
||||
const menuItems = <Menu>{list}</Menu>;
|
||||
const defaultToggle = (
|
||||
<StyledDropDownContainer>
|
||||
<BlueprintButton
|
||||
rightIcon={IconNames.CHEVRON_DOWN}
|
||||
text={
|
||||
selectedLabelModifier
|
||||
? selectedLabelModifier(selectedOption)
|
||||
: selectedOption.label
|
||||
}
|
||||
className={`t--open-dropdown-${defaultText.split(" ").join("-")}`}
|
||||
/>
|
||||
</StyledDropDownContainer>
|
||||
);
|
||||
return (
|
||||
<StyledPopover
|
||||
usePortal={true}
|
||||
minimal={true}
|
||||
content={menuItems}
|
||||
position={PopoverPosition.AUTO_END}
|
||||
>
|
||||
{toggle ? toggle : defaultToggle}
|
||||
</StyledPopover>
|
||||
);
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ const DatasourcesField = (
|
|||
const options = props.datasources.list
|
||||
.filter(r => r.pluginId === props.pluginId)
|
||||
.filter(r => r.datasourceConfiguration !== null)
|
||||
.filter(r => r.datasourceConfiguration.url)
|
||||
.map(r => ({
|
||||
label: r.datasourceConfiguration.url.endsWith("/")
|
||||
? r.datasourceConfiguration.url.slice(0, -1)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ interface DropdownFieldProps {
|
|||
}>;
|
||||
placeholder: string;
|
||||
width?: number;
|
||||
isSearchable?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const DropdownField = (props: DropdownFieldProps) => {
|
||||
|
|
@ -23,6 +25,8 @@ const DropdownField = (props: DropdownFieldProps) => {
|
|||
format={(value: string) => _.find(props.options, { value }) || ""}
|
||||
normalize={(option: { value: string }) => option.value}
|
||||
width={props.width}
|
||||
isSearchable={props.isSearchable}
|
||||
isDisabled={props.isDisabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { FieldArray, WrappedFieldArrayProps } from "redux-form";
|
||||
import styled from "styled-components";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import DynamicTextField from "./DynamicTextField";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import styled from "styled-components";
|
||||
|
||||
const FormRowWithLabel = styled(FormRow)`
|
||||
flex-wrap: wrap;
|
||||
|
|
@ -20,46 +20,88 @@ const FormRowWithLabel = styled(FormRow)`
|
|||
const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
||||
useEffect(() => {
|
||||
// Always maintain 2 rows
|
||||
if (props.fields.length < 2) {
|
||||
if (props.fields.length < 2 && props.pushFields !== false) {
|
||||
for (let i = props.fields.length; i < 2; i += 1) {
|
||||
props.fields.push({ key: "", value: "" });
|
||||
}
|
||||
}
|
||||
}, [props.fields]);
|
||||
}, [props.fields, props.pushFields]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index}>
|
||||
{index === 0 && <FormLabel>{props.label}</FormLabel>}
|
||||
<DynamicTextField
|
||||
name={`${field}.key`}
|
||||
placeholder="Key"
|
||||
singleLine
|
||||
/>
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
placeholder="Value"
|
||||
singleLine
|
||||
/>
|
||||
{index === props.fields.length - 1 ? (
|
||||
<Icon
|
||||
icon="plus"
|
||||
iconSize={20}
|
||||
onClick={() => props.fields.push({ key: "", value: "" })}
|
||||
color={"#A3B3BF"}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
)}
|
||||
</FormRowWithLabel>
|
||||
))}
|
||||
{typeof props.fields.getAll() === "object" && (
|
||||
<React.Fragment>
|
||||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index}>
|
||||
{index === 0 && props.label !== "" && (
|
||||
<FormLabel>{props.label}</FormLabel>
|
||||
)}
|
||||
<DynamicTextField
|
||||
name={`${field}.key`}
|
||||
placeholder="Key"
|
||||
singleLine
|
||||
/>
|
||||
{!props.actionConfig && (
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
placeholder="Value"
|
||||
singleLine
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.actionConfig && props.actionConfig[index] && (
|
||||
<React.Fragment>
|
||||
<DynamicTextField
|
||||
name={`${field}.value`}
|
||||
height={30}
|
||||
placeholder={
|
||||
props.placeholder
|
||||
? props.placeholder
|
||||
: props.actionConfig[index].mandatory &&
|
||||
props.actionConfig[index].type
|
||||
? `${props.actionConfig[index].type}`
|
||||
: props.actionConfig[index].type
|
||||
? `${props.actionConfig[index].type} (Optional)`
|
||||
: `(Optional)`
|
||||
}
|
||||
singleLine
|
||||
rightIcon={
|
||||
props.actionConfig[index].description && props.rightIcon
|
||||
}
|
||||
description={props.actionConfig[index].description}
|
||||
disabled={
|
||||
props.actionConfig[index].editable ||
|
||||
props.actionConfig[index].editable === undefined
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{props.addOrDeleteFields !== false && (
|
||||
<React.Fragment>
|
||||
{index === props.fields.length - 1 ? (
|
||||
<Icon
|
||||
icon="plus"
|
||||
iconSize={20}
|
||||
onClick={() => props.fields.push({ key: "", value: "" })}
|
||||
color={"#A3B3BF"}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</FormRowWithLabel>
|
||||
))}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
|
@ -67,6 +109,14 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
type Props = {
|
||||
name: string;
|
||||
label: string;
|
||||
rightIcon?: Function;
|
||||
description?: string;
|
||||
actionConfig?: any;
|
||||
addOrDeleteFields?: boolean;
|
||||
mandatory?: boolean;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
pushFields?: boolean;
|
||||
};
|
||||
|
||||
const KeyValueFieldArray = (props: Props) => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class TextField extends React.Component<
|
|||
component={BaseTextInput}
|
||||
{...this.props}
|
||||
noValidate
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import DynamicActionCreator from "components/editorComponents/DynamicActionCreator";
|
||||
// import DynamicActionCreator from "components/editorComponents/DynamicActionCreator";
|
||||
import { ActionCreator } from "components/editorComponents/actioncreator/ActionCreator";
|
||||
|
||||
class ActionSelectorControl extends BaseControl<ControlProps> {
|
||||
handleValueUpdate = (newValue: string) => {
|
||||
|
|
@ -12,7 +12,7 @@ class ActionSelectorControl extends BaseControl<ControlProps> {
|
|||
render() {
|
||||
const { propertyValue } = this.props;
|
||||
return (
|
||||
<DynamicActionCreator
|
||||
<ActionCreator
|
||||
value={propertyValue}
|
||||
isValid={this.props.isValid}
|
||||
validationMessage={this.props.validationMessage}
|
||||
|
|
@ -21,7 +21,7 @@ class ActionSelectorControl extends BaseControl<ControlProps> {
|
|||
);
|
||||
}
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "ACTION_SELECTOR";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ abstract class BaseControl<P extends ControlProps, S = {}> extends Component<
|
|||
if (!_.isNil(this.props.onPropertyChange))
|
||||
this.props.onPropertyChange(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
abstract getControlType(): ControlType;
|
||||
}
|
||||
|
||||
export interface ControlBuilder<T extends ControlProps> {
|
||||
|
|
|
|||
210
app/client/src/components/propertyControls/ChartDataControl.tsx
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import {
|
||||
ControlWrapper,
|
||||
StyledInputGroup,
|
||||
StyledPropertyPaneButton,
|
||||
} from "./StyledControls";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { AnyStyledComponent } from "styled-components";
|
||||
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
|
||||
|
||||
const StyledOptionControlWrapper = styled(ControlWrapper)`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledOptionControlInputGroup = styled(StyledInputGroup)`
|
||||
margin-right: 2px;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
&&& {
|
||||
input {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
&:focus {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDynamicInput = styled.div`
|
||||
width: 100%;
|
||||
&&& {
|
||||
input {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
&:focus {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
|
||||
padding: 0;
|
||||
position: relative;
|
||||
margin-left: 15px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
type RenderComponentProps = {
|
||||
index: number;
|
||||
item: {
|
||||
seriesName: string;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: string;
|
||||
}>;
|
||||
};
|
||||
length: number;
|
||||
deleteOption: Function;
|
||||
updateOption: Function;
|
||||
};
|
||||
|
||||
function DataControlComponent(props: RenderComponentProps) {
|
||||
const { deleteOption, updateOption, item, index, length } = props;
|
||||
return (
|
||||
<StyledOptionControlWrapper orientation={"VERTICAL"}>
|
||||
{length > 1 && (
|
||||
<StyledOptionControlWrapper orientation={"HORIZONTAL"}>
|
||||
<StyledOptionControlInputGroup
|
||||
type="text"
|
||||
placeholder="Series Name"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateOption(index, "seriesName", event.target.value);
|
||||
}}
|
||||
defaultValue={item.seriesName}
|
||||
/>
|
||||
<StyledDeleteIcon
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => {
|
||||
deleteOption(index);
|
||||
}}
|
||||
/>
|
||||
</StyledOptionControlWrapper>
|
||||
)}
|
||||
<StyledDynamicInput>
|
||||
<DynamicAutocompleteInput
|
||||
input={{
|
||||
value: item.data,
|
||||
onChange: (
|
||||
event: React.ChangeEvent<HTMLTextAreaElement> | string,
|
||||
) => {
|
||||
let value = event;
|
||||
if (typeof event !== "string") {
|
||||
value = event.target.value;
|
||||
}
|
||||
updateOption(index, "data", value);
|
||||
},
|
||||
}}
|
||||
meta={{
|
||||
error: "",
|
||||
touched: true,
|
||||
}}
|
||||
theme={"DARK"}
|
||||
singleLine={false}
|
||||
placeholder=""
|
||||
/>
|
||||
</StyledDynamicInput>
|
||||
</StyledOptionControlWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
class ChartDataControl extends BaseControl<ControlProps> {
|
||||
render() {
|
||||
const chartData: Array<{
|
||||
seriesName: string;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: string;
|
||||
}>;
|
||||
}> = this.props.propertyValue || [];
|
||||
const dataLength = chartData.length;
|
||||
return (
|
||||
<React.Fragment>
|
||||
{chartData.map((data, index) => {
|
||||
return (
|
||||
<DataControlComponent
|
||||
key={index}
|
||||
index={index}
|
||||
item={data}
|
||||
length={dataLength}
|
||||
deleteOption={this.deleteOption}
|
||||
updateOption={this.updateOption}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<StyledPropertyPaneButton
|
||||
text="Add Data"
|
||||
icon="plus"
|
||||
color="#FFFFFF"
|
||||
minimal
|
||||
onClick={this.addOption}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
deleteOption = (index: number) => {
|
||||
const chartData: object[] = this.props.propertyValue.slice();
|
||||
chartData.splice(index, 1);
|
||||
this.updateProperty(this.props.propertyName, chartData);
|
||||
};
|
||||
|
||||
updateOption = (
|
||||
index: number,
|
||||
propertyName: string,
|
||||
updatedValue: string,
|
||||
) => {
|
||||
const chartData: Array<{
|
||||
seriesName: string;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: string;
|
||||
}>;
|
||||
}> = this.props.propertyValue;
|
||||
this.updateProperty(
|
||||
this.props.propertyName,
|
||||
chartData.map((item, i) => {
|
||||
if (index === i) {
|
||||
if (propertyName === "seriesName") {
|
||||
item.seriesName = updatedValue;
|
||||
} else {
|
||||
item.data = JSON.parse(updatedValue);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
addOption = () => {
|
||||
const chartData: Array<{
|
||||
seriesName: string;
|
||||
data: Array<{
|
||||
x: string;
|
||||
y: string;
|
||||
}>;
|
||||
}> = this.props.propertyValue ? this.props.propertyValue.slice() : [];
|
||||
chartData.push({ seriesName: "", data: [{ x: "", y: "" }] });
|
||||
this.updateProperty(this.props.propertyName, chartData);
|
||||
};
|
||||
|
||||
static getControlType() {
|
||||
return "CHART_DATA";
|
||||
}
|
||||
}
|
||||
|
||||
export default ChartDataControl;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { ChangeEvent } from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
|
||||
import { EventOrValueHandler } from "redux-form";
|
||||
class CodeEditorControl extends BaseControl<ControlProps> {
|
||||
|
|
@ -25,7 +24,7 @@ class CodeEditorControl extends BaseControl<ControlProps> {
|
|||
this.updateProperty(this.props.propertyName, value);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "CODE_EDITOR";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ import React from "react";
|
|||
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledPropertyPaneButton } from "./StyledControls";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import { generateReactKey } from "utils/generators";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { AnyStyledComponent } from "styled-components";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { InputText } from "components/propertyControls/InputTextControl";
|
||||
import DynamicActionCreator from "components/editorComponents/DynamicActionCreator";
|
||||
import { ActionCreator } from "components/editorComponents/actioncreator/ActionCreator";
|
||||
|
||||
export interface ColumnAction {
|
||||
label: string;
|
||||
|
|
@ -54,7 +53,7 @@ class ColumnActionSelectorControl extends BaseControl<
|
|||
/>
|
||||
</Wrapper>
|
||||
<Wrapper>
|
||||
<DynamicActionCreator
|
||||
<ActionCreator
|
||||
value={columnAction.dynamicTrigger}
|
||||
isValid={(columnAction as any).isValid}
|
||||
validationMessage={(columnAction as any).message}
|
||||
|
|
@ -128,7 +127,7 @@ class ColumnActionSelectorControl extends BaseControl<
|
|||
this.updateProperty(this.props.propertyName, update);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "COLUMN_ACTION_SELECTOR";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,198 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledDatePicker } from "./StyledControls";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import {
|
||||
StyledDatePicker,
|
||||
StyledTimeZonePicker,
|
||||
StyledSwitch,
|
||||
} from "./StyledControls";
|
||||
import moment from "moment-timezone";
|
||||
import "../../../node_modules/@blueprintjs/timezone/lib/css/blueprint-timezone.css";
|
||||
import styled from "styled-components";
|
||||
import { TIMEZONE, ENABLE_TIME } from "constants/messages";
|
||||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
|
||||
const DatePickerControlWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&&& {
|
||||
input {
|
||||
background: ${props => props.theme.colors.paneTextBG};
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
font-size: ${props => props.theme.fontSizes[3]}px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const LabelWrapper = styled.div`
|
||||
color: ${props => props.theme.colors.paneText};
|
||||
margin-bottom: 4px;
|
||||
font-size: ${props => props.theme.fontSizes[3]}px;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
type DatePickerContorlState = {
|
||||
date: Date;
|
||||
dateValue: number;
|
||||
isTimeEnabled: boolean;
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
class DatePickerControl extends BaseControl<
|
||||
DatePickerControlProps,
|
||||
DatePickerContorlState
|
||||
> {
|
||||
constructor(props: DatePickerControlProps) {
|
||||
super(props);
|
||||
const timezone =
|
||||
this.props.propertyValue && this.props.propertyValue.timezone
|
||||
? this.props.propertyValue.timezone
|
||||
: moment.tz.guess();
|
||||
const isTimeEnabled =
|
||||
this.props.propertyValue && this.props.propertyValue.isTimeEnabled
|
||||
? this.props.propertyValue.isTimeEnabled
|
||||
: false;
|
||||
const dateValue =
|
||||
this.props.propertyValue && this.props.propertyValue.dateValue
|
||||
? this.props.propertyValue.dateValue
|
||||
: new Date().getTime();
|
||||
const offset = this.getOffset(timezone, dateValue);
|
||||
const date = new Date(dateValue + offset);
|
||||
this.state = {
|
||||
date,
|
||||
timezone,
|
||||
dateValue,
|
||||
isTimeEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const timezone =
|
||||
this.props.propertyValue && this.props.propertyValue.timezone
|
||||
? this.props.propertyValue.timezone
|
||||
: moment.tz.guess();
|
||||
const isTimeEnabled =
|
||||
this.props.propertyValue && this.props.propertyValue.isTimeEnabled
|
||||
? this.props.propertyValue.isTimeEnabled
|
||||
: false;
|
||||
const dateValue =
|
||||
this.props.propertyValue && this.props.propertyValue.dateValue
|
||||
? this.props.propertyValue.dateValue
|
||||
: new Date().getTime();
|
||||
const offset = this.getOffset(timezone, dateValue);
|
||||
const date = new Date(dateValue + offset);
|
||||
this.setState({
|
||||
date,
|
||||
timezone,
|
||||
dateValue,
|
||||
isTimeEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
getOffset = (timezone: string, timeStamp: number) => {
|
||||
return moment.tz.zone(timezone)!.utcOffset(timeStamp) * 60 * 1000;
|
||||
};
|
||||
|
||||
class DatePickerControl extends BaseControl<DatePickerControlProps> {
|
||||
render() {
|
||||
const now = moment();
|
||||
const year = now.get("year");
|
||||
const date = now.get("date");
|
||||
const month = now.get("month");
|
||||
const minDate = now.clone().set({ month, date: date - 1, year: year - 20 });
|
||||
const maxDate = now.clone().set({ month, date: date + 1, year: year + 20 });
|
||||
|
||||
return (
|
||||
<StyledDatePicker
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
placeholder={"DD/MM/YYYY"}
|
||||
showActionsBar={true}
|
||||
timePickerProps={{
|
||||
useAmPm: true,
|
||||
value: new Date(this.props.propertyValue),
|
||||
showArrowButtons: true,
|
||||
}}
|
||||
onChange={this.onDateSelected}
|
||||
value={new Date(this.props.propertyValue)}
|
||||
/>
|
||||
<DatePickerControlWrapper>
|
||||
<StyledDatePicker
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
placeholder="DD/MM/YYYY"
|
||||
showActionsBar
|
||||
timePrecision={
|
||||
this.state.isTimeEnabled ? TimePrecision.MINUTE : undefined
|
||||
}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
maxDate={maxDate.toDate()}
|
||||
minDate={minDate.toDate()}
|
||||
value={this.state.date}
|
||||
/>
|
||||
<LabelWrapper>{TIMEZONE}</LabelWrapper>
|
||||
<StyledTimeZonePicker
|
||||
onChange={this.onTimeZoneSelected}
|
||||
valueDisplayFormat="composite"
|
||||
showLocalTimezone
|
||||
value={this.state.timezone || moment.tz.guess()}
|
||||
/>
|
||||
<LabelWrapper>{ENABLE_TIME}</LabelWrapper>
|
||||
<StyledSwitch
|
||||
onChange={this.onToggle}
|
||||
checked={this.state.isTimeEnabled}
|
||||
large
|
||||
/>
|
||||
</DatePickerControlWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
return moment(date).format("DD/MM/YYYY");
|
||||
onToggle = () => {
|
||||
const { isTimeEnabled } = this.state;
|
||||
this.setState({ isTimeEnabled: !isTimeEnabled }, () => {
|
||||
this.updatePropertyValue();
|
||||
});
|
||||
};
|
||||
|
||||
parseDate = (dateStr: string): Date => {
|
||||
return moment(dateStr).toDate();
|
||||
onTimeZoneSelected = (timezone: string): void => {
|
||||
const { dateValue } = this.state;
|
||||
const offset = this.getOffset(timezone, dateValue);
|
||||
const dateVisible = new Date(dateValue + offset);
|
||||
this.setState({ timezone: timezone, date: dateVisible }, () => {
|
||||
this.updatePropertyValue();
|
||||
});
|
||||
};
|
||||
|
||||
onDateSelected = (date: Date): void => {
|
||||
this.updateProperty(this.props.propertyName, date);
|
||||
const { timezone } = this.state;
|
||||
const offset = this.getOffset(timezone, date.getTime());
|
||||
const dateValue = new Date(date.getTime() - offset).getTime();
|
||||
this.setState({ date: date, dateValue: dateValue }, () => {
|
||||
this.updatePropertyValue();
|
||||
});
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
updatePropertyValue = () => {
|
||||
const { dateValue, isTimeEnabled, timezone } = this.state;
|
||||
this.updateProperty(this.props.propertyName, {
|
||||
timezone: timezone,
|
||||
dateValue: dateValue,
|
||||
isTimeEnabled: isTimeEnabled,
|
||||
});
|
||||
};
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
let dateFormat = "DD/MM/YYYY";
|
||||
if (this.state.isTimeEnabled) {
|
||||
dateFormat = "DD/MM/YYYY HH:mm";
|
||||
}
|
||||
return moment(date).format(dateFormat);
|
||||
};
|
||||
|
||||
parseDate = (dateStr: string): Date => {
|
||||
const date = moment(dateStr).toDate();
|
||||
return date;
|
||||
};
|
||||
|
||||
static getControlType() {
|
||||
return "DATE_PICKER";
|
||||
}
|
||||
}
|
||||
|
||||
export interface DatePickerControlProps extends ControlProps {
|
||||
placeholderText: string;
|
||||
propertyValue: Date;
|
||||
enableTimePicker: boolean;
|
||||
propertyValue: {
|
||||
timezone: string;
|
||||
dateValue: number;
|
||||
isTimeEnabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export default DatePickerControl;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { Button, MenuItem } from "@blueprintjs/core";
|
|||
import { IItemRendererProps } from "@blueprintjs/select";
|
||||
import { StyledDropDown, StyledDropDownContainer } from "./StyledControls";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
|
||||
class DropDownControl extends BaseControl<DropDownControlProps> {
|
||||
render() {
|
||||
|
|
@ -57,7 +56,7 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
|
|||
return selectedOption.value === this.props.propertyValue;
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "DROP_DOWN";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React from "react";
|
|||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledDynamicInput } from "./StyledControls";
|
||||
import { InputType } from "widgets/InputWidget";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
|
||||
|
||||
export function InputText(props: {
|
||||
|
|
@ -75,7 +74,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
this.updateProperty(this.props.propertyName, value);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "INPUT_TEXT";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import React, { MutableRefObject } from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import styled from "styled-components";
|
||||
import SearchBox from "react-google-maps/lib/components/places/SearchBox";
|
||||
import StandaloneSearchBox from "react-google-maps/lib/components/places/StandaloneSearchBox";
|
||||
const { compose, withProps, lifecycle } = require("recompose");
|
||||
const { withScriptjs } = require("react-google-maps");
|
||||
|
||||
const StyledInput = styled.input`
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
text-overflow: ellipses;
|
||||
background: #272821;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
`;
|
||||
|
||||
interface StandaloneSearchBoxProps {
|
||||
onSearchBoxMounted: (ref: SearchBoxRef) => void;
|
||||
onPlacesChanged: () => void;
|
||||
}
|
||||
|
||||
type SearchBoxRef = MutableRefObject<StandaloneSearchBox | null>;
|
||||
|
||||
const PlacesWithStandaloneSearchBox = compose(
|
||||
withProps({
|
||||
googleMapURL:
|
||||
"https://maps.googleapis.com/maps/api/js?key=AIzaSyC2H_twoNbEKMm9Q0nYAh7715Dplg2asCI&v=3.exp&libraries=geometry,drawing,places",
|
||||
loadingElement: <div style={{ height: `100%` }} />,
|
||||
containerElement: <div style={{ height: `400px` }} />,
|
||||
}),
|
||||
lifecycle({
|
||||
componentWillMount() {
|
||||
let searchBox: SearchBoxRef = React.createRef<SearchBox>();
|
||||
this.setState({
|
||||
places: [],
|
||||
onSearchBoxMounted: (ref: SearchBoxRef) => {
|
||||
searchBox = ref;
|
||||
},
|
||||
onPlacesChanged: () => {
|
||||
if (searchBox === null) return;
|
||||
if (searchBox.current === null) return;
|
||||
if (searchBox.current.getPlaces === null) return;
|
||||
const places = searchBox.current.getPlaces();
|
||||
this.setState({
|
||||
places,
|
||||
});
|
||||
this.props.onLocationSelection(places);
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
withScriptjs,
|
||||
)((props: any) => (
|
||||
<div data-standalone-searchbox="">
|
||||
<StandaloneSearchBox
|
||||
ref={props.onSearchBoxMounted}
|
||||
onPlacesChanged={props.onPlacesChanged}
|
||||
>
|
||||
<StyledInput type="text" placeholder="Enter location" />
|
||||
</StandaloneSearchBox>
|
||||
</div>
|
||||
));
|
||||
|
||||
class LocationSearchControl extends BaseControl<ControlProps> {
|
||||
onLocationSelection = (
|
||||
places: Array<{
|
||||
geometry: { location: { lat: () => number; lng: () => number } };
|
||||
}>,
|
||||
) => {
|
||||
const location = places[0].geometry.location;
|
||||
const lat = location.lat();
|
||||
const lng = location.lng();
|
||||
const value = { lat, lng };
|
||||
this.updateProperty(this.props.propertyName, value);
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<PlacesWithStandaloneSearchBox
|
||||
onLocationSelection={this.onLocationSelection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static getControlType() {
|
||||
return "LOCATION_SEARCH";
|
||||
}
|
||||
}
|
||||
|
||||
export default LocationSearchControl;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import { MenuItem } from "@blueprintjs/core";
|
||||
import { IItemRendererProps } from "@blueprintjs/select";
|
||||
import { StyledMultiSelectDropDown } from "./StyledControls";
|
||||
|
|
@ -81,7 +80,7 @@ class MultiSelectControl extends BaseControl<MultiSelectControlProps> {
|
|||
);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "MULTI_SELECT";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { IconName, ButtonGroup, Button, Classes } from "@blueprintjs/core";
|
||||
import { IconNames } from "@blueprintjs/icons";
|
||||
const iconNames: string[] = Object.values({ ...IconNames });
|
||||
|
||||
class MultiSwitchControl extends BaseControl<MultiSwitchControlProps> {
|
||||
renderOption = (option: SwitchOption) => {
|
||||
const isIcon: boolean =
|
||||
!!option.icon && iconNames.indexOf(option.icon) > -1;
|
||||
return (
|
||||
<Button
|
||||
key={option.label || option.icon}
|
||||
icon={isIcon ? (option.icon as IconName) : undefined}
|
||||
text={!isIcon && option.label}
|
||||
active={this.props.propertyValue === option.value}
|
||||
onClick={() =>
|
||||
this.updateProperty(this.props.propertyName, option.value)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<ButtonGroup className={Classes.DARK}>
|
||||
{this.props.options.map(this.renderOption)}
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
static getControlType() {
|
||||
return "MULTI_SWITCH";
|
||||
}
|
||||
}
|
||||
|
||||
interface SwitchOption {
|
||||
label?: string;
|
||||
icon?: string;
|
||||
altText?: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface MultiSwitchControlProps extends ControlProps {
|
||||
options: SwitchOption[];
|
||||
}
|
||||
|
||||
export default MultiSwitchControl;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { DropdownOption } from "widgets/DropdownWidget";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import { KeyValueComponent } from "./KeyValueComponent";
|
||||
|
||||
export type DropDownOptionWithKey = DropdownOption & {
|
||||
|
|
@ -22,7 +21,7 @@ class OptionControl extends BaseControl<ControlProps> {
|
|||
this.updateProperty("options", options);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "OPTION_INPUT";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
app/client/src/components/propertyControls/StepControl.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import StepComponent from "components/designSystems/appsmith/StepComponent";
|
||||
|
||||
class StepControl extends BaseControl<StepControlProps> {
|
||||
getStepTypeControls = () => {
|
||||
const { stepType } = this.props;
|
||||
if (stepType === "ZOOM_PERCENTAGE") {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
steps: 5,
|
||||
displayFormat: (value: number): string => {
|
||||
return `${value}%`;
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
steps: 1,
|
||||
displayFormat: (value: number): string => {
|
||||
return `${value}`;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const { min, max, steps, displayFormat } = this.getStepTypeControls();
|
||||
return (
|
||||
<StepComponent
|
||||
min={min}
|
||||
max={max}
|
||||
steps={steps}
|
||||
value={this.props.propertyValue}
|
||||
onChange={(value: number) => {
|
||||
this.updateProperty(this.props.propertyName, value);
|
||||
}}
|
||||
displayFormat={displayFormat}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
static getControlType() {
|
||||
return "STEP";
|
||||
}
|
||||
}
|
||||
|
||||
export type StepType = "ZOOM_PERCENTAGE";
|
||||
|
||||
export interface StepControlProps extends ControlProps {
|
||||
stepType: StepType;
|
||||
}
|
||||
|
||||
export default StepControl;
|
||||
|
|
@ -154,6 +154,9 @@ export const StyledDropDown = styled(DropDown)`
|
|||
`;
|
||||
|
||||
export const StyledPopover = styled(Popover)`
|
||||
.${Classes.POPOVER_TARGET} {
|
||||
display: flex;
|
||||
}
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
|
@ -188,6 +191,9 @@ export const StyledMenuItem = styled(MenuItem)`
|
|||
border-radius: ${props => props.theme.radii[1]}px;
|
||||
&:hover {
|
||||
background: ${Colors.POLAR};
|
||||
&&&.bp3-menu-item.bp3-intent-danger:hover {
|
||||
background: ${props => props.theme.colors.error};
|
||||
}
|
||||
}
|
||||
&.${Classes.ACTIVE} {
|
||||
background: ${Colors.POLAR};
|
||||
|
|
@ -225,6 +231,7 @@ export const StyledSwitch = styled(Switch)`
|
|||
`;
|
||||
|
||||
export const StyledDynamicInput = styled.div`
|
||||
width: 100%;
|
||||
&&& {
|
||||
input {
|
||||
border: none;
|
||||
|
|
@ -248,14 +255,11 @@ export const StyledInputGroup = styled(InputGroup)`
|
|||
`;
|
||||
|
||||
export const StyledDatePicker = styled(DateInput)`
|
||||
& {
|
||||
input {
|
||||
placeholderText: ${props => props.placeholder};
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
}
|
||||
> input {
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
border: 1px solid green;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledTimeZonePicker = styled(TimezonePicker)`
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledSwitch } from "./StyledControls";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
|
||||
class SwitchControl extends BaseControl<ControlProps> {
|
||||
render() {
|
||||
|
|
@ -18,7 +17,7 @@ class SwitchControl extends BaseControl<ControlProps> {
|
|||
this.updateProperty(this.props.propertyName, !this.props.propertyValue);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "SWITCH";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
180
app/client/src/components/propertyControls/TabControl.tsx
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledInputGroup, StyledPropertyPaneButton } from "./StyledControls";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import { ControlIcons } from "icons/ControlIcons";
|
||||
import { AnyStyledComponent } from "styled-components";
|
||||
import { DragDropContext } from "react-beautiful-dnd";
|
||||
import { generateReactKey } from "utils/generators";
|
||||
import { DroppableComponent } from "../designSystems/appsmith/DraggableListComponent";
|
||||
|
||||
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
|
||||
padding: 0;
|
||||
position: relative;
|
||||
margin-left: 15px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledDragIcon = styled(
|
||||
ControlIcons.DRAGGABLE_CONTROL as AnyStyledComponent,
|
||||
)`
|
||||
padding: 0;
|
||||
position: relative;
|
||||
margin-left: 15px;
|
||||
cursor: move;
|
||||
svg {
|
||||
path {
|
||||
fill: ${props => props.theme.colors.paneSectionLabel};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPropertyPaneButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
const StyledOptionControlInputGroup = styled(StyledInputGroup)`
|
||||
margin-right: 2px;
|
||||
&&& {
|
||||
input {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
&:focus {
|
||||
border: none;
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
background: ${props => props.theme.colors.paneInputBG};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type RenderComponentProps = {
|
||||
index: number;
|
||||
item: {
|
||||
label: string;
|
||||
};
|
||||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, value: string) => void;
|
||||
};
|
||||
|
||||
function TabControlComponent(props: RenderComponentProps) {
|
||||
const { deleteOption, updateOption, item, index } = props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<StyledOptionControlInputGroup
|
||||
type="text"
|
||||
placeholder="Tab Title"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateOption(index, event.target.value);
|
||||
}}
|
||||
defaultValue={item.label}
|
||||
/>
|
||||
<StyledDeleteIcon
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={() => {
|
||||
deleteOption(index);
|
||||
}}
|
||||
/>
|
||||
<StyledDragIcon height={20} width={20} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
class TabControl extends BaseControl<ControlProps> {
|
||||
onDragEnd = (result: any) => {
|
||||
const { destination, source } = result;
|
||||
if (!destination) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
destination.droppableId === source.droppableId &&
|
||||
destination.index === source.index
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const tabs: string[] = this.props.propertyValue || [""];
|
||||
const sourceTab = tabs[source.index];
|
||||
const destinationTab = tabs[destination.index];
|
||||
this.updateProperty(
|
||||
this.props.propertyName,
|
||||
tabs.map((tab, index) => {
|
||||
if (index === source.index) {
|
||||
return destinationTab;
|
||||
} else if (index === destination.index) {
|
||||
return sourceTab;
|
||||
}
|
||||
return tab;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = this.props.propertyValue || [{ id: "" }];
|
||||
return (
|
||||
<DragDropContext onDragEnd={this.onDragEnd}>
|
||||
<DroppableComponent
|
||||
items={tabs}
|
||||
renderComponent={TabControlComponent}
|
||||
deleteOption={this.deleteOption}
|
||||
updateOption={this.updateOption}
|
||||
/>
|
||||
<StyledPropertyPaneButtonWrapper>
|
||||
<StyledPropertyPaneButton
|
||||
text="Add a Tab"
|
||||
color="#FFFFFF"
|
||||
minimal
|
||||
onClick={this.addOption}
|
||||
/>
|
||||
</StyledPropertyPaneButtonWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
|
||||
deleteOption = (index: number) => {
|
||||
const tabs: object[] = this.props.propertyValue.slice();
|
||||
tabs.splice(index, 1);
|
||||
this.updateProperty(this.props.propertyName, tabs);
|
||||
};
|
||||
|
||||
updateOption = (index: number, updatedLabel: string) => {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = this.props.propertyValue;
|
||||
this.updateProperty(
|
||||
this.props.propertyName,
|
||||
tabs.map((tab, tabIndex) => {
|
||||
if (index === tabIndex) {
|
||||
tab.label = updatedLabel;
|
||||
}
|
||||
return tab;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
addOption = () => {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = this.props.propertyValue ? this.props.propertyValue.slice() : [];
|
||||
const newTabId = generateReactKey({ prefix: "tab" });
|
||||
tabs.push({ id: newTabId, label: `Tab ${tabs.length + 1}` });
|
||||
this.updateProperty(this.props.propertyName, tabs);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
return "TABS_INPUT";
|
||||
}
|
||||
}
|
||||
|
||||
export default TabControl;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledTimeZonePicker } from "./StyledControls";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import moment from "moment-timezone";
|
||||
import "../../../node_modules/@blueprintjs/timezone/lib/css/blueprint-timezone.css";
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ class TimeZoneControl extends BaseControl<TimeZoneControlProps> {
|
|||
this.updateProperty(this.props.propertyName, timezone);
|
||||
};
|
||||
|
||||
getControlType(): ControlType {
|
||||
static getControlType() {
|
||||
return "TIME_ZONE";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
app/client/src/components/propertyControls/index.test.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { getPropertyControlTypes } from "./index";
|
||||
import _ from "lodash";
|
||||
|
||||
const types = getPropertyControlTypes();
|
||||
|
||||
it("Checks for uniqueness of control types", () => {
|
||||
const result = Object.keys(getPropertyControlTypes());
|
||||
const output = _.uniq(result);
|
||||
expect(types.length).toEqual(output.length);
|
||||
});
|
||||
67
app/client/src/components/propertyControls/index.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import InputTextControl, {
|
||||
InputControlProps,
|
||||
} from "components/propertyControls/InputTextControl";
|
||||
import DropDownControl, {
|
||||
DropDownControlProps,
|
||||
} from "components/propertyControls/DropDownControl";
|
||||
import SwitchControl, {
|
||||
SwitchControlProps,
|
||||
} from "components/propertyControls/SwitchControl";
|
||||
import OptionControl from "components/propertyControls/OptionControl";
|
||||
import BaseControl, {
|
||||
ControlProps,
|
||||
} from "components/propertyControls/BaseControl";
|
||||
import CodeEditorControl from "components/propertyControls/CodeEditorControl";
|
||||
import MultiSelectControl, {
|
||||
MultiSelectControlProps,
|
||||
} from "components/propertyControls/MultiSelectControl";
|
||||
import DatePickerControl, {
|
||||
DatePickerControlProps,
|
||||
} from "components/propertyControls/DatePickerControl";
|
||||
import ChartDataControl from "components/propertyControls/ChartDataControl";
|
||||
import LocationSearchControl from "components/propertyControls/LocationSearchControl";
|
||||
import StepControl, {
|
||||
StepControlProps,
|
||||
} from "components/propertyControls/StepControl";
|
||||
import ActionSelectorControl from "components/propertyControls/ActionSelectorControl";
|
||||
import ColumnActionSelectorControl from "components/propertyControls/ColumnActionSelectorControl";
|
||||
import MultiSwitchControl, {
|
||||
MultiSwitchControlProps,
|
||||
} from "components/propertyControls/MultiSwitchControl";
|
||||
|
||||
export const PropertyControls = {
|
||||
InputTextControl,
|
||||
DropDownControl,
|
||||
SwitchControl,
|
||||
OptionControl,
|
||||
CodeEditorControl,
|
||||
MultiSelectControl,
|
||||
DatePickerControl,
|
||||
ActionSelectorControl,
|
||||
ColumnActionSelectorControl,
|
||||
MultiSwitchControl,
|
||||
ChartDataControl,
|
||||
LocationSearchControl,
|
||||
StepControl,
|
||||
};
|
||||
|
||||
export type PropertyControlPropsType =
|
||||
| ControlProps
|
||||
| InputControlProps
|
||||
| DropDownControlProps
|
||||
| SwitchControlProps
|
||||
| MultiSelectControlProps
|
||||
| DatePickerControlProps
|
||||
| MultiSwitchControlProps
|
||||
| StepControlProps;
|
||||
|
||||
export const getPropertyControlTypes = (): { [key: string]: string } => {
|
||||
const _types: { [key: string]: string } = {};
|
||||
Object.values(PropertyControls).forEach(
|
||||
(Control: typeof BaseControl & { getControlType: () => string }) => {
|
||||
const controlType = Control.getControlType();
|
||||
_types[controlType] = controlType;
|
||||
},
|
||||
);
|
||||
return _types;
|
||||
};
|
||||
1
app/client/src/components/utils/AppsmithLoader.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export default null;
|
||||
19
app/client/src/components/utils/Skeleton.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
type SkeletonProps = {
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export const Skeleton = () => {
|
||||
return <StyledDiv className={Classes.SKELETON} />;
|
||||
};
|
||||
|
||||
export default Skeleton;
|
||||
|
|
@ -33,6 +33,8 @@ export enum EventType {
|
|||
ON_DATE_SELECTED = "ON_DATE_SELECTED",
|
||||
ON_DATE_RANGE_SELECTED = "ON_DATE_RANGE_SELECTED",
|
||||
ON_OPTION_CHANGE = "ON_OPTION_CHANGE",
|
||||
ON_MARKER_CLICK = "ON_MARKER_CLICK",
|
||||
ON_CREATE_MARKER = "ON_CREATE_MARKER",
|
||||
}
|
||||
|
||||
export type ActionType =
|
||||
|
|
|
|||
|
|
@ -8,9 +8,17 @@ export const HTTP_METHOD_OPTIONS = HTTP_METHODS.map(method => ({
|
|||
}));
|
||||
|
||||
export const PLUGIN_NAME = "RestTemplatePluginExecutor";
|
||||
|
||||
export const REST_PLUGIN_PACKAGE_NAME = "restapi-plugin";
|
||||
export const DEFAULT_API_ACTION: Partial<RestAction> = {
|
||||
actionConfiguration: {
|
||||
httpMethod: HTTP_METHODS[0],
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
||||
export const POST_BODY_FORMATS = ["application/json", "x-www-form-urlencoded"];
|
||||
|
||||
export const POST_BODY_FORMAT_OPTIONS = POST_BODY_FORMATS.map(method => ({
|
||||
label: method,
|
||||
value: method,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export const Colors: Record<string, string> = {
|
|||
AZURE_RADIANCE: "#0384FE",
|
||||
OCEAN_GREEN: "#36AB80",
|
||||
BUTTER_CUP: "#F7AF22",
|
||||
BLUE_CHARCOAL: "#23292E",
|
||||
TROUT: "#4C565E",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ export const theme: Theme = {
|
|||
paneInputBG: Colors.SHARK,
|
||||
paneBG: Colors.OUTER_SPACE,
|
||||
paneText: Colors.GRAY_CHATEAU,
|
||||
paneTextBG: Colors.DEEP_SPACE,
|
||||
paneTextUnderline: Colors.LIGHT_GREYISH_BLUE,
|
||||
paneSectionLabel: Colors.CADET_BLUE,
|
||||
navBG: Colors.SHARK,
|
||||
|
|
@ -384,7 +385,7 @@ export const theme: Theme = {
|
|||
headerHeight: "50px",
|
||||
canvasPadding: "20px 0 200px 0",
|
||||
sideNav: {
|
||||
maxWidth: 300,
|
||||
maxWidth: 220,
|
||||
minWidth: 50,
|
||||
bgColor: Colors.OXFORD_BLUE,
|
||||
fontColor: Colors.WHITE,
|
||||
|
|
@ -414,6 +415,8 @@ export const theme: Theme = {
|
|||
shadows: [
|
||||
"0px 2px 4px rgba(67, 70, 74, 0.14)",
|
||||
`0px 2px 4px ${Colors.MYSTIC}`,
|
||||
`inset -1px 0px 0px ${Colors.ATHENS_GRAY}, inset 1px 0px 0px ${Colors.ATHENS_GRAY}, inset 0px 4px 0px ${Colors.GREEN}`,
|
||||
`inset -1px 0px 0px ${Colors.ATHENS_GRAY}, inset 1px 0px 0px ${Colors.ATHENS_GRAY}, inset 0px 1px 0px ${Colors.ATHENS_GRAY}`,
|
||||
],
|
||||
widgets: {
|
||||
tableWidget: {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,3 @@
|
|||
export type ControlType =
|
||||
| "INPUT_TEXT"
|
||||
| "RICH_TEXT_EDITOR"
|
||||
| "ICON_PICKER"
|
||||
| "SEGMENT_CONTROL"
|
||||
| "SWITCH"
|
||||
| "CHECKBOX"
|
||||
| "DATE_PICKER"
|
||||
| "DROP_DOWN"
|
||||
| "MULTI_SELECT"
|
||||
| "COLOR_PICKER"
|
||||
| "TIMEZONE_PICKER"
|
||||
| "ACTION_SELECTOR"
|
||||
| "RECORD_ACTION_SELECTOR"
|
||||
| "OPTION_INPUT"
|
||||
| "IMAGE_PICKER"
|
||||
| "SHAPE_PICKER"
|
||||
| "VALIDATION_INPUT"
|
||||
| "ZOOM"
|
||||
| "TIME_ZONE"
|
||||
| "CODE_EDITOR"
|
||||
| "COLUMN_ACTION_SELECTOR";
|
||||
import { getPropertyControlTypes } from "components/propertyControls";
|
||||
const ControlTypes = getPropertyControlTypes();
|
||||
export type ControlType = typeof ControlTypes[keyof typeof ControlTypes];
|
||||
|
|
|
|||