diff --git a/app/client/cypress/fixtures/Js_toggle_dsl.json b/app/client/cypress/fixtures/Js_toggle_dsl.json
index 864e137c8f..ad292ab6e2 100644
--- a/app/client/cypress/fixtures/Js_toggle_dsl.json
+++ b/app/client/cypress/fixtures/Js_toggle_dsl.json
@@ -61,7 +61,7 @@
"options": "[{'label':'Vegetarian','value':'VEG'},{'label':'Non-Vegetarian','value':'NON_VEG'},{'label':'Vegan','value':'VEGAN'}]",
"widgetName": "Dropdown1",
"defaultOptionValue": "VEG",
- "type": "DROP_DOWN_WIDGET",
+ "type": "SELECT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
diff --git a/app/client/cypress/fixtures/formSelectTreeselectDsl.json b/app/client/cypress/fixtures/formSelectTreeselectDsl.json
index 79b4647b61..0bc7b07d4f 100644
--- a/app/client/cypress/fixtures/formSelectTreeselectDsl.json
+++ b/app/client/cypress/fixtures/formSelectTreeselectDsl.json
@@ -127,7 +127,7 @@
"topRow": 8,
"bottomRow": 15,
"parentRowSpace": 10,
- "type": "DROP_DOWN_WIDGET",
+ "type": "SELECT_WIDGET",
"serverSideFiltering": false,
"hideCard": false,
"defaultOptionValue": "GREEN",
diff --git a/app/client/cypress/fixtures/formdsl.json b/app/client/cypress/fixtures/formdsl.json
index f8859c48a5..fa5e8ed94a 100644
--- a/app/client/cypress/fixtures/formdsl.json
+++ b/app/client/cypress/fixtures/formdsl.json
@@ -65,7 +65,7 @@
"parentRowSpace": 38,
"isVisible": true,
"label": "Test Dropdown",
- "type": "DROP_DOWN_WIDGET",
+ "type": "SELECT_WIDGET",
"dynamicBindingPathList": [],
"isLoading": false,
"selectionType": "",
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Multipart_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Multipart_spec.js
index 5b6d2f3154..9e6ad04537 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Multipart_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ApiPaneTests/API_Multipart_spec.js
@@ -25,6 +25,7 @@ describe("API Panel request body", function() {
cy.SelectAction(testdata.getAction);
cy.contains(apiEditor.bodyTab).click();
+ cy.get(`[data-cy=${testdata.apiContentTypeNone}]`).click();
cy.get(testdata.noBodyErrorMessageDiv).should("exist");
cy.get(testdata.noBodyErrorMessageDiv).contains(
testdata.noBodyErrorMessage,
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
index 872fd5bf90..e6d1043c6e 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
@@ -14,13 +14,11 @@ describe("Check debugger logs state when there are onPageLoad actions", function
cy.CreateAPI("TestApi");
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
cy.SaveAndRunAPI();
-
cy.get(explorer.addWidget).click();
-
cy.reload();
// Wait for the debugger icon to be visible
cy.get(".t--debugger").should("be.visible");
- cy.get(debuggerLocators.errorCount).should("not.exist");
+ //cy.get(debuggerLocators.errorCount).should("not.exist");
cy.wait("@postExecute");
cy.contains(debuggerLocators.errorCount, 1);
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_Widget_Loading_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_Widget_Loading_spec.js
index 068753ff44..623ff378d5 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_Widget_Loading_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_Widget_Loading_spec.js
@@ -81,11 +81,12 @@ describe("Chart Widget Skeleton Loading Functionality", function() {
//Step9:
cy.get(".bp3-button-text")
.first()
- .click();
+ .click({ force: true });
//Step10:
cy.get(".t--widget-chartwidget div[class*='bp3-skeleton']").should("exist");
+ /* This section is flaky hence commenting out
//Step11:
cy.reload();
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Dropdown_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Dropdown_spec.js
index b4605d47fe..cb628053f0 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Dropdown_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Dropdown_spec.js
@@ -1,10 +1,16 @@
const dsl = require("../../../../fixtures/emptyDSL.json");
const explorer = require("../../../../locators/explorerlocators.json");
+const formWidgetsPage = require("../../../../locators/FormWidgets.json");
+const commonlocators = require("../../../../locators/commonlocators.json");
+const publish = require("../../../../locators/publishWidgetspage.json");
describe("Dropdown Widget Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
+ beforeEach(() => {
+ cy.wait(7000);
+ });
it("Add new dropdown widget", () => {
cy.get(explorer.addWidget).click();
@@ -36,7 +42,7 @@ describe("Dropdown Widget Functionality", function() {
);
});
- it("should check that more thatn empty value is not allowed in options", () => {
+ it("should check that more than one empty value is not allowed in options", () => {
cy.openPropertyPane("selectwidget");
cy.updateCodeInput(
".t--property-control-options",
@@ -59,4 +65,61 @@ describe("Dropdown Widget Functionality", function() {
"exist",
);
});
+
+ it.skip("should check that Objects can be added to Select Widget default value", () => {
+ cy.openPropertyPane("selectwidget");
+ cy.updateCodeInput(
+ ".t--property-control-options",
+ `[
+ {
+ "label": "Blue",
+ "value": "BLUE"
+ },
+ {
+ "label": "Green",
+ "value": "GREEN"
+ },
+ {
+ "label": "Red",
+ "value": "RED"
+ }
+ ]`,
+ );
+ cy.updateCodeInput(
+ ".t--property-control-defaultvalue",
+ `
+ {
+ "label": "Green",
+ "value": "GREEN"
+ }
+ `,
+ );
+ cy.get(".t--property-control-options .t--codemirror-has-error").should(
+ "not.exist",
+ );
+ cy.get(".t--property-control-defaultvalue .t--codemirror-has-error").should(
+ "not.exist",
+ );
+ cy.get(formWidgetsPage.dropdownDefaultButton).should("contain", "Green");
+ });
+
+ it("Dropdown Functionality To Check disabled Widget", function() {
+ cy.openPropertyPane("selectwidget");
+ // Disable the visible JS
+ cy.togglebarDisable(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ // Verify the disabled visible JS
+ cy.get(publish.selectwidget + " " + "input").should("not.exist");
+ cy.goToEditFromPublish();
+ });
+
+ it("Dropdown Functionality To UnCheck disabled Widget", function() {
+ cy.openPropertyPane("selectwidget");
+ // Check the visible JS
+ cy.togglebar(commonlocators.visibleCheckbox);
+ cy.PublishtheApp();
+ // Verify the checked visible JS
+ cy.get(publish.selectwidget).should("exist");
+ cy.goToEditFromPublish();
+ });
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/MultiSelect_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/MultiSelect_spec.js
index b29375b619..7f435bdacc 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/MultiSelect_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/MultiSelect_spec.js
@@ -1,12 +1,15 @@
const dsl = require("../../../../fixtures/emptyDSL.json");
const explorer = require("../../../../locators/explorerlocators.json");
+const formWidgetsPage = require("../../../../locators/FormWidgets.json");
describe("MultiSelect Widget Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
-
- it("Add new dropdown widget", () => {
+ beforeEach(() => {
+ cy.wait(7000);
+ });
+ it("Add new multiselect widget", () => {
cy.get(explorer.addWidget).click();
cy.dragAndDropToCanvas("multiselectwidgetv2", { x: 300, y: 300 });
cy.get(".t--widget-multiselectwidgetv2").should("exist");
@@ -36,7 +39,7 @@ describe("MultiSelect Widget Functionality", function() {
);
});
- it("should check that more thatn empty value is not allowed in options", () => {
+ it("should check that more that one empty value is not allowed in options", () => {
cy.openPropertyPane("multiselectwidgetv2");
cy.updateCodeInput(
".t--property-control-options",
@@ -59,4 +62,44 @@ describe("MultiSelect Widget Functionality", function() {
"exist",
);
});
+ it("should check that Objects can be added to multiselect Widget default value", () => {
+ cy.openPropertyPane("multiselectwidgetv2");
+ cy.updateCodeInput(
+ ".t--property-control-options",
+ `[
+ {
+ "label": "Blue",
+ "value": ""
+ },
+ {
+ "label": "Green",
+ "value": "GREEN"
+ },
+ {
+ "label": "Red",
+ "value": "RED"
+ }
+ ]`,
+ );
+ cy.updateCodeInput(
+ ".t--property-control-defaultvalue",
+ `[
+ {
+ "label": "Green",
+ "value": "GREEN"
+ }
+ ]`,
+ );
+ cy.get(".t--property-control-options .t--codemirror-has-error").should(
+ "not.exist",
+ );
+ cy.get(".t--property-control-defaultvalue .t--codemirror-has-error").should(
+ "not.exist",
+ );
+ cy.wait(100);
+ cy.get(formWidgetsPage.multiselectwidgetv2)
+ .find(".rc-select-selection-item-content")
+ .first()
+ .should("have.text", "Green");
+ });
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/PreviewMode_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/PreviewMode_spec.js
index 9161d94201..dced5ce447 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/PreviewMode_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/PreviewMode/PreviewMode_spec.js
@@ -1,4 +1,6 @@
const dsl = require("../../../../fixtures/previewMode.json");
+const commonlocators = require("../../../../locators/commonlocators.json");
+const publishPage = require("../../../../locators/publishWidgetspage.json");
describe("Preview mode functionality", function() {
before(() => {
@@ -26,4 +28,25 @@ describe("Preview mode functionality", function() {
`${selector}:first-of-type .t--widget-propertypane-toggle > .t--widget-name`,
).should("not.exist");
});
+
+ it("check invisible widget should not show in proview mode and should show in edit mode", function() {
+ cy.get(".t--switch-comment-mode-off").click();
+ cy.openPropertyPane("buttonwidget");
+ cy.UncheckWidgetProperties(commonlocators.visibleCheckbox);
+
+ // button should not show in preview mode
+ cy.get(".t--switch-preview-mode-toggle").click();
+ cy.get(`${publishPage.buttonWidget} button`).should("not.exist");
+
+ // Text widget should show
+ cy.get(`${publishPage.textWidget} .bp3-ui-text`).should("exist");
+
+ // button should show in edit mode
+ cy.get(".t--switch-comment-mode-off").click();
+ cy.get(`${publishPage.buttonWidget} button`).should("exist");
+ });
+
+ afterEach(() => {
+ // put your clean up code if any
+ });
});
diff --git a/app/client/cypress/locators/FormWidgets.json b/app/client/cypress/locators/FormWidgets.json
index 86b0a178a0..de86eb7b3f 100644
--- a/app/client/cypress/locators/FormWidgets.json
+++ b/app/client/cypress/locators/FormWidgets.json
@@ -1,7 +1,7 @@
{
"checkboxWidget": ".t--draggable-checkboxwidget",
"selectwidget": ".t--draggable-selectwidget",
- "dropdownWidget": ".t--draggable-dropdownwidget",
+ "dropdownWidget": ".t--draggable-selectwidget",
"menuButtonWidget": ".t--draggable-menubuttonwidget",
"multiselectwidgetv2": ".t--draggable-multiselectwidgetv2",
"multiselecttreeWidget": ".t--draggable-multiselecttreewidget",
diff --git a/app/client/src/comments/dsl.json b/app/client/src/comments/dsl.json
index d1ec3cf4e1..be0b1974a2 100644
--- a/app/client/src/comments/dsl.json
+++ b/app/client/src/comments/dsl.json
@@ -183,7 +183,7 @@
"widgetName":"Dropdown1",
"defaultOptionValue":"VEG",
"version":1,
- "type":"DROP_DOWN_WIDGET",
+ "type":"SELECT_WIDGET",
"isLoading":false,
"parentColumnSpace":60.131249999999994,
"parentRowSpace":40,
diff --git a/app/client/src/components/ads/TextInput.tsx b/app/client/src/components/ads/TextInput.tsx
index 8255be1b3f..ab371f8702 100644
--- a/app/client/src/components/ads/TextInput.tsx
+++ b/app/client/src/components/ads/TextInput.tsx
@@ -300,7 +300,7 @@ const TextInput = forwardRef(
const [isFocused, setIsFocused] = useState(false);
const [inputValue, setInputValue] = useState(props.defaultValue);
- const { trimValue = true } = props;
+ const { trimValue = false } = props;
const setRightSideRef = useCallback((ref: HTMLDivElement) => {
if (ref) {
diff --git a/app/client/src/components/editorComponents/PreviewModeComponent.tsx b/app/client/src/components/editorComponents/PreviewModeComponent.tsx
new file mode 100644
index 0000000000..e076ddf324
--- /dev/null
+++ b/app/client/src/components/editorComponents/PreviewModeComponent.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import { useSelector } from "react-redux";
+import { previewModeSelector } from "selectors/editorSelectors";
+
+type Props = {
+ children: React.ReactNode;
+ isVisible?: boolean;
+};
+
+/**
+ * render only visible components in preview mode
+ */
+function PreviewModeComponent({
+ children,
+ isVisible,
+}: Props): React.ReactElement {
+ const isPreviewMode = useSelector(previewModeSelector);
+ if (!isPreviewMode || isVisible) return children as React.ReactElement;
+ else return
;
+}
+
+export default PreviewModeComponent;
diff --git a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx
index 2e8f62b692..7d5a55304b 100644
--- a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx
+++ b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx
@@ -814,7 +814,7 @@ class DatasourceRestAPIEditor extends React.Component {
value: "HEADER",
},
],
- "Send client credentials with",
+ "Send client credentials with (on refresh token):",
"",
false,
"",
diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts
index dfb91b387f..d56e79e7a6 100644
--- a/app/client/src/sagas/ApiPaneSagas.ts
+++ b/app/client/src/sagas/ApiPaneSagas.ts
@@ -67,6 +67,7 @@ import {
} from "utils/ApiPaneUtils";
import { updateReplayEntity } from "actions/pageActions";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
+import { getDisplayFormat } from "selectors/apiPaneSelectors";
function* syncApiParamsSaga(
actionPayload: ReduxActionWithMeta,
@@ -138,7 +139,8 @@ function* handleUpdateBodyContentType(
) {
const { apiId, title } = action.payload;
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
- // this is a previous value gotten before the updated content type has been set
+
+ // this is the previous value gotten before the new content type has been set
const previousContentType =
values.actionConfiguration?.formData?.apiContentType;
@@ -150,31 +152,37 @@ function* handleUpdateBodyContentType(
return;
}
- // this is the update for the new api contentType
- // update the api content type so it can be persisted.
+ // this is the update for the new apicontentType
+ // Quick Context: APiContentype is the field that represents the content type the user wants while in RAW mode.
+ // users should be able to set the content type to whatever they want.
let formData = { ...values.actionConfiguration.formData };
if (formData === undefined) formData = {};
- formData["apiContentType"] = title;
+ formData["apiContentType"] =
+ title === POST_BODY_FORMAT_OPTIONS.RAW ||
+ title === POST_BODY_FORMAT_OPTIONS.NONE
+ ? previousContentType
+ : title;
yield put(
change(API_EDITOR_FORM_NAME, "actionConfiguration.formData", formData),
);
- if (displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW) {
- // update the content type header if raw has been selected
- yield put({
- type: ReduxActionTypes.SET_EXTRA_FORMDATA,
- payload: {
- id: apiId,
- values: {
- displayFormat: {
- label: displayFormatValue,
- value: displayFormatValue,
- },
+ // Quick Context: The extra formadata action is responsible for updating the current multi switch mode you see on api editor body tab
+ // whenever a user selects a new content type through the tab e.g application/json, this action is dispatched to update that value, which is then read in the PostDataBody file
+ // to show the appropriate content type section.
+
+ yield put({
+ type: ReduxActionTypes.SET_EXTRA_FORMDATA,
+ payload: {
+ id: apiId,
+ values: {
+ displayFormat: {
+ label: title,
+ value: title,
},
},
- });
- }
+ },
+ });
const headers = cloneDeep(values.actionConfiguration.headers);
@@ -186,25 +194,19 @@ function* handleUpdateBodyContentType(
);
const indexToUpdate = getIndextoUpdate(headers, contentTypeHeaderIndex);
- // If the user has selected "None" as the body type & there was a content-type
+ // If the user has selected "None" or "Raw" as the body type & there was a content-type
// header present in the API configuration, keep the previous content type header
- // but if the user has selected "raw", set the content header to text/plain
+ // this is done to ensure user input isn't cleared off if they switch to raw or none mode.
+ // however if the user types in a new value, we use the updated value (formValueChangeSaga - line 426).
if (
- displayFormatValue === POST_BODY_FORMAT_OPTIONS.NONE &&
+ (displayFormatValue === POST_BODY_FORMAT_OPTIONS.NONE ||
+ displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW) &&
indexToUpdate !== -1
) {
headers[indexToUpdate] = {
key: previousContentType ? CONTENT_TYPE_HEADER_KEY : "",
value: previousContentType ? previousContentType : "",
};
- } else if (
- displayFormatValue === POST_BODY_FORMAT_OPTIONS.RAW &&
- indexToUpdate !== -1
- ) {
- headers[indexToUpdate] = {
- key: CONTENT_TYPE_HEADER_KEY,
- value: POST_BODY_FORMAT_OPTIONS.RAW,
- };
} else {
headers[indexToUpdate] = {
key: CONTENT_TYPE_HEADER_KEY,
@@ -212,6 +214,7 @@ function* handleUpdateBodyContentType(
};
}
+ // update the new header values.
yield put(
change(API_EDITOR_FORM_NAME, "actionConfiguration.headers", headers),
);
@@ -235,18 +238,19 @@ function* handleUpdateBodyContentType(
}
function* initializeExtraFormDataSaga() {
- const state = yield select();
- const { extraformData } = state.ui.apiPane;
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
const { values } = formData;
- // const headers = get(values, "actionConfiguration.headers");
- const apiContentType = get(
- values,
- "actionConfiguration.formData.apiContentType",
- );
- if (!extraformData[values.id]) {
- yield call(setHeaderFormat, values.id, apiContentType);
+ // when initializing, check if theres a display format present, if not use Json display format as default.
+ const extraFormData = yield select(getDisplayFormat, values.id);
+
+ // as a fail safe, if no display format is present, use Raw mode
+ const rawApiContentType = extraFormData?.displayFormat?.value
+ ? extraFormData?.displayFormat?.value
+ : POST_BODY_FORMAT_OPTIONS.RAW;
+
+ if (!extraFormData) {
+ yield call(setHeaderFormat, values.id, rawApiContentType);
}
}
@@ -296,6 +300,7 @@ function* changeApiSaga(
function* setHeaderFormat(apiId: string, apiContentType?: string) {
// use the current apiContentType to set appropriate Headers for action
let displayFormat;
+
if (apiContentType) {
if (apiContentType === POST_BODY_FORMAT_OPTIONS.NONE) {
displayFormat = {
@@ -336,8 +341,9 @@ export function* updateFormFields(
const value = actionPayload.payload;
log.debug("updateFormFields: " + JSON.stringify(value));
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
- let apiContentType = values.actionConfiguration.formData.apiContentType;
+ // get current content type of the action
+ let apiContentType = values.actionConfiguration.formData.apiContentType;
if (field === "actionConfiguration.httpMethod") {
const { actionConfiguration } = values;
if (!actionConfiguration.headers) return;
@@ -370,14 +376,7 @@ export function* updateFormFields(
};
}
}
- // change apiContentType when user changes api Http Method
- yield put(
- change(
- API_EDITOR_FORM_NAME,
- "actionConfiguration.formData.apiContentType",
- apiContentType,
- ),
- );
+
yield put(
change(
API_EDITOR_FORM_NAME,
@@ -385,9 +384,6 @@ export function* updateFormFields(
actionConfigurationHeaders,
),
);
- } else if (field.includes("actionConfiguration.headers")) {
- const apiId = get(values, "id");
- yield call(setHeaderFormat, apiId, apiContentType);
}
}
@@ -424,29 +420,21 @@ function* formValueChangeSaga(
}),
);
// when user types a content type value, update actionConfiguration.formData.apiContent type as well.
+ // we don't do this initally because we want to specifically catch user editing the content-type value
if (
field === `actionConfiguration.headers[${contentTypeHeaderIndex}].value`
) {
- if (
- // if the value is not a registered content type, make the default apiContentType raw but don't change header
- Object.values(POST_BODY_FORMAT_OPTIONS).includes(actionPayload.payload)
- ) {
- yield put(
- change(
- API_EDITOR_FORM_NAME,
- "actionConfiguration.formData.apiContentType",
- actionPayload.payload,
- ),
- );
- } else {
- yield put(
- change(
- API_EDITOR_FORM_NAME,
- "actionConfiguration.formData.apiContentType",
- POST_BODY_FORMAT_OPTIONS.RAW,
- ),
- );
- }
+ yield put(
+ change(
+ API_EDITOR_FORM_NAME,
+ "actionConfiguration.formData.apiContentType",
+ actionPayload.payload,
+ ),
+ );
+ const apiId = get(values, "id");
+ // when the user specifically sets a new content type value, we check if the input value is a supported post body type and switch to it
+ // if it does not we set the default to Raw mode.
+ yield call(setHeaderFormat, apiId, actionPayload.payload);
}
}
yield all([
diff --git a/app/client/src/selectors/apiPaneSelectors.ts b/app/client/src/selectors/apiPaneSelectors.ts
new file mode 100644
index 0000000000..ae61f10616
--- /dev/null
+++ b/app/client/src/selectors/apiPaneSelectors.ts
@@ -0,0 +1,11 @@
+import { AppState } from "reducers";
+
+type GetFormData = (
+ state: AppState,
+ apiId: string,
+) => { label: string; value: string };
+
+export const getDisplayFormat: GetFormData = (state, apiId) => {
+ const displayFormat = state.ui.apiPane.extraformData[apiId];
+ return displayFormat;
+};
diff --git a/app/client/src/utils/hooks/useHorizontalResize.tsx b/app/client/src/utils/hooks/useHorizontalResize.tsx
index ef9e50a0b8..1e3cec8dc4 100644
--- a/app/client/src/utils/hooks/useHorizontalResize.tsx
+++ b/app/client/src/utils/hooks/useHorizontalResize.tsx
@@ -112,7 +112,7 @@ const useHorizontalResize = (
unFocus(document, window);
if (ref.current) {
- const width = ref.current.getBoundingClientRect().width;
+ const width = ref.current.clientWidth;
const current = event.touches[0].clientX;
const positionDelta = position - current;
const widthDelta = inverse ? -positionDelta : positionDelta;
diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx
index ee632b8a22..fa133715ea 100644
--- a/app/client/src/widgets/BaseWidget.tsx
+++ b/app/client/src/widgets/BaseWidget.tsx
@@ -38,6 +38,7 @@ import OverlayCommentsWrapper from "comments/inlineComments/OverlayCommentsWrapp
import PreventInteractionsOverlay from "components/editorComponents/PreventInteractionsOverlay";
import AppsmithConsole from "utils/AppsmithConsole";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
+import PreviewModeComponent from "components/editorComponents/PreviewModeComponent";
/***
* BaseWidget
@@ -313,12 +314,20 @@ abstract class BaseWidget<
);
}
+ addPreviewModeWidget(content: ReactNode): React.ReactElement {
+ return (
+
+ {content}
+
+ );
+ }
+
private getWidgetView(): ReactNode {
let content: ReactNode;
-
switch (this.props.renderMode) {
case RenderModes.CANVAS:
content = this.getCanvasView();
+ content = this.addPreviewModeWidget(content);
content = this.addPreventInteractionOverlay(content);
content = this.addOverlayComments(content);
if (!this.props.detachFromLayout) {
diff --git a/app/client/src/widgets/InputWidgetV2/widget/index.tsx b/app/client/src/widgets/InputWidgetV2/widget/index.tsx
index 60d11f7f02..d9c532f210 100644
--- a/app/client/src/widgets/InputWidgetV2/widget/index.tsx
+++ b/app/client/src/widgets/InputWidgetV2/widget/index.tsx
@@ -207,9 +207,7 @@ class InputWidget extends BaseInputWidget {
isTriggerProperty: false,
validation: {
type: ValidationTypes.NUMBER,
- params: {
- min: 1,
- },
+ params: { min: 1, natural: true },
},
hidden: (props: InputWidgetProps) => {
return props.inputType !== InputTypes.TEXT;
diff --git a/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx
index 7d545646e3..ee18a3e454 100644
--- a/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx
+++ b/app/client/src/widgets/MultiSelectWidgetV2/component/index.tsx
@@ -28,7 +28,7 @@ import Icon from "components/ads/Icon";
import { Button, Classes, InputGroup } from "@blueprintjs/core";
import { WidgetContainerDiff } from "widgets/WidgetUtils";
import { Colors } from "constants/Colors";
-import _ from "lodash";
+import { uniqBy } from "lodash";
const menuItemSelectedIcon = (props: { isSelected: boolean }) => {
return ;
@@ -121,13 +121,12 @@ function MultiSelectComponent({
}),
);
// get unique selected values amongst SelectedAllValue and Value
- const allSelectedOptions = _.uniqBy(
- [...allOption, ...value],
- "value",
- ).map((val) => ({
- ...val,
- key: val.value,
- }));
+ const allSelectedOptions = uniqBy([...allOption, ...value], "value").map(
+ (val) => ({
+ ...val,
+ key: val.value,
+ }),
+ );
onChange(allSelectedOptions);
return;
}
diff --git a/app/client/src/widgets/MultiSelectWidgetV2/index.ts b/app/client/src/widgets/MultiSelectWidgetV2/index.ts
index 38e569b8b9..21e74f63cf 100644
--- a/app/client/src/widgets/MultiSelectWidgetV2/index.ts
+++ b/app/client/src/widgets/MultiSelectWidgetV2/index.ts
@@ -6,7 +6,6 @@ export const CONFIG = {
name: "MultiSelect",
iconSVG: IconSVG,
needsMeta: true,
- isFilterable: true,
defaults: {
rows: 7,
columns: 20,
@@ -18,8 +17,9 @@ export const CONFIG = {
{ label: "Red", value: "RED" },
],
widgetName: "MultiSelect",
+ isFilterable: true,
serverSideFiltering: false,
- defaultOptionValue: [{ label: "Green", value: "GREEN" }],
+ defaultOptionValue: ["GREEN", "RED"],
version: 1,
isRequired: false,
isDisabled: false,
diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.test.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.test.tsx
new file mode 100644
index 0000000000..9762e057a2
--- /dev/null
+++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.test.tsx
@@ -0,0 +1,249 @@
+import _ from "lodash";
+import { defaultOptionValueValidation, MultiSelectWidgetProps } from ".";
+
+describe("defaultOptionValueValidation - ", () => {
+ it("should get tested with empty string", () => {
+ const input = "";
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: [],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with array of strings|number", () => {
+ const input = ["green", "red"];
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: input,
+ messages: [""],
+ });
+ });
+
+ it("should get tested with array json string", () => {
+ const input = `["green", "red"]`;
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: ["green", "red"],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with array of object json string", () => {
+ const input = `[
+ {
+ "label": "green",
+ "value": "green"
+ },
+ {
+ "label": "red",
+ "value": "red"
+ }
+ ]`;
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: [
+ {
+ label: "green",
+ value: "green",
+ },
+ {
+ label: "red",
+ value: "red",
+ },
+ ],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with comma seperated strings", () => {
+ const input = "green, red";
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: ["green", "red"],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with simple string", () => {
+ const input = "green";
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: ["green"],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with simple string", () => {
+ const input = `{"green"`;
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: [`{"green"`],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with array of label, value", () => {
+ const input = [
+ {
+ label: "green",
+ value: "green",
+ },
+ {
+ label: "red",
+ value: "red",
+ },
+ ];
+
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: [
+ {
+ label: "green",
+ value: "green",
+ },
+ {
+ label: "red",
+ value: "red",
+ },
+ ],
+ messages: [""],
+ });
+ });
+
+ it("should get tested with array of invalid values", () => {
+ const testValues = [
+ [
+ undefined,
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ null,
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ true,
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ {},
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ [undefined],
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ [true],
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ [
+ ["green", "green"],
+ {
+ isValid: false,
+ parsed: [],
+ messages: ["values must be unique. Duplicate values found"],
+ },
+ ],
+ [
+ [
+ {
+ label: "green",
+ value: "green",
+ },
+ {
+ label: "green",
+ value: "green",
+ },
+ ],
+ {
+ isValid: false,
+ parsed: [],
+ messages: ["path:value must be unique. Duplicate values found"],
+ },
+ ],
+ [
+ [
+ {
+ label: "green",
+ },
+ {
+ label: "green",
+ },
+ ],
+ {
+ isValid: false,
+ parsed: [],
+ messages: [
+ "value should match: Array | Array<{label: string, value: string | number}>",
+ ],
+ },
+ ],
+ ];
+
+ testValues.forEach(([input, expected]) => {
+ expect(
+ defaultOptionValueValidation(input, {} as MultiSelectWidgetProps, _),
+ ).toEqual(expected);
+ });
+ });
+});
diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx
index 3b5616f07b..b9ee721fde 100644
--- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx
+++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx
@@ -2,8 +2,11 @@ import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
-import { isArray } from "lodash";
-import { ValidationTypes } from "constants/WidgetValidation";
+import { isArray, isString, isNumber } from "lodash";
+import {
+ ValidationResponse,
+ ValidationTypes,
+} from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import MultiSelectComponent from "../component";
import {
@@ -12,6 +15,114 @@ import {
} from "rc-select/lib/interface/generator";
import { Layers } from "constants/Layers";
import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import { AutocompleteDataType } from "utils/autocomplete/TernServer";
+
+export function defaultOptionValueValidation(
+ value: unknown,
+ props: MultiSelectWidgetProps,
+ _: any,
+): ValidationResponse {
+ let isValid;
+ let parsed;
+ let message = "";
+
+ /*
+ * Function to check if the object has `label` and `value`
+ */
+ const hasLabelValue = (obj: any) => {
+ return (
+ _.isPlainObject(obj) &&
+ obj.hasOwnProperty("label") &&
+ obj.hasOwnProperty("value") &&
+ _.isString(obj.label) &&
+ (_.isString(obj.value) || _.isFinite(obj.value))
+ );
+ };
+
+ /*
+ * Function to check for duplicate values in array
+ */
+ const hasUniqueValues = (arr: Array) => {
+ const uniqueValues = new Set(arr);
+
+ return uniqueValues.size === arr.length;
+ };
+
+ /*
+ * When value is "['green', 'red']", "[{label: 'green', value: 'green'}]" and "green, red"
+ */
+ if (_.isString(value) && (value as string).trim() !== "") {
+ try {
+ /*
+ * when value is "['green', 'red']", "[{label: 'green', value: 'green'}]"
+ */
+ value = JSON.parse(value as string);
+ } catch (e) {
+ /*
+ * when value is "green, red", JSON.parse throws error
+ */
+ const splitByComma = (value as string).split(",") || [];
+
+ value = splitByComma.map((s) => s.trim());
+ }
+ }
+
+ if (_.isString(value) && (value as string).trim() === "") {
+ isValid = true;
+ parsed = [];
+ message = "";
+ } else if (Array.isArray(value)) {
+ if (value.every((val) => _.isString(val) || _.isFinite(val))) {
+ /*
+ * When value is ["green", "red"]
+ */
+ if (hasUniqueValues(value as [])) {
+ isValid = true;
+ parsed = value;
+ message = "";
+ } else {
+ isValid = false;
+ parsed = [];
+ message = "values must be unique. Duplicate values found";
+ }
+ } else if (value.every(hasLabelValue)) {
+ /*
+ * When value is [{label: "green", value: "red"}]
+ */
+ if (hasUniqueValues(value.map((val) => val.value) as [])) {
+ isValid = true;
+ parsed = value;
+ message = "";
+ } else {
+ isValid = false;
+ parsed = [];
+ message = "path:value must be unique. Duplicate values found";
+ }
+ } else {
+ /*
+ * When value is [true, false], [undefined, undefined] etc.
+ */
+ isValid = false;
+ parsed = [];
+ message =
+ "value should match: Array | Array<{label: string, value: string | number}>";
+ }
+ } else {
+ /*
+ * When value is undefined, null, {} etc.
+ */
+ isValid = false;
+ parsed = [];
+ message =
+ "value should match: Array | Array<{label: string, value: string | number}>";
+ }
+
+ return {
+ isValid,
+ parsed,
+ messages: [message],
+ };
+}
class MultiSelectWidget extends BaseWidget<
MultiSelectWidgetProps,
@@ -66,7 +177,7 @@ class MultiSelectWidget extends BaseWidget<
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
- helpText: "Selects the option with value by default",
+ helpText: "Selects the option(s) with value by default",
propertyName: "defaultOptionValue",
label: "Default Value",
controlType: "INPUT_TEXT",
@@ -74,32 +185,13 @@ class MultiSelectWidget extends BaseWidget<
isBindProperty: true,
isTriggerProperty: false,
validation: {
- type: ValidationTypes.ARRAY,
+ type: ValidationTypes.FUNCTION,
params: {
- unique: ["value"],
- children: {
- type: ValidationTypes.OBJECT,
- params: {
- required: true,
- allowedKeys: [
- {
- name: "label",
- type: ValidationTypes.TEXT,
- params: {
- default: "",
- requiredKey: true,
- },
- },
- {
- name: "value",
- type: ValidationTypes.TEXT,
- params: {
- default: "",
- requiredKey: true,
- },
- },
- ],
- },
+ fn: defaultOptionValueValidation,
+ expected: {
+ type: "Array of values",
+ example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`,
+ autocompleteDataType: AutocompleteDataType.ARRAY,
},
},
},
@@ -304,8 +396,8 @@ class MultiSelectWidget extends BaseWidget<
static getDerivedPropertiesMap() {
return {
- selectedOptionLabels: `{{ this.selectedOptions ? this.selectedOptions.map((o) => o.label ) : [] }}`,
- selectedOptionValues: `{{ this.selectedOptions ? this.selectedOptions.map((o) => o.value ) : [] }}`,
+ selectedOptionLabels: `{{ this.selectedOptions ? this.selectedOptions.map((o) => _.isNil(o.label) ? o : o.label ) : [] }}`,
+ selectedOptionValues: `{{ this.selectedOptions ? this.selectedOptions.map((o) => _.isNil(o.value) ? o : o.value ) : [] }}`,
isValid: `{{this.isRequired ? !!this.selectedOptionValues && this.selectedOptionValues.length > 0 : true}}`,
isDirty: `{{ ((array1, array2) => {if (array1.length === array2.length) {return !array1.map((o) => o.value).every(element => array2.includes(element));} return true;})(this.defaultOptionValue, this.selectedOptionValues); }}`,
};
@@ -329,7 +421,13 @@ class MultiSelectWidget extends BaseWidget<
const options = isArray(this.props.options) ? this.props.options : [];
const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace;
const { componentWidth } = this.getComponentDimensions();
-
+ const values: LabelValueType[] = this.props.selectedOptions
+ ? this.props.selectedOptions.map((o) =>
+ isString(o) || isNumber(o)
+ ? { label: o, value: o }
+ : { label: o.label, value: o.value },
+ )
+ : [];
return (
@@ -366,6 +464,10 @@ class MultiSelectWidget extends BaseWidget<
}
onOptionChange = (value: DefaultValueType) => {
+ if (!this.props.isDirty) {
+ this.props.updateWidgetMetaProperty("isDirty", true);
+ }
+
this.props.updateWidgetMetaProperty("selectedOptions", value, {
triggerPropertyName: "onOptionChange",
dynamicString: this.props.onOptionChange,
diff --git a/app/client/src/widgets/SelectWidget/component/index.styled.tsx b/app/client/src/widgets/SelectWidget/component/index.styled.tsx
index 8160aaf662..45fa1ddb1a 100644
--- a/app/client/src/widgets/SelectWidget/component/index.styled.tsx
+++ b/app/client/src/widgets/SelectWidget/component/index.styled.tsx
@@ -12,6 +12,7 @@ import {
BlueprintCSSTransform,
createGlobalStyle,
} from "constants/DefaultTheme";
+import { isEmptyOrNill } from ".";
export const TextLabelWrapper = styled.div<{
compactMode: boolean;
@@ -145,7 +146,8 @@ export const StyledSingleDropDown = styled(SingleDropDown)<{
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
- color: ${(props) => (props.value ? Colors.GREY_10 : Colors.GREY_6)};
+ color: ${(props) =>
+ !isEmptyOrNill(props.value) ? Colors.GREY_10 : Colors.GREY_6};
}
&& {
.${Classes.ICON} {
diff --git a/app/client/src/widgets/SelectWidget/component/index.tsx b/app/client/src/widgets/SelectWidget/component/index.tsx
index ac0c0169a8..b5c94cea29 100644
--- a/app/client/src/widgets/SelectWidget/component/index.tsx
+++ b/app/client/src/widgets/SelectWidget/component/index.tsx
@@ -3,7 +3,7 @@ import { ComponentProps } from "widgets/BaseComponent";
import { MenuItem, Button, Classes } from "@blueprintjs/core";
import { DropdownOption } from "../constants";
import { IItemRendererProps } from "@blueprintjs/select";
-import _ from "lodash";
+import { debounce, findIndex, isEmpty, isNil } from "lodash";
import "../../../../node_modules/@blueprintjs/select/lib/css/blueprint-select.css";
import { Colors } from "constants/Colors";
import { TextSize } from "constants/WidgetConstants";
@@ -19,6 +19,7 @@ import {
import Fuse from "fuse.js";
import { WidgetContainerDiff } from "widgets/WidgetUtils";
import Icon, { IconSize } from "components/ads/Icon";
+import { isString } from "../../../utils/helpers";
const FUSE_OPTIONS = {
shouldSort: true,
@@ -29,6 +30,10 @@ const FUSE_OPTIONS = {
keys: ["label", "value"],
};
+export const isEmptyOrNill = (value: any) => {
+ return isNil(value) || (isString(value) && value === "");
+};
+
const DEBOUNCE_TIMEOUT = 800;
interface SelectComponentState {
@@ -59,7 +64,7 @@ class SelectComponent extends React.Component<
handleActiveItemChange = (activeItem: DropdownOption | null) => {
// find new index from options
- const activeItemIndex = _.findIndex(this.props.options, [
+ const activeItemIndex = findIndex(this.props.options, [
"label",
activeItem?.label,
]);
@@ -77,20 +82,21 @@ class SelectComponent extends React.Component<
widgetId,
} = this.props;
// active focused item
- const activeItem = !_.isEmpty(this.props.options)
+ const activeItem = !isEmpty(this.props.options)
? this.props.options[this.state.activeItemIndex]
: undefined;
// get selected option label from selectedIndex
const selectedOption =
- !_.isEmpty(this.props.options) &&
+ !isEmpty(this.props.options) &&
this.props.selectedIndex !== undefined &&
this.props.selectedIndex > -1
? this.props.options[this.props.selectedIndex].label
: this.props.label;
// for display selected option, there is no separate option to show placeholder
- const value = selectedOption
- ? selectedOption
- : this.props.placeholder || "-- Select --";
+ const value =
+ !isNil(selectedOption) && selectedOption !== ""
+ ? selectedOption
+ : this.props.placeholder || "-- Select --";
return (
@@ -142,7 +148,7 @@ class SelectComponent extends React.Component<
onClose: () => {
if (!this.props.selectedIndex) return;
return this.handleActiveItemChange(
- this.props.options[this.props.selectedIndex as number],
+ this.props.options[this.props.selectedIndex],
);
},
modifiers: {
@@ -160,7 +166,7 @@ class SelectComponent extends React.Component<
disabled={this.props.disabled}
rightIcon={
- {this.props.value ? (
+ {!isEmptyOrNill(this.props.value) ? (
{
- const optionIndex = _.findIndex(this.props.options, (option) => {
+ const optionIndex = findIndex(this.props.options, (option) => {
return option.value === selectedOption.value;
});
return optionIndex === this.props.selectedIndex;
@@ -211,7 +217,7 @@ class SelectComponent extends React.Component<
if (!this.props.serverSideFiltering) return;
return this.serverSideSearch(filterValue);
};
- serverSideSearch = _.debounce((filterValue: string) => {
+ serverSideSearch = debounce((filterValue: string) => {
this.props.onFilterChange(filterValue);
}, DEBOUNCE_TIMEOUT);
diff --git a/app/client/src/widgets/SelectWidget/index.ts b/app/client/src/widgets/SelectWidget/index.ts
index a9ec78804a..1395ed040a 100644
--- a/app/client/src/widgets/SelectWidget/index.ts
+++ b/app/client/src/widgets/SelectWidget/index.ts
@@ -18,9 +18,9 @@ export const CONFIG = {
],
serverSideFiltering: false,
widgetName: "Select",
- defaultOptionValue: { label: "Green", value: "GREEN" },
+ defaultOptionValue: "GREEN",
version: 1,
- isFilterable: false,
+ isFilterable: true,
isRequired: false,
isDisabled: false,
animateLoading: true,
diff --git a/app/client/src/widgets/SelectWidget/widget/index.test.tsx b/app/client/src/widgets/SelectWidget/widget/index.test.tsx
new file mode 100644
index 0000000000..9d697ce970
--- /dev/null
+++ b/app/client/src/widgets/SelectWidget/widget/index.test.tsx
@@ -0,0 +1,109 @@
+import _ from "lodash";
+import { SelectWidgetProps, defaultOptionValueValidation } from ".";
+
+describe("defaultOptionValueValidation - ", () => {
+ it("should get tested with simple string", () => {
+ const input = "";
+
+ expect(
+ defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: "",
+ messages: [""],
+ });
+ });
+
+ it("should get tested with simple string", () => {
+ const input = "green";
+
+ expect(
+ defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: "green",
+ messages: [""],
+ });
+ });
+
+ it("should get tested with plain object", () => {
+ const input = {
+ label: "green",
+ value: "green",
+ };
+
+ expect(
+ defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
+ ).toEqual({
+ isValid: true,
+ parsed: {
+ label: "green",
+ value: "green",
+ },
+ messages: [""],
+ });
+ });
+
+ it("should get tested with invalid values", () => {
+ const testValues = [
+ [
+ undefined,
+ {
+ isValid: false,
+ parsed: {},
+ messages: [
+ `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
+ ],
+ },
+ ],
+ [
+ null,
+ {
+ isValid: false,
+ parsed: {},
+ messages: [
+ `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
+ ],
+ },
+ ],
+ [
+ [],
+ {
+ isValid: false,
+ parsed: {},
+ messages: [
+ `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
+ ],
+ },
+ ],
+ [
+ true,
+ {
+ isValid: false,
+ parsed: {},
+ messages: [
+ `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
+ ],
+ },
+ ],
+ [
+ {
+ label: "green",
+ },
+ {
+ isValid: false,
+ parsed: {},
+ messages: [
+ `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`,
+ ],
+ },
+ ],
+ ];
+
+ testValues.forEach(([input, expected]) => {
+ expect(
+ defaultOptionValueValidation(input, {} as SelectWidgetProps, _),
+ ).toEqual(expected);
+ });
+ });
+});
diff --git a/app/client/src/widgets/SelectWidget/widget/index.tsx b/app/client/src/widgets/SelectWidget/widget/index.tsx
index 43f03cbada..c55ca8df31 100644
--- a/app/client/src/widgets/SelectWidget/widget/index.tsx
+++ b/app/client/src/widgets/SelectWidget/widget/index.tsx
@@ -3,13 +3,70 @@ import BaseWidget, { WidgetProps, WidgetState } from "../../BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import SelectComponent from "../component";
-import _ from "lodash";
import { DropdownOption } from "../constants";
-import { ValidationTypes } from "constants/WidgetValidation";
+import {
+ ValidationResponse,
+ ValidationTypes,
+} from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { MinimumPopupRows, GRID_DENSITY_MIGRATION_V1 } from "widgets/constants";
+import { AutocompleteDataType } from "utils/autocomplete/TernServer";
+import { findIndex, isArray, isNumber, isString } from "lodash";
+
+export function defaultOptionValueValidation(
+ value: unknown,
+ props: SelectWidgetProps,
+ _: any,
+): ValidationResponse {
+ let isValid;
+ let parsed;
+ let message = "";
+
+ /*
+ * Function to check if the object has `label` and `value`
+ */
+ const hasLabelValue = (obj: any) => {
+ return (
+ _.isPlainObject(value) &&
+ obj.hasOwnProperty("label") &&
+ obj.hasOwnProperty("value") &&
+ _.isString(obj.label) &&
+ (_.isString(obj.value) || _.isFinite(obj.value))
+ );
+ };
+
+ /*
+ * When value is "{label: 'green', value: 'green'}"
+ */
+ if (typeof value === "string") {
+ try {
+ value = JSON.parse(value);
+ } catch (e) {}
+ }
+
+ if (_.isString(value) || _.isFinite(value) || hasLabelValue(value)) {
+ /*
+ * When value is "", "green", 444, {label: "green", value: "green"}
+ */
+ isValid = true;
+ parsed = value;
+ } else {
+ isValid = false;
+ parsed = {};
+ message = `value does not evaluate to type: string | { "label": "label1", "value": "value1" }`;
+ }
+
+ return {
+ isValid,
+ parsed,
+ messages: [message],
+ };
+}
class SelectWidget extends BaseWidget {
+ constructor(props: SelectWidgetProps) {
+ super(props);
+ }
static getPropertyPaneConfig() {
return [
{
@@ -21,7 +78,7 @@ class SelectWidget extends BaseWidget {
propertyName: "options",
label: "Options",
controlType: "INPUT_TEXT",
- placeholderText: '[{ "label": "Option1", "value": "Option2" }]',
+ placeholderText: '[{ "label": "label1", "value": "value1" }]',
isBindProperty: true,
isTriggerProperty: false,
validation: {
@@ -60,34 +117,24 @@ class SelectWidget extends BaseWidget {
{
helpText: "Selects the option with value by default",
propertyName: "defaultOptionValue",
- label: "Default Option",
+ label: "Default Value",
controlType: "INPUT_TEXT",
- placeholderText: '{ "label": "Option1", "value": "Option2" }',
+ placeholderText: '{ "label": "label1", "value": "value1" }',
isBindProperty: true,
isTriggerProperty: false,
validation: {
- type: ValidationTypes.OBJECT,
+ type: ValidationTypes.FUNCTION,
params: {
- allowedKeys: [
- {
- name: "label",
- type: ValidationTypes.TEXT,
- params: {
- default: "",
- requiredKey: true,
- },
- },
- {
- name: "value",
- type: ValidationTypes.TEXT,
- params: {
- default: "",
- requiredKey: true,
- },
- },
- ],
+ fn: defaultOptionValueValidation,
+ expected: {
+ type: 'value1 or { "label": "label1", "value": "value1" }',
+ example: `value1 | { "label": "label1", "value": "value1" }`,
+ autocompleteDataType: AutocompleteDataType.STRING,
+ },
},
},
+ evaluationSubstitutionType:
+ EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Sets a Label Text",
@@ -273,57 +320,49 @@ class SelectWidget extends BaseWidget {
];
}
- static getDerivedPropertiesMap() {
- return {
- isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
- selectedOptionLabel: `{{ this.optionValue.label ?? this.optionValue.value }}`,
- selectedOptionValue: `{{ this.optionValue.value }}`,
- isDirty: `{{ this.optionValue.value !== this.defaultValue.value }}`,
- };
- }
-
static getDefaultPropertiesMap(): Record {
return {
defaultValue: "defaultOptionValue",
- optionValue: "defaultOptionValue",
+ value: "defaultOptionValue",
+ label: "defaultOptionValue",
+ filterText: "",
};
}
static getMetaPropertiesMap(): Record {
return {
- defaultValue: undefined,
- optionValue: undefined,
+ value: undefined,
+ label: undefined,
+ filterText: "",
+ };
+ }
+
+ static getDerivedPropertiesMap() {
+ return {
+ isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
+ selectedOptionLabel: `{{(()=>{const label = _.isPlainObject(this.label) ? this.label?.label : this.label; return label; })()}}`,
+ selectedOptionValue: `{{(()=>{const value = _.isPlainObject(this.value) ? this.value?.value : this.value; return value; })()}}`,
+ isDirty: `{{ this.value !== this.defaultValue }}`,
};
}
componentDidMount() {
+ super.componentDidMount();
this.changeSelectedOption();
}
- componentDidUpdate(prevProps: SelectWidgetProps): void {
- // removing selectedOptionValue if defaultValueChanges
- if (
- prevProps.defaultOptionValue?.value !==
- this.props.defaultOptionValue?.value ||
- prevProps.option !== this.props.option
- ) {
- this.changeSelectedOption();
- }
- }
- changeSelectedOption = () => {
- this.props.updateWidgetMetaProperty("optionValue", this.props.optionValue);
- };
+ isStringOrNumber = (value: any): value is string | number =>
+ isString(value) || isNumber(value);
getPageView() {
- const options = _.isArray(this.props.options) ? this.props.options : [];
+ const options = isArray(this.props.options) ? this.props.options : [];
const isInvalid =
"isValid" in this.props && !this.props.isValid && !!this.props.isDirty;
const dropDownWidth = MinimumPopupRows * this.props.parentColumnSpace;
- const selectedIndex = _.findIndex(this.props.options, {
+ const selectedIndex = findIndex(this.props.options, {
value: this.props.selectedOptionValue,
});
-
const { componentHeight, componentWidth } = this.getComponentDimensions();
return (
{
isFilterable={this.props.isFilterable}
isLoading={this.props.isLoading}
isValid={this.props.isValid}
- label={this.props.optionValue?.label}
+ label={this.props.selectedOptionLabel}
labelStyle={this.props.labelStyle}
labelText={this.props.labelText}
labelTextColor={this.props.labelTextColor}
@@ -353,7 +392,7 @@ class SelectWidget extends BaseWidget {
placeholder={this.props.placeholderText}
selectedIndex={selectedIndex > -1 ? selectedIndex : undefined}
serverSideFiltering={this.props.serverSideFiltering}
- value={this.props.optionValue?.value}
+ value={this.props.selectedOptionValue}
widgetId={this.props.widgetId}
width={componentWidth}
/>
@@ -369,9 +408,11 @@ class SelectWidget extends BaseWidget {
isChanged = !(this.props.selectedOptionValue === selectedOption.value);
}
if (isChanged) {
- this.props.updateWidgetMetaProperty("optionValue", selectedOption, {
+ this.props.updateWidgetMetaProperty("label", selectedOption.label ?? "");
+
+ this.props.updateWidgetMetaProperty("value", selectedOption.value ?? "", {
triggerPropertyName: "onOptionChange",
- dynamicString: this.props.onOptionChange as string,
+ dynamicString: this.props.onOptionChange,
event: {
type: EventType.ON_OPTION_CHANGE,
},
@@ -379,16 +420,29 @@ class SelectWidget extends BaseWidget {
}
};
+ changeSelectedOption = () => {
+ const label = this.isStringOrNumber(this.props.label)
+ ? this.props.label
+ : this.props.label?.label;
+ const value = this.isStringOrNumber(this.props.value)
+ ? this.props.value
+ : this.props.value?.value;
+ this.props.updateWidgetMetaProperty("value", value);
+ this.props.updateWidgetMetaProperty("label", label);
+ };
+
onFilterChange = (value: string) => {
this.props.updateWidgetMetaProperty("filterText", value);
- super.executeAction({
- triggerPropertyName: "onFilterUpdate",
- dynamicString: this.props.onFilterUpdate,
- event: {
- type: EventType.ON_FILTER_UPDATE,
- },
- });
+ if (this.props.onFilterUpdate && this.props.serverSideFiltering) {
+ super.executeAction({
+ triggerPropertyName: "onFilterUpdate",
+ dynamicString: this.props.onFilterUpdate,
+ event: {
+ type: EventType.ON_FILTER_UPDATE,
+ },
+ });
+ }
};
static getWidgetType(): WidgetType {
@@ -398,13 +452,12 @@ class SelectWidget extends BaseWidget {
export interface SelectWidgetProps extends WidgetProps {
placeholderText?: string;
- label?: string;
selectedIndex?: number;
- selectedOption: DropdownOption;
options?: DropdownOption[];
onOptionChange?: string;
- defaultOptionValue?: { label?: string; value?: string };
- value?: string;
+ defaultOptionValue?: any;
+ value?: any;
+ label?: any;
isRequired: boolean;
isFilterable: boolean;
defaultValue: string;
diff --git a/app/client/src/workers/evaluation.test.ts b/app/client/src/workers/evaluation.test.ts
index a08627d277..71b3efe80c 100644
--- a/app/client/src/workers/evaluation.test.ts
+++ b/app/client/src/workers/evaluation.test.ts
@@ -66,27 +66,20 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
},
metaProperties: {},
},
- DROP_DOWN_WIDGET: {
+ SELECT_WIDGET: {
defaultProperties: {
- selectedOptionValue: "defaultOptionValue",
- selectedOptionValueArr: "defaultOptionValue",
+ selectedOption: "defaultOptionValue",
+ filterText: "",
},
derivedProperties: {
- isValid:
- "{{this.isRequired ? this.selectionType === 'SINGLE_SELECT' ? !!this.selectedOption : !!this.selectedIndexArr && this.selectedIndexArr.length > 0 : true}}",
- selectedOption:
- "{{ this.selectionType === 'SINGLE_SELECT' ? _.find(this.options, { value: this.selectedOptionValue }) : undefined}}",
- selectedOptionArr:
- '{{this.selectionType === "MULTI_SELECT" ? this.options.filter(opt => _.includes(this.selectedOptionValueArr, opt.value)) : undefined}}',
- selectedIndex:
- "{{ _.findIndex(this.options, { value: this.selectedOption.value } ) }}",
- selectedIndexArr:
- "{{ this.selectedOptionValueArr.map(o => _.findIndex(this.options, { value: o })) }}",
- value:
- "{{ this.selectionType === 'SINGLE_SELECT' ? this.selectedOptionValue : this.selectedOptionValueArr }}",
- selectedOptionValues: "{{ this.selectedOptionValueArr }}",
+ selectedOptionLabel: `{{_.isPlainObject(this.selectedOption) ? this.selectedOption?.label : this.selectedOption}}`,
+ selectedOptionValue: `{{_.isPlainObject(this.selectedOption) ? this.selectedOption?.value : this.selectedOption}}`,
+ isValid: `{{this.isRequired ? !!this.selectedOptionValue || this.selectedOptionValue === 0 : true}}`,
+ },
+ metaProperties: {
+ selectedOption: undefined,
+ filterText: "",
},
- metaProperties: {},
},
RADIO_GROUP_WIDGET: {
defaultProperties: {
@@ -278,25 +271,25 @@ const mockDerived = jest.spyOn(WidgetFactory, "getWidgetDerivedPropertiesMap");
const dependencyMap = {
Dropdown1: [
"Dropdown1.defaultOptionValue",
+ "Dropdown1.filterText",
"Dropdown1.isValid",
- "Dropdown1.selectedIndex",
- "Dropdown1.selectedIndexArr",
+ "Dropdown1.meta",
"Dropdown1.selectedOption",
- "Dropdown1.selectedOptionArr",
+ "Dropdown1.selectedOptionLabel",
"Dropdown1.selectedOptionValue",
- "Dropdown1.selectedOptionValueArr",
- "Dropdown1.selectedOptionValues",
- "Dropdown1.value",
],
"Dropdown1.isValid": [],
- "Dropdown1.selectedIndex": [],
- "Dropdown1.selectedIndexArr": [],
- "Dropdown1.selectedOption": [],
- "Dropdown1.selectedOptionArr": [],
- "Dropdown1.selectedOptionValue": ["Dropdown1.defaultOptionValue"],
- "Dropdown1.selectedOptionValueArr": ["Dropdown1.defaultOptionValue"],
- "Dropdown1.selectedOptionValues": [],
- "Dropdown1.value": [],
+ "Dropdown1.filterText": ["Dropdown1.meta.filterText"],
+ "Dropdown1.meta": [
+ "Dropdown1.meta.filterText",
+ "Dropdown1.meta.selectedOption",
+ ],
+ "Dropdown1.selectedOption": [
+ "Dropdown1.defaultOptionValue",
+ "Dropdown1.meta.selectedOption",
+ ],
+ "Dropdown1.selectedOptionLabel": [],
+ "Dropdown1.selectedOptionValue": [],
Table1: [
"Table1.defaultSearchText",
"Table1.defaultSelectedRow",
@@ -394,7 +387,7 @@ describe("DataTreeEvaluator", () => {
value: "valueTest2",
},
],
- type: "DROP_DOWN_WIDGET",
+ type: "SELECT_WIDGET",
},
{},
),
@@ -492,7 +485,7 @@ describe("DataTreeEvaluator", () => {
value: "valueTest2",
},
],
- type: "DROP_DOWN_WIDGET",
+ type: "SELECT_WIDGET",
bindingPaths: {
options: EvaluationSubstitutionType.TEMPLATE,
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
@@ -501,11 +494,8 @@ describe("DataTreeEvaluator", () => {
isDisabled: EvaluationSubstitutionType.TEMPLATE,
isValid: EvaluationSubstitutionType.TEMPLATE,
selectedOption: EvaluationSubstitutionType.TEMPLATE,
- selectedOptionArr: EvaluationSubstitutionType.TEMPLATE,
- selectedIndex: EvaluationSubstitutionType.TEMPLATE,
- selectedIndexArr: EvaluationSubstitutionType.TEMPLATE,
- value: EvaluationSubstitutionType.TEMPLATE,
- selectedOptionValues: EvaluationSubstitutionType.TEMPLATE,
+ selectedOptionValue: EvaluationSubstitutionType.TEMPLATE,
+ selectedOptionLabel: EvaluationSubstitutionType.TEMPLATE,
},
},
};
diff --git a/app/client/test/factories/Widgets/WidgetTypeFactories.ts b/app/client/test/factories/Widgets/WidgetTypeFactories.ts
index 1bf4b72f5d..57620c73d2 100644
--- a/app/client/test/factories/Widgets/WidgetTypeFactories.ts
+++ b/app/client/test/factories/Widgets/WidgetTypeFactories.ts
@@ -34,7 +34,7 @@ export const WidgetTypeFactories: Record = {
DATE_PICKER_WIDGET: OldDatepickerFactory,
DATE_PICKER_WIDGET2: DatepickerFactory,
TABLE_WIDGET: TableFactory,
- DROP_DOWN_WIDGET: DropdownFactory,
+ SELECT_WIDGET: DropdownFactory,
CHECKBOX_WIDGET: CheckboxFactory,
RADIO_GROUP_WIDGET: RadiogroupFactory,
TABS_WIDGET: TabsFactory,
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java
index 82a8d3e930..7f30021490 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/AclPermission.java
@@ -8,6 +8,7 @@ import com.appsmith.server.domains.CommentThread;
import com.appsmith.external.models.Datasource;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Page;
+import com.appsmith.server.domains.Theme;
import com.appsmith.server.domains.User;
import lombok.Getter;
@@ -74,13 +75,15 @@ public enum AclPermission {
READ_DATASOURCES("read:datasources", Datasource.class),
EXECUTE_DATASOURCES("execute:datasources", Datasource.class),
- COMMENT_ON_THREAD("canComment:commentThreads", CommentThread.class),
- READ_THREAD("read:commentThreads", CommentThread.class),
- MANAGE_THREAD("manage:commentThreads", CommentThread.class),
+ COMMENT_ON_THREADS("canComment:commentThreads", CommentThread.class),
+ READ_THREADS("read:commentThreads", CommentThread.class),
+ MANAGE_THREADS("manage:commentThreads", CommentThread.class),
- READ_COMMENT("read:comments", Comment.class),
- MANAGE_COMMENT("manage:comments", Comment.class),
+ READ_COMMENTS("read:comments", Comment.class),
+ MANAGE_COMMENTS("manage:comments", Comment.class),
+ READ_THEMES("read:themes", Theme.class),
+ MANAGE_THEMES("manage:themes", Theme.class),
;
private final String value;
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java
index d7e776bf7a..dfc1ee6ccd 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java
@@ -20,7 +20,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.server.acl.AclPermission.COMMENT_ON_APPLICATIONS;
-import static com.appsmith.server.acl.AclPermission.COMMENT_ON_THREAD;
+import static com.appsmith.server.acl.AclPermission.COMMENT_ON_THREADS;
import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
import static com.appsmith.server.acl.AclPermission.EXPORT_APPLICATIONS;
@@ -30,6 +30,7 @@ import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
import static com.appsmith.server.acl.AclPermission.MANAGE_ORGANIZATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
+import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
import static com.appsmith.server.acl.AclPermission.MANAGE_USERS;
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_EXPORT_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_MANAGE_APPLICATIONS;
@@ -38,11 +39,12 @@ import static com.appsmith.server.acl.AclPermission.ORGANIZATION_READ_APPLICATIO
import static com.appsmith.server.acl.AclPermission.PUBLISH_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
-import static com.appsmith.server.acl.AclPermission.READ_COMMENT;
+import static com.appsmith.server.acl.AclPermission.READ_COMMENTS;
import static com.appsmith.server.acl.AclPermission.READ_DATASOURCES;
import static com.appsmith.server.acl.AclPermission.READ_ORGANIZATIONS;
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
-import static com.appsmith.server.acl.AclPermission.READ_THREAD;
+import static com.appsmith.server.acl.AclPermission.READ_THEMES;
+import static com.appsmith.server.acl.AclPermission.READ_THREADS;
import static com.appsmith.server.acl.AclPermission.READ_USERS;
import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS;
import static com.appsmith.server.acl.AclPermission.USER_READ_ORGANIZATIONS;
@@ -81,6 +83,7 @@ public class PolicyGeneratorCE {
createPagePolicyGraph();
createActionPolicyGraph();
createCommentPolicyGraph();
+ createThemePolicyGraph();
}
/**
@@ -142,11 +145,17 @@ public class PolicyGeneratorCE {
}
private void createCommentPolicyGraph() {
- hierarchyGraph.addEdge(COMMENT_ON_APPLICATIONS, COMMENT_ON_THREAD);
+ hierarchyGraph.addEdge(COMMENT_ON_APPLICATIONS, COMMENT_ON_THREADS);
- lateralGraph.addEdge(COMMENT_ON_THREAD, READ_THREAD);
+ lateralGraph.addEdge(COMMENT_ON_THREADS, READ_THREADS);
- hierarchyGraph.addEdge(COMMENT_ON_THREAD, READ_COMMENT);
+ hierarchyGraph.addEdge(COMMENT_ON_THREADS, READ_COMMENTS);
+ }
+
+ private void createThemePolicyGraph() {
+ hierarchyGraph.addEdge(MANAGE_APPLICATIONS, MANAGE_THEMES);
+ hierarchyGraph.addEdge(READ_APPLICATIONS, READ_THEMES);
+ lateralGraph.addEdge(MANAGE_THEMES, READ_THEMES);
}
public Set getLateralPolicies(AclPermission permission, Set userNames, Class extends BaseDomain> destinationEntity) {
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java
index 5d1d5be344..48ae524bba 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/FieldName.java
@@ -106,5 +106,6 @@ public class FieldName {
public static final String ACTION_LIST = "actionList";
public static final String ACTION_COLLECTION_LIST = "actionCollectionList";
public static final String DECRYPTED_FIELDS = "decryptedFields";
+ public static final String THEME = "theme";
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java
index 903f646d65..4257af339b 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java
@@ -4,18 +4,23 @@ import com.appsmith.server.constants.Url;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.Theme;
import com.appsmith.server.dtos.ResponseDTO;
+import com.appsmith.server.exceptions.AppsmithError;
+import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.services.ThemeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
+import java.util.List;
@Slf4j
@RequestMapping(Url.THEME_URL)
@@ -24,15 +29,38 @@ public class ThemeControllerCE extends BaseController> create(Theme resource, String originHeader, ServerWebExchange exchange) {
+ throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
+ }
+
@GetMapping("applications/{applicationId}")
- public Mono> getThemes(@PathVariable String applicationId, @RequestParam(required = false, defaultValue = "EDIT") ApplicationMode mode) {
+ public Mono>> getApplicationThemes(@PathVariable String applicationId) {
+ return service.getApplicationThemes(applicationId).collectList()
+ .map(themes -> new ResponseDTO<>(HttpStatus.OK.value(), themes, null));
+ }
+
+ @GetMapping("applications/{applicationId}/current")
+ public Mono> getCurrentTheme(@PathVariable String applicationId, @RequestParam(required = false, defaultValue = "EDIT") ApplicationMode mode) {
return service.getApplicationTheme(applicationId, mode)
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
}
- @PostMapping("applications/{applicationId}")
+ @PutMapping("applications/{applicationId}")
public Mono> updateTheme(@PathVariable String applicationId, @Valid @RequestBody Theme resource) {
return service.updateTheme(applicationId, resource)
.map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
}
+
+ @PatchMapping("applications/{applicationId}")
+ public Mono> publishCurrentTheme(@PathVariable String applicationId, @RequestBody Theme resource) {
+ return service.persistCurrentTheme(applicationId, resource)
+ .map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
+ }
+
+ @PatchMapping("{themeId}")
+ public Mono> updateName(@PathVariable String themeId, @Valid @RequestBody Theme resource) {
+ return service.updateName(themeId, resource)
+ .map(theme -> new ResponseDTO<>(HttpStatus.OK.value(), theme, null));
+ }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Theme.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Theme.java
index 9941dc9cee..71b1bfceac 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Theme.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Theme.java
@@ -2,7 +2,6 @@ package com.appsmith.server.domains;
import com.appsmith.external.models.BaseDomain;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@@ -11,7 +10,6 @@ import lombok.Setter;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.NotNull;
-import java.util.List;
import java.util.Map;
@Getter
@@ -23,23 +21,15 @@ public class Theme extends BaseDomain {
@NotNull
private String name;
- private Config config;
- private Properties properties;
- private Map stylesheet;
+ private String applicationId;
+ private String organizationId;
+ private Object config;
+ private Object properties;
+ private Map stylesheet;
@JsonProperty("isSystemTheme") // manually setting property name to make sure it's compatible with Gson
private boolean isSystemTheme = false; // should be false by default
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public static class Properties {
- private Colors colors;
- private BorderRadiusProperties borderRadius;
- private BoxShadowProperties boxShadow;
- private FontFamilyProperties fontFamily;
- }
-
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -47,87 +37,4 @@ public class Theme extends BaseDomain {
private String primaryColor;
private String backgroundColor;
}
-
- @Data
- public static class Config {
- private Colors colors;
- private BorderRadius borderRadius;
- private BoxShadow boxShadow;
- private FontFamily fontFamily;
- }
-
- @Data
- public static class ResponsiveAttributes {
- @JsonProperty("none")
- @SerializedName("none")
- private String noneValue;
-
- @JsonProperty("DEFAULT")
- @SerializedName("DEFAULT")
- private String defaultValue;
-
- @JsonProperty("md")
- @SerializedName("md")
- private String mdValue;
-
- @JsonProperty("lg")
- @SerializedName("lg")
- private String lgValue;
-
- @JsonProperty("xl")
- @SerializedName("xl")
- private String xlValue;
-
- @JsonProperty("2xl")
- @SerializedName("2xl")
- private String doubleXlValue;
-
- @JsonProperty("3xl")
- @SerializedName("3xl")
- private String tripleXlValue;
-
- @JsonProperty("full")
- @SerializedName("full")
- private String fullValue;
- }
-
- @Data
- public static class BorderRadius {
- private ResponsiveAttributes appBorderRadius;
- }
-
- @Data
- public static class BoxShadow {
- private ResponsiveAttributes appBoxShadow;
- }
-
- @Data
- public static class FontFamily {
- private List appFont;
- }
-
- @Data
- public static class FontFamilyProperties {
- private String appFont;
- }
-
- @Data
- public static class WidgetStyle {
- private String backgroundColor;
- private String borderRadius;
- private String boxShadow;
- private String primaryColor;
- private String menuColor;
- private String buttonColor;
- }
-
- @Data
- public static class BorderRadiusProperties {
- private String appBorderRadius;
- }
-
- @Data
- public static class BoxShadowProperties {
- private String appBoxShadow;
- }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java
index 78c5215c80..deff21a929 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/PolicyUtils.java
@@ -10,6 +10,7 @@ import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.CommentThread;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
+import com.appsmith.server.domains.Theme;
import com.appsmith.server.domains.User;
import com.appsmith.server.repositories.ActionCollectionRepository;
import com.appsmith.server.repositories.ApplicationRepository;
@@ -17,9 +18,11 @@ import com.appsmith.server.repositories.CommentThreadRepository;
import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.NewActionRepository;
import com.appsmith.server.repositories.NewPageRepository;
+import com.appsmith.server.repositories.ThemeRepository;
import lombok.AllArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -35,6 +38,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES;
+import static com.appsmith.server.acl.AclPermission.READ_THEMES;
@Component
@AllArgsConstructor
@@ -47,6 +51,7 @@ public class PolicyUtils {
private final NewActionRepository newActionRepository;
private final CommentThreadRepository commentThreadRepository;
private final ActionCollectionRepository actionCollectionRepository;
+ private final ThemeRepository themeRepository;
public T addPoliciesToExistingObject(Map policyMap, T obj) {
// Making a deep copy here so we don't modify the `policyMap` object.
@@ -231,12 +236,37 @@ public class PolicyUtils {
.saveAll(updatedPages));
}
+ public Flux updateThemePolicies(Application application, Map themePolicyMap, boolean addPolicyToObject) {
+ Flux applicationThemes = themeRepository.getApplicationThemes(application.getId(), READ_THEMES);
+ if(StringUtils.hasLength(application.getEditModeThemeId())) {
+ applicationThemes = applicationThemes.concatWith(
+ themeRepository.findById(application.getEditModeThemeId(), READ_THEMES)
+ );
+ }
+ if(StringUtils.hasLength(application.getPublishedModeThemeId())) {
+ applicationThemes = applicationThemes.concatWith(
+ themeRepository.findById(application.getPublishedModeThemeId(), READ_THEMES)
+ );
+ }
+ return applicationThemes
+ .filter(theme -> !theme.isSystemTheme()) // skip the system themes
+ .map(theme -> {
+ if (addPolicyToObject) {
+ return addPoliciesToExistingObject(themePolicyMap, theme);
+ } else {
+ return removePoliciesFromExistingObject(themePolicyMap, theme);
+ }
+ })
+ .collectList()
+ .flatMapMany(themeRepository::saveAll);
+ }
+
public Flux updateCommentThreadPermissions(
String applicationId, Map commentThreadPolicyMap, String username, boolean addPolicyToObject) {
return
// fetch comment threads with read permissions
- commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREAD)
+ commentThreadRepository.findByApplicationId(applicationId, AclPermission.READ_THREADS)
.switchIfEmpty(Mono.empty())
.map(thread -> {
if(!Boolean.TRUE.equals(thread.getIsPrivate())) {
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java
index 5b08b8d8ee..366f92f401 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java
@@ -138,6 +138,7 @@ import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_EXPORT_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
+import static com.appsmith.server.acl.AclPermission.READ_THEMES;
import static com.appsmith.server.constants.FieldName.DEFAULT_RESOURCES;
import static com.appsmith.server.constants.FieldName.DYNAMIC_TRIGGER_PATH_LIST;
import static com.appsmith.server.helpers.CollectionUtils.isNullOrEmpty;
@@ -4744,37 +4745,6 @@ public class DatabaseChangelog {
mongockTemplate.save(firestorePlugin);
}
- @ChangeSet(order = "108", id = "create-system-themes", author = "")
- public void createSystemThemes(MongockTemplate mongockTemplate) throws IOException {
- Index uniqueApplicationIdIndex = new Index()
- .on(fieldName(QTheme.theme.isSystemTheme), Sort.Direction.ASC)
- .named("system_theme_index");
-
- ensureIndexes(mongockTemplate, Theme.class, uniqueApplicationIdIndex);
-
- final String themesJson = StreamUtils.copyToString(
- new DefaultResourceLoader().getResource("system-themes.json").getInputStream(),
- Charset.defaultCharset()
- );
- Theme[] themes = new Gson().fromJson(themesJson, Theme[].class);
-
- Theme legacyTheme = null;
- for (Theme theme : themes) {
- theme.setSystemTheme(true);
- Theme savedTheme = mongockTemplate.save(theme);
- if(savedTheme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) {
- legacyTheme = savedTheme;
- }
- }
-
- // migrate all applications and set legacy theme to them in both mode
- Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId())
- .set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId());
- mongockTemplate.updateMulti(
- new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class
- );
- }
-
/**
* This method sets the key formData.aggregate.limit to 101 for all Mongo plugin actions.
* It iterates over each action id one by one to avoid out of memory error.
@@ -4817,6 +4787,11 @@ public class DatabaseChangelog {
return true;
}
+ @ChangeSet(order = "108", id = "create-system-themes", author = "")
+ public void createSystemThemes(MongockTemplate mongockTemplate) throws IOException {
+ createSystemThemes2(mongockTemplate);
+ }
+
/**
* This migration adds a new field to Mongo aggregate command to set batchSize: formData.aggregate.limit. Its value
* is set by this migration to 101 for all existing actions since this is the default `batchSize` used by
@@ -5025,4 +5000,75 @@ public class DatabaseChangelog {
);
}
+ /**
+ * Adding this migration again because we've added permission to themes.
+ * Also there are couple of changes in the system theme properties.
+ * @param mongockTemplate
+ * @throws IOException
+ */
+ @ChangeSet(order = "117", id = "create-system-themes-v2", author = "")
+ public void createSystemThemes2(MongockTemplate mongockTemplate) throws IOException {
+ Index systemThemeIndex = new Index()
+ .on(fieldName(QTheme.theme.isSystemTheme), Sort.Direction.ASC)
+ .named("system_theme_index")
+ .background();
+
+ Index applicationIdIndex = new Index()
+ .on(fieldName(QTheme.theme.applicationId), Sort.Direction.ASC)
+ .on(fieldName(QTheme.theme.deleted), Sort.Direction.ASC)
+ .named("application_id_index")
+ .background();
+
+ dropIndexIfExists(mongockTemplate, Theme.class, "system_theme_index");
+ dropIndexIfExists(mongockTemplate, Theme.class, "application_id_index");
+ ensureIndexes(mongockTemplate, Theme.class, systemThemeIndex, applicationIdIndex);
+
+ final String themesJson = StreamUtils.copyToString(
+ new DefaultResourceLoader().getResource("system-themes.json").getInputStream(),
+ Charset.defaultCharset()
+ );
+ Theme[] themes = new Gson().fromJson(themesJson, Theme[].class);
+
+ Theme legacyTheme = null;
+ boolean themeExists = false;
+
+ Policy policyWithCurrentPermission = Policy.builder().permission(READ_THEMES.getValue())
+ .users(Set.of(FieldName.ANONYMOUS_USER)).build();
+
+ for (Theme theme : themes) {
+ theme.setSystemTheme(true);
+ theme.setCreatedAt(Instant.now());
+ theme.setPolicies(Set.of(policyWithCurrentPermission));
+ Query query = new Query(Criteria.where(fieldName(QTheme.theme.name)).is(theme.getName())
+ .and(fieldName(QTheme.theme.isSystemTheme)).is(true));
+
+ Theme savedTheme = mongockTemplate.findOne(query, Theme.class);
+ if(savedTheme == null) { // this theme does not exist, create it
+ savedTheme = mongockTemplate.save(theme);
+ } else { // theme already found, update
+ themeExists = true;
+ savedTheme.setPolicies(theme.getPolicies());
+ savedTheme.setConfig(theme.getConfig());
+ savedTheme.setProperties(theme.getProperties());
+ savedTheme.setStylesheet(theme.getStylesheet());
+ if(savedTheme.getCreatedAt() == null) {
+ savedTheme.setCreatedAt(Instant.now());
+ }
+ mongockTemplate.save(savedTheme);
+ }
+
+ if(theme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) {
+ legacyTheme = savedTheme;
+ }
+ }
+
+ if(!themeExists) { // this is the first time we're running the migration
+ // migrate all applications and set legacy theme to them in both mode
+ Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId())
+ .set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId());
+ mongockTemplate.updateMulti(
+ new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class
+ );
+ }
+ }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomCommentThreadRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomCommentThreadRepositoryCEImpl.java
index d40b45672f..fe3470acc6 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomCommentThreadRepositoryCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomCommentThreadRepositoryCEImpl.java
@@ -58,13 +58,13 @@ public class CustomCommentThreadRepositoryCEImpl extends BaseAppsmithRepositoryI
where(fieldName(QCommentThread.commentThread.applicationId)).is(applicationId),
where(fieldName(QCommentThread.commentThread.isPrivate)).is(TRUE)
);
- return queryOne(criteria, AclPermission.READ_THREAD);
+ return queryOne(criteria, AclPermission.READ_THREADS);
}
@Override
public Mono removeSubscriber(String threadId, String username) {
Update update = new Update().pull(fieldName(QCommentThread.commentThread.subscribers), username);
- return this.updateById(threadId, update, AclPermission.READ_THREAD);
+ return this.updateById(threadId, update, AclPermission.READ_THREADS);
}
@Override
@@ -92,7 +92,7 @@ public class CustomCommentThreadRepositoryCEImpl extends BaseAppsmithRepositoryI
where(fieldName(QCommentThread.commentThread.applicationId)).is(applicationId),
where(resolvedActiveFieldKey).is(false)
);
- return count(criteriaList, AclPermission.READ_THREAD);
+ return count(criteriaList, AclPermission.READ_THREADS);
}
@Override
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCE.java
index a87099d01a..44845df85c 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCE.java
@@ -1,11 +1,13 @@
package com.appsmith.server.repositories.ce;
+import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.Theme;
import com.appsmith.server.repositories.AppsmithRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CustomThemeRepositoryCE extends AppsmithRepository {
+ Flux getApplicationThemes(String applicationId, AclPermission aclPermission);
Flux getSystemThemes();
Mono getSystemThemeByName(String themeName);
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCEImpl.java
index ecd21d0273..02ec685332 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomThemeRepositoryCEImpl.java
@@ -1,5 +1,6 @@
package com.appsmith.server.repositories.ce;
+import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.QTheme;
import com.appsmith.server.domains.Theme;
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
@@ -24,10 +25,18 @@ public class CustomThemeRepositoryCEImpl extends BaseAppsmithRepositoryImpl getApplicationThemes(String applicationId, AclPermission aclPermission) {
+ Criteria appThemeCriteria = Criteria.where(fieldName(QTheme.theme.applicationId)).is(applicationId);
+ Criteria systemThemeCriteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
+ Criteria criteria = new Criteria().orOperator(appThemeCriteria, systemThemeCriteria);
+ return queryAll(List.of(criteria), aclPermission);
+ }
+
@Override
public Flux getSystemThemes() {
- Criteria criteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
- return queryAll(List.of(criteria), null);
+ Criteria systemThemeCriteria = Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(Boolean.TRUE);
+ return queryAll(List.of(systemThemeCriteria), AclPermission.READ_THEMES);
}
@Override
@@ -35,6 +44,6 @@ public class CustomThemeRepositoryCEImpl extends BaseAppsmithRepositoryImpl
- themeService.cloneThemeToApplication(sourceApplication.getEditModeThemeId(), application.getId())
- .zipWith(themeService.cloneThemeToApplication(sourceApplication.getPublishedModeThemeId(), application.getId()))
- .map(themesZip -> {
- application.setEditModeThemeId(themesZip.getT1().getId());
- application.setPublishedModeThemeId(themesZip.getT2().getId());
- return application;
- })
- )
// Now fetch the pages of the source application, clone and add them to this new application
.flatMap(savedApplication -> Flux.fromIterable(sourceApplication.getPages())
.flatMap(applicationPage -> {
@@ -728,6 +718,20 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
savedApplication.setPages(clonedPages);
return applicationService.save(savedApplication);
})
+ )
+ // duplicate the source application's themes if required i.e. if they were customized
+ .flatMap(application ->
+ themeService.cloneThemeToApplication(sourceApplication.getEditModeThemeId(), application)
+ .zipWith(themeService.cloneThemeToApplication(sourceApplication.getPublishedModeThemeId(), application))
+ .flatMap(themesZip -> {
+ String editModeThemeId = themesZip.getT1().getId();
+ String publishedModeThemeId = themesZip.getT2().getId();
+ application.setEditModeThemeId(editModeThemeId);
+ application.setPublishedModeThemeId(publishedModeThemeId);
+ return applicationService.setAppTheme(
+ application.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
+ ).thenReturn(application);
+ })
);
});
@@ -842,9 +846,9 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE {
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId)))
.cache();
- Mono publishThemeMono = applicationMono.flatMap(application -> themeService.publishTheme(
- application.getEditModeThemeId(), application.getPublishedModeThemeId(), application.getId()
- ));
+ Mono publishThemeMono = applicationMono.flatMap(
+ application -> themeService.publishTheme(application.getId())
+ );
Flux publishApplicationAndPages = applicationMono
//Return all the pages in the Application
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
index 14b7d1c2f7..befa831434 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCE.java
@@ -5,6 +5,7 @@ import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.dtos.ApplicationAccessDTO;
import com.appsmith.server.services.CrudService;
+import com.mongodb.client.result.UpdateResult;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -62,4 +63,6 @@ public interface ApplicationServiceCE extends CrudService {
String getRandomAppCardColor();
+ Mono setAppTheme(String applicationId, String editModeThemeId, String publishedModeThemeId, AclPermission aclPermission);
+
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
index 4a0e29b0ff..586fd4bafa 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java
@@ -13,6 +13,7 @@ import com.appsmith.server.domains.GitAuth;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Page;
+import com.appsmith.server.domains.Theme;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.ActionDTO;
import com.appsmith.server.dtos.ApplicationAccessDTO;
@@ -28,6 +29,7 @@ import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.BaseService;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.SessionUserService;
+import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
@@ -308,18 +310,23 @@ public class ApplicationServiceCEImpl extends BaseService pagePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(applicationPolicyMap, Application.class, Page.class);
Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class);
Map datasourcePolicyMap = policyUtils.generatePolicyFromPermission(Set.of(EXECUTE_DATASOURCES), user);
+ Map themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
+ applicationPolicyMap, Application.class, Theme.class
+ );
final Flux updatedPagesFlux = policyUtils
.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, isPublic);
// Use the same policy map as actions for action collections since action collections have the same kind of permissions
final Flux updatedActionCollectionsFlux = policyUtils
.updateWithPagePermissionsToAllItsActionCollections(application.getId(), actionPolicyMap, isPublic);
-
+ Flux updatedThemesFlux = policyUtils.updateThemePolicies(application, themePolicyMap, isPublic);
final Flux updatedActionsFlux = updatedPagesFlux
.collectList()
.thenMany(updatedActionCollectionsFlux)
.collectList()
.then(Mono.justOrEmpty(application.getId()))
+ .thenMany(updatedThemesFlux)
+ .collectList()
.flatMapMany(applicationId -> policyUtils.updateWithPagePermissionsToAllItsActions(application.getId(), actionPolicyMap, isPublic));
return updatedActionsFlux
@@ -547,4 +554,8 @@ public class ApplicationServiceCEImpl extends BaseService setAppTheme(String applicationId, String editModeThemeId, String publishedModeThemeId, AclPermission aclPermission) {
+ return repository.setAppTheme(applicationId, editModeThemeId, publishedModeThemeId, aclPermission);
+ }
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java
index 8ff28b7eab..d290541af8 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java
@@ -61,9 +61,9 @@ import static com.appsmith.server.acl.AclPermission.COMMENT_ON_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
import static com.appsmith.server.acl.AclPermission.READ_APPLICATIONS;
-import static com.appsmith.server.acl.AclPermission.READ_COMMENT;
+import static com.appsmith.server.acl.AclPermission.READ_COMMENTS;
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
-import static com.appsmith.server.acl.AclPermission.READ_THREAD;
+import static com.appsmith.server.acl.AclPermission.READ_THREADS;
import static com.appsmith.server.constants.CommentConstants.APPSMITH_BOT_NAME;
import static com.appsmith.server.constants.CommentConstants.APPSMITH_BOT_USERNAME;
import static java.lang.Boolean.FALSE;
@@ -177,7 +177,7 @@ public class CommentServiceCEImpl extends BaseService updateThreadOnAddComment(commentThread, comment, user))
.flatMap(commentThread -> create(commentThread, user, comment, originHeader));
@@ -188,7 +188,7 @@ public class CommentServiceCEImpl extends BaseService findByIdAndBranchName(String id, String branchName) {
// Ignore branch name as comments are not shared across git branches
- return repository.findById(id, READ_COMMENT)
+ return repository.findById(id, READ_COMMENTS)
.map(responseUtils::updatePageAndAppIdWithDefaultResourcesForComments);
}
@@ -234,9 +234,9 @@ public class CommentServiceCEImpl extends BaseService commentMono;
@@ -429,7 +429,7 @@ public class CommentServiceCEImpl extends BaseService {
updatedThread.setIsViewed(true);
// Update branched applicationId and pageId with default Ids
@@ -520,7 +520,7 @@ public class CommentServiceCEImpl extends BaseService get(MultiValueMap params) {
// Remove branch name as comments are not shared across branches
params.remove(FieldName.DEFAULT_RESOURCES + "." + FieldName.BRANCH_NAME);
- return super.getWithPermission(params, READ_COMMENT)
+ return super.getWithPermission(params, READ_COMMENTS)
.map(responseUtils::updatePageAndAppIdWithDefaultResourcesForComments);
}
@@ -580,7 +580,7 @@ public class CommentServiceCEImpl extends BaseService {
final Map threadsByThreadId = new HashMap<>();
@@ -626,10 +626,10 @@ public class CommentServiceCEImpl extends BaseService deleteComment(String id) {
- return repository.findById(id, AclPermission.MANAGE_COMMENT)
+ return repository.findById(id, AclPermission.MANAGE_COMMENTS)
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.COMMENT, id)))
.flatMap(repository::archive)
- .flatMap(comment -> threadRepository.findById(comment.getThreadId(), READ_THREAD).flatMap(commentThread ->
+ .flatMap(comment -> threadRepository.findById(comment.getThreadId(), READ_THREADS).flatMap(commentThread ->
sendCommentNotifications(commentThread.getSubscribers(), comment, CommentNotificationEvent.DELETED)
.thenReturn(comment)
))
@@ -639,7 +639,7 @@ public class CommentServiceCEImpl extends BaseService deleteThread(String threadId) {
- return threadRepository.findById(threadId, AclPermission.MANAGE_THREAD)
+ return threadRepository.findById(threadId, AclPermission.MANAGE_THREADS)
.flatMap(threadRepository::archive)
.flatMap(commentThread ->
notificationService.createNotification(
@@ -653,7 +653,7 @@ public class CommentServiceCEImpl extends BaseService createReaction(String commentId, Comment.Reaction reaction) {
return Mono.zip(
- repository.findById(commentId, READ_COMMENT),
+ repository.findById(commentId, READ_COMMENTS),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
@@ -671,7 +671,7 @@ public class CommentServiceCEImpl extends BaseService deleteReaction(String commentId, Comment.Reaction reaction) {
return Mono.zip(
- repository.findById(commentId, READ_COMMENT),
+ repository.findById(commentId, READ_COMMENTS),
sessionUserService.getCurrentUser()
)
.flatMap(tuple -> {
@@ -702,7 +702,7 @@ public class CommentServiceCEImpl extends BaseService commentSeq;
if (TRUE.equals(commentThread.getIsPrivate())) {
Collection policyCollection = policyUtils.generatePolicyFromPermission(
- Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD),
+ Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS),
user
).values();
policies.addAll(policyCollection);
@@ -714,9 +714,9 @@ public class CommentServiceCEImpl extends BaseService {
Mono getApplicationTheme(String applicationId, ApplicationMode applicationMode);
+ Flux getApplicationThemes(String applicationId);
+ Flux getSystemThemes();
+ Mono getSystemTheme(String themeName);
Mono updateTheme(String applicationId, Theme resource);
Mono changeCurrentTheme(String themeId, String applicationId);
@@ -18,14 +24,17 @@ public interface ThemeServiceCE extends CrudService {
Mono getDefaultThemeId();
/**
- * Duplicates a theme if the theme is customized one. It'll set the application id to the new theme.
+ * Duplicates a theme if the theme is customized one.
* If the source theme is a system theme, it'll skip creating a new theme and return the system theme instead.
* @param srcThemeId ID of source theme that needs to be duplicated
* @param destApplicationId ID of the application for which theme'll be created
* @return newly created theme if source is not system theme, otherwise return the system theme
*/
- Mono cloneThemeToApplication(String srcThemeId, String destApplicationId);
-
- Mono publishTheme(String editModeThemeId, String publishedThemeId, String applicationId);
- void resetDefaultThemeIdCache();
+ Mono cloneThemeToApplication(String srcThemeId, Application destApplication);
+ Mono publishTheme(String applicationId);
+ Mono persistCurrentTheme(String applicationId, Theme theme);
+ Mono getThemeById(String themeId, AclPermission permission);
+ Mono save(Theme theme);
+ Mono updateName(String id, Theme theme);
+ Mono getOrSaveTheme(Theme theme, Application destApplication);
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java
index 58d488c6cf..06c6699892 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java
@@ -1,7 +1,9 @@
package com.appsmith.server.services.ce;
import com.appsmith.server.acl.AclPermission;
+import com.appsmith.server.acl.PolicyGenerator;
import com.appsmith.server.constants.FieldName;
+import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.Theme;
import com.appsmith.server.exceptions.AppsmithError;
@@ -19,43 +21,48 @@ import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
+import reactor.util.function.Tuples;
import javax.validation.Validator;
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
+import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES;
+import static com.appsmith.server.acl.AclPermission.READ_THEMES;
@Slf4j
public class ThemeServiceCEImpl extends BaseService implements ThemeServiceCE {
private final ApplicationRepository applicationRepository;
+ private final PolicyGenerator policyGenerator;
private String defaultThemeId; // acts as a simple cache so that we don't need to fetch from DB always
- public ThemeServiceCEImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository) {
+ public ThemeServiceCEImpl(Scheduler scheduler, Validator validator, MongoConverter mongoConverter, ReactiveMongoTemplate reactiveMongoTemplate, ThemeRepository repository, AnalyticsService analyticsService, ApplicationRepository applicationRepository, PolicyGenerator policyGenerator) {
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
this.applicationRepository = applicationRepository;
- }
-
- @Override
- public Flux get(MultiValueMap params) {
- return repository.getSystemThemes(); // return the list of system themes
+ this.policyGenerator = policyGenerator;
}
@Override
public Mono create(Theme resource) {
- // user can get the list of themes under an application only
- throw new UnsupportedOperationException();
+ return repository.save(resource);
}
@Override
public Mono update(String s, Theme resource) {
// we don't allow to update a theme by id, user can only update a theme under their application
- throw new UnsupportedOperationException();
+ throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
}
@Override
public Mono getById(String s) {
- // TODO: better to add permission check
- return repository.findById(s);
+ // we don't allow to get a theme by id from DB
+ throw new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION);
+ }
+
+ @Override
+ public Flux get(MultiValueMap params) {
+ // we return all system themes
+ return repository.getSystemThemes();
}
@Override
@@ -69,45 +76,75 @@ public class ThemeServiceCEImpl extends BaseService getApplicationThemes(String applicationId) {
+ return repository.getApplicationThemes(applicationId, READ_THEMES);
+ }
+
+ @Override
+ public Flux getSystemThemes() {
+ return repository.getSystemThemes();
+ }
+
@Override
public Mono updateTheme(String applicationId, Theme resource) {
return applicationRepository.findById(applicationId, AclPermission.MANAGE_APPLICATIONS)
.flatMap(application -> {
// makes sure user has permission to edit application and an application exists by this applicationId
// check if this application has already a customized them
- return saveThemeForApplication(application.getEditModeThemeId(), resource, applicationId, ApplicationMode.EDIT);
+ return saveThemeForApplication(application.getEditModeThemeId(), resource, application, ApplicationMode.EDIT);
});
}
@Override
public Mono changeCurrentTheme(String newThemeId, String applicationId) {
- // set provided theme to application and return that theme object
- Mono setAppThemeMono = applicationRepository.setAppTheme(
- applicationId, newThemeId,null, MANAGE_APPLICATIONS
- ).then(repository.findById(newThemeId));
-
- // in case a customized theme was set to application, we need to delete that
return applicationRepository.findById(applicationId, AclPermission.MANAGE_APPLICATIONS)
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))
)
- .flatMap(application -> repository.findById(application.getEditModeThemeId())
+ .flatMap(application -> repository.findById(application.getEditModeThemeId(), READ_THEMES)
.defaultIfEmpty(new Theme())
- .flatMap(currentTheme -> {
- if (!StringUtils.isEmpty(currentTheme.getId()) && !currentTheme.isSystemTheme()) {
- // current theme is not a system theme but customized one, delete this
- return repository.delete(currentTheme).then(setAppThemeMono);
+ .zipWith(repository.findById(newThemeId, READ_THEMES))
+ .flatMap(themeTuple2 -> {
+ Theme currentTheme = themeTuple2.getT1();
+ Theme newTheme = themeTuple2.getT2();
+ Mono saveThemeMono;
+ if(!newTheme.isSystemTheme()) {
+ // we'll create a copy of newTheme
+ newTheme.setId(null);
+ newTheme.setApplicationId(null);
+ newTheme.setOrganizationId(null);
+ newTheme.setPolicies(policyGenerator.getAllChildPolicies(
+ application.getPolicies(), Application.class, Theme.class
+ ));
+ saveThemeMono = repository.save(newTheme);
+ } else {
+ saveThemeMono = Mono.just(newTheme);
}
- return setAppThemeMono;
- }));
+
+ return saveThemeMono.flatMap(savedTheme -> {
+ if (StringUtils.hasLength(currentTheme.getId()) && !currentTheme.isSystemTheme()
+ && !StringUtils.hasLength(currentTheme.getApplicationId())) {
+ // current theme is neither a system theme nor app theme, delete the user customizations
+ return repository.delete(currentTheme).then(applicationRepository.setAppTheme(
+ applicationId, savedTheme.getId(),null, MANAGE_APPLICATIONS
+ )).thenReturn(savedTheme);
+ } else {
+ return applicationRepository.setAppTheme(
+ applicationId, savedTheme.getId(),null, MANAGE_APPLICATIONS
+ ).thenReturn(savedTheme);
+ }
+ });
+ })
+ );
}
@Override
@@ -122,79 +159,97 @@ public class ThemeServiceCEImpl extends BaseService cloneThemeToApplication(String srcThemeId, String destApplicationId) {
- return applicationRepository.findById(destApplicationId, MANAGE_APPLICATIONS).then(
- // make sure the current user has permission to manage application
- repository.findById(srcThemeId).flatMap(theme -> {
- if (theme.isSystemTheme()) { // it's a system theme, no need to copy
- return Mono.just(theme);
- } else { // it's a customized theme, create a copy and return the copy
- theme.setId(null); // setting id to null so that save method will create a new instance
- return repository.save(theme);
- }
- })
- );
- }
-
- @Override
- public Mono publishTheme(String editModeThemeId, String publishedThemeId, String applicationId) {
- Mono editModeThemeMono;
- if(!StringUtils.hasLength(editModeThemeId)) { // theme id is empty, use the default theme
- editModeThemeMono = repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME);
- } else { // theme id is not empty, fetch it by id
- editModeThemeMono = repository.findById(editModeThemeId);
- }
-
- Mono publishThemeMono = editModeThemeMono.flatMap(editModeTheme -> {
- if (editModeTheme.isSystemTheme()) { // system theme is set as edit mode theme
- // just set the system theme id as edit and published mode theme id to application object
- return applicationRepository.setAppTheme(
- applicationId, editModeTheme.getId(), editModeTheme.getId(), MANAGE_APPLICATIONS
- ).thenReturn(editModeTheme);
- } else { // a customized theme is set as edit mode theme, copy that theme for published mode
- return saveThemeForApplication(publishedThemeId, editModeTheme, applicationId, ApplicationMode.PUBLISHED);
+ public Mono cloneThemeToApplication(String srcThemeId, Application destApplication) {
+ return repository.findById(srcThemeId, READ_THEMES).flatMap(theme -> {
+ if (theme.isSystemTheme()) { // it's a system theme, no need to copy
+ return Mono.just(theme);
+ } else { // it's a customized theme, create a copy and return the copy
+ theme.setId(null); // setting id to null so that save method will create a new instance
+ theme.setApplicationId(null);
+ theme.setOrganizationId(null);
+ theme.setPolicies(policyGenerator.getAllChildPolicies(
+ destApplication.getPolicies(), Application.class, Theme.class
+ ));
+ return repository.save(theme);
}
});
+ }
+
+ /**
+ * Publishes a theme from edit mode to published mode
+ * @param applicationId application id
+ * @return Mono of theme object that was set in published mode
+ */
+ @Override
+ public Mono publishTheme(String applicationId) {
// fetch application to make sure user has permission to manage this application
- return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS).then(publishThemeMono);
+ return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS).flatMap(application -> {
+ Mono editModeThemeMono;
+ if(!StringUtils.hasLength(application.getEditModeThemeId())) { // theme id is empty, use the default theme
+ editModeThemeMono = repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME);
+ } else { // theme id is not empty, fetch it by id
+ editModeThemeMono = repository.findById(application.getEditModeThemeId(), READ_THEMES);
+ }
+
+ return editModeThemeMono.flatMap(editModeTheme -> {
+ if (editModeTheme.isSystemTheme()) { // system theme is set as edit mode theme
+ // Delete published mode theme if it was a copy of custom theme
+ return deletePublishedCustomizedThemeCopy(application.getPublishedModeThemeId()).then(
+ // Set the system theme id as edit and published mode theme id to application object
+ applicationRepository.setAppTheme(
+ applicationId, editModeTheme.getId(), editModeTheme.getId(), MANAGE_APPLICATIONS
+ )
+ ).thenReturn(editModeTheme);
+ } else { // a customized theme is set as edit mode theme, copy that theme for published mode
+ return saveThemeForApplication(
+ application.getPublishedModeThemeId(), editModeTheme, application, ApplicationMode.PUBLISHED
+ );
+ }
+ });
+ });
}
/**
* Creates a new theme if Theme with provided themeId is a system theme.
* It sets the properties from the provided theme resource to the existing or newly created theme.
* It'll also update the application if a new theme was created.
- * @param themeId ID of the existing theme that might be updated
- * @param resource new theme DTO that'll be stored as a new theme or override the existing theme
- * @param applicationId Application that contains the theme
+ * @param currentThemeId ID of the existing theme that might be updated
+ * @param targetThemeResource new theme DTO that'll be stored as a new theme or override the existing theme
+ * @param application Application that contains the theme
* @param applicationMode In which mode this theme will be set
* @return Updated or newly created theme Publisher
*/
- private Mono saveThemeForApplication(String themeId, Theme resource, String applicationId, ApplicationMode applicationMode) {
- return repository.findById(themeId)
- .flatMap(theme -> {
+ private Mono saveThemeForApplication(String currentThemeId, Theme targetThemeResource, Application application, ApplicationMode applicationMode) {
+ return repository.findById(currentThemeId, READ_THEMES)
+ .flatMap(currentTheme -> {
// set the edit mode values to published mode theme
- theme.setConfig(resource.getConfig());
- theme.setStylesheet(resource.getStylesheet());
- theme.setProperties(resource.getProperties());
- theme.setName(resource.getName());
+ currentTheme.setConfig(targetThemeResource.getConfig());
+ currentTheme.setStylesheet(targetThemeResource.getStylesheet());
+ currentTheme.setProperties(targetThemeResource.getProperties());
+ if(StringUtils.hasLength(targetThemeResource.getName())) {
+ currentTheme.setName(targetThemeResource.getName());
+ }
boolean newThemeCreated = false;
- if (theme.isSystemTheme()) {
+ if (currentTheme.isSystemTheme()) {
// if this is a system theme, create a new one
- theme.setId(null); // setting id to null will create a new theme
- theme.setSystemTheme(false);
+ currentTheme.setId(null); // setting id to null will create a new theme
+ currentTheme.setSystemTheme(false);
+ currentTheme.setPolicies(policyGenerator.getAllChildPolicies(
+ application.getPolicies(), Application.class, Theme.class
+ ));
newThemeCreated = true;
}
- return repository.save(theme).zipWith(Mono.just(newThemeCreated));
+ return repository.save(currentTheme).zipWith(Mono.just(newThemeCreated));
}).flatMap(savedThemeTuple -> {
Theme theme = savedThemeTuple.getT1();
- if (savedThemeTuple.getT2()) { // new published theme created, update the application
+ if (savedThemeTuple.getT2()) { // new theme created, update the application
if(applicationMode == ApplicationMode.EDIT) {
return applicationRepository.setAppTheme(
- applicationId, theme.getId(), null, MANAGE_APPLICATIONS
+ application.getId(), theme.getId(), null, MANAGE_APPLICATIONS
).thenReturn(theme);
} else {
return applicationRepository.setAppTheme(
- applicationId, null, theme.getId(), MANAGE_APPLICATIONS
+ application.getId(), null, theme.getId(), MANAGE_APPLICATIONS
).thenReturn(theme);
}
} else {
@@ -204,7 +259,117 @@ public class ThemeServiceCEImpl extends BaseService persistCurrentTheme(String applicationId, Theme resource) {
+ return applicationRepository.findById(applicationId, MANAGE_APPLICATIONS)
+ .switchIfEmpty(Mono.error(
+ new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, applicationId))
+ )
+ .flatMap(application -> {
+ String themeId = application.getEditModeThemeId();
+ if(!StringUtils.hasLength(themeId)) { // theme id is not present, raise error
+ return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
+ } else {
+ return repository.findById(themeId, READ_THEMES)
+ .map(theme -> Tuples.of(theme, application));
+ }
+ })
+ .flatMap(themeAndApplicationTuple -> {
+ Theme theme = themeAndApplicationTuple.getT1();
+ Application application = themeAndApplicationTuple.getT2();
+ theme.setId(null); // we'll create a copy so setting id to null
+ theme.setSystemTheme(false);
+ theme.setApplicationId(applicationId);
+ theme.setOrganizationId(application.getOrganizationId());
+ theme.setPolicies(policyGenerator.getAllChildPolicies(
+ application.getPolicies(), Application.class, Theme.class
+ ));
+
+ if(StringUtils.hasLength(resource.getName())) {
+ theme.setName(resource.getName());
+ } else {
+ theme.setName(theme.getName() + " copy");
+ }
+ return repository.save(theme);
+ });
+ }
+
+ /**
+ * This method will fetch a theme by id and delete this if it's not a system theme.
+ * When an app is published with a customized theme, we store a copy of that theme so that changes are available
+ * in published mode even user has changed the theme in edit mode. When user switches back to another theme and
+ * publish the application where that app was previously published with a custom theme, we should delete that copy.
+ * Otherwise there'll be a lot of orphan theme copies that were set a published mode once but are used no more.
+ * @param themeId id of the theme that'll be deleted
+ * @return deleted theme mono
+ */
+ private Mono deletePublishedCustomizedThemeCopy(String themeId) {
+ if(!StringUtils.hasLength(themeId)) {
+ return Mono.empty();
+ }
+ return repository.findById(themeId).flatMap(theme -> {
+ if(!theme.isSystemTheme()) {
+ return repository.deleteById(themeId).thenReturn(theme);
+ }
+ return Mono.just(theme);
+ });
+ }
+
+ @Override
+ public Mono delete(String themeId) {
+ return repository.findById(themeId, MANAGE_THEMES)
+ .switchIfEmpty(Mono.error(
+ new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.APPLICATION, FieldName.THEME))
+ ).flatMap(theme -> {
+ if (StringUtils.hasLength(theme.getApplicationId())) { // only persisted themes are allowed to delete
+ return repository.archive(theme);
+ } else {
+ return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_OPERATION));
+ }
+ });
+ }
+
+ @Override
+ public Mono getSystemTheme(String themeName) {
+ return repository.getSystemThemeByName(themeName);
+ }
+
+
+ @Override
+ public Mono getThemeById(String themeId, AclPermission permission) {
+ return repository.findById(themeId, permission);
+ }
+
+ @Override
+ public Mono save(Theme theme) {
+ return repository.save(theme);
+ }
+
+ @Override
+ public Mono updateName(String id, Theme themeDto) {
+ return repository.findById(id, MANAGE_THEMES)
+ .switchIfEmpty(Mono.error(
+ new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.THEME, id))
+ ).flatMap(theme -> {
+ if(StringUtils.hasLength(themeDto.getName())) {
+ theme.setName(themeDto.getName());
+ }
+ return repository.save(theme);
+ });
+ }
+
+ @Override
+ public Mono getOrSaveTheme(Theme theme, Application destApplication) {
+ if(theme == null) { // this application was exported without theme, assign the legacy theme to it
+ return repository.getSystemThemeByName(Theme.LEGACY_THEME_NAME); // return the default theme
+ } else if (theme.isSystemTheme()) {
+ return repository.getSystemThemeByName(theme.getName());
+ } else {
+ theme.setApplicationId(null);
+ theme.setOrganizationId(null);
+ theme.setPolicies(policyGenerator.getAllChildPolicies(
+ destApplication.getPolicies(), Application.class, Theme.class
+ ));
+ return repository.save(theme);
+ }
}
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserOrganizationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserOrganizationServiceCEImpl.java
index 271bae14ee..b2e606d3fa 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserOrganizationServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserOrganizationServiceCEImpl.java
@@ -13,6 +13,7 @@ import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Organization;
import com.appsmith.server.domains.Page;
+import com.appsmith.server.domains.Theme;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.UserRole;
import com.appsmith.server.exceptions.AppsmithError;
@@ -172,6 +173,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
Map commentThreadPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
applicationPolicyMap, Application.class, CommentThread.class
);
+ Map themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
+ applicationPolicyMap, Application.class, Theme.class
+ );
//Now update the organization policies
Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
updatedOrganization.setUserRoles(userRoles);
@@ -179,7 +183,7 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
// Update the underlying application/page/action
Flux updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, true);
Flux updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, true)
- .cache();
+ .cache(); // .cache is very important, as we will execute once and reuse the results multiple times
Flux updatedPagesFlux = updatedApplicationsFlux
.flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, true));
Flux updatedActionsFlux = updatedApplicationsFlux
@@ -188,14 +192,18 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
.flatMap(application -> policyUtils.updateWithPagePermissionsToAllItsActionCollections(application.getId(), actionPolicyMap, true));
Flux updatedThreadsFlux = updatedApplicationsFlux
.flatMap(application -> policyUtils.updateCommentThreadPermissions(application.getId(), commentThreadPolicyMap, user.getUsername(), true));
-
+ Flux updatedThemesFlux = updatedApplicationsFlux
+ .flatMap(application -> policyUtils.updateThemePolicies(
+ application, themePolicyMap, true
+ ));
return Mono.zip(
updatedDatasourcesFlux.collectList(),
updatedPagesFlux.collectList(),
updatedActionsFlux.collectList(),
updatedActionCollectionsFlux.collectList(),
Mono.just(updatedOrganization),
- updatedThreadsFlux.collectList()
+ updatedThreadsFlux.collectList(),
+ updatedThemesFlux.collectList()
)
.flatMap(tuple -> {
//By now all the datasources/applications/pages/actions have been updated. Just save the organization now
@@ -256,6 +264,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
Map commentThreadPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
applicationPolicyMap, Application.class, CommentThread.class
);
+ Map themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
+ applicationPolicyMap, Application.class, Theme.class
+ );
//Now update the organization policies
Organization updatedOrganization = policyUtils.removePoliciesFromExistingObject(orgPolicyMap, organization);
@@ -264,7 +275,7 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
// Update the underlying application/page/action
Flux updatedDatasourcesFlux = policyUtils.updateWithNewPoliciesToDatasourcesByOrgId(updatedOrganization.getId(), datasourcePolicyMap, false);
Flux updatedApplicationsFlux = policyUtils.updateWithNewPoliciesToApplicationsByOrgId(updatedOrganization.getId(), applicationPolicyMap, false)
- .cache();
+ .cache(); // .cache is very important, as we will execute once and reuse the results multiple times
Flux updatedPagesFlux = updatedApplicationsFlux
.flatMap(application -> policyUtils.updateWithApplicationPermissionsToAllItsPages(application.getId(), pagePolicyMap, false));
Flux updatedActionsFlux = updatedApplicationsFlux
@@ -275,6 +286,10 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
.flatMap(application -> policyUtils.updateCommentThreadPermissions(
application.getId(), commentThreadPolicyMap, user.getUsername(), false
));
+ Flux updatedThemesFlux = updatedApplicationsFlux
+ .flatMap(application -> policyUtils.updateThemePolicies(
+ application, themePolicyMap, false
+ ));
return Mono.zip(
updatedDatasourcesFlux.collectList(),
@@ -282,7 +297,8 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
updatedActionsFlux.collectList(),
updatedActionCollectionsFlux.collectList(),
updatedThreadsFlux.collectList(),
- Mono.just(updatedOrganization)
+ Mono.just(updatedOrganization),
+ updatedThemesFlux.collectList()
).flatMap(tuple -> {
//By now all the datasources/applications/pages/actions have been updated. Just save the organization now
Organization updatedOrgBeforeSave = tuple.getT6();
@@ -435,6 +451,9 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
applicationPolicyMap, Application.class, CommentThread.class
);
Map actionPolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(pagePolicyMap, Page.class, Action.class);
+ Map themePolicyMap = policyUtils.generateInheritedPoliciesFromSourcePolicies(
+ applicationPolicyMap, Application.class, Theme.class
+ );
// Now update the organization policies
Organization updatedOrganization = policyUtils.addPoliciesToExistingObject(orgPolicyMap, organization);
@@ -454,13 +473,19 @@ public class UserOrganizationServiceCEImpl implements UserOrganizationServiceCE
.flatMap(application -> policyUtils.updateCommentThreadPermissions(
application.getId(), commentThreadPolicyMap, null, true
));
+ Flux updatedThemesFlux = updatedApplicationsFlux
+ .flatMap(application -> policyUtils.updateThemePolicies(
+ application, themePolicyMap, true
+ ));
return Mono.when(
- updatedDatasourcesFlux.collectList(),
- updatedPagesFlux.collectList(),
- updatedActionsFlux.collectList(),
- updatedActionCollectionsFlux.collectList(),
- updatedThreadsFlux.collectList())
+ updatedDatasourcesFlux.collectList(),
+ updatedPagesFlux.collectList(),
+ updatedActionsFlux.collectList(),
+ updatedActionCollectionsFlux.collectList(),
+ updatedThreadsFlux.collectList(),
+ updatedThemesFlux.collectList()
+ )
// By now all the
// data sources/applications/pages/actions/action collections/comment threads
// have been updated. Just save the organization now
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationClonerImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationClonerImpl.java
index 4569f4f843..8f525bf9c7 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationClonerImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationClonerImpl.java
@@ -13,6 +13,7 @@ import com.appsmith.server.services.LayoutCollectionService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
+import com.appsmith.server.services.ThemeService;
import com.appsmith.server.services.UserService;
import com.appsmith.server.solutions.ce.ExamplesOrganizationClonerCEImpl;
import lombok.extern.slf4j.Slf4j;
@@ -35,10 +36,11 @@ public class ExamplesOrganizationClonerImpl extends ExamplesOrganizationClonerCE
NewActionService newActionService,
LayoutActionService layoutActionService,
ActionCollectionService actionCollectionService,
- LayoutCollectionService layoutCollectionService) {
+ LayoutCollectionService layoutCollectionService,
+ ThemeService themeService) {
super(organizationService, organizationRepository, datasourceService, datasourceRepository, configService,
sessionUserService, userService, applicationService, applicationPageService, newPageRepository,
- newActionService, layoutActionService, actionCollectionService, layoutCollectionService);
+ newActionService, layoutActionService, actionCollectionService, layoutCollectionService, themeService);
}
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
index 31efb7e34f..bf25f69945 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java
@@ -5,7 +5,6 @@ import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.NewActionRepository;
import com.appsmith.server.repositories.NewPageRepository;
import com.appsmith.server.repositories.PluginRepository;
-import com.appsmith.server.repositories.ThemeRepository;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
@@ -15,6 +14,7 @@ import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SequenceService;
import com.appsmith.server.services.SessionUserService;
+import com.appsmith.server.services.ThemeService;
import com.appsmith.server.solutions.ce.ImportExportApplicationServiceCEImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -38,11 +38,11 @@ public class ImportExportApplicationServiceImpl extends ImportExportApplicationS
ExamplesOrganizationCloner examplesOrganizationCloner,
ActionCollectionRepository actionCollectionRepository,
ActionCollectionService actionCollectionService,
- ThemeRepository themeRepository) {
+ ThemeService themeService) {
super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository,
organizationService, applicationService, newPageService, applicationPageService, newPageRepository,
newActionService, sequenceService, examplesOrganizationCloner, actionCollectionRepository,
- actionCollectionService, themeRepository);
+ actionCollectionService, themeService);
}
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ExamplesOrganizationClonerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ExamplesOrganizationClonerCEImpl.java
index 3a1bf806e1..b498edce03 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ExamplesOrganizationClonerCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ExamplesOrganizationClonerCEImpl.java
@@ -6,6 +6,7 @@ import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DefaultResources;
+import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
import com.appsmith.server.domains.Application;
@@ -13,6 +14,7 @@ import com.appsmith.server.domains.ApplicationPage;
import com.appsmith.server.domains.Layout;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Organization;
+import com.appsmith.server.domains.Theme;
import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.ActionCollectionDTO;
import com.appsmith.server.dtos.ActionDTO;
@@ -33,7 +35,9 @@ import com.appsmith.server.services.LayoutCollectionService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SessionUserService;
+import com.appsmith.server.services.ThemeService;
import com.appsmith.server.services.UserService;
+import com.mongodb.client.result.UpdateResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
@@ -68,6 +72,7 @@ public class ExamplesOrganizationClonerCEImpl implements ExamplesOrganizationClo
private final LayoutActionService layoutActionService;
private final ActionCollectionService actionCollectionService;
private final LayoutCollectionService layoutCollectionService;
+ private final ThemeService themeService;
public Mono cloneExamplesOrganization() {
return sessionUserService
@@ -417,17 +422,35 @@ public class ExamplesOrganizationClonerCEImpl implements ExamplesOrganizationClo
.flatMapMany(
savedApplication -> {
applicationIds.add(savedApplication.getId());
- return newPageRepository
- .findByApplicationId(templateApplicationId)
- .map(newPage -> {
- log.info("Preparing page for cloning {} {}.", newPage.getUnpublishedPage().getName(), newPage.getId());
- newPage.setApplicationId(savedApplication.getId());
- return newPage;
- });
+ return forkThemes(application, savedApplication).thenMany(
+ newPageRepository
+ .findByApplicationId(templateApplicationId)
+ .map(newPage -> {
+ log.info("Preparing page for cloning {} {}.", newPage.getUnpublishedPage().getName(), newPage.getId());
+ newPage.setApplicationId(savedApplication.getId());
+ return newPage;
+ })
+ );
}
);
}
+ private Mono forkThemes(Application srcApplication, Application destApplication) {
+ return Mono.zip(
+ themeService.cloneThemeToApplication(srcApplication.getEditModeThemeId(), destApplication),
+ themeService.cloneThemeToApplication(srcApplication.getPublishedModeThemeId(), destApplication)
+ ).flatMap(themes -> {
+ Theme editModeTheme = themes.getT1();
+ Theme publishedModeTheme = themes.getT2();
+ return applicationService.setAppTheme(
+ destApplication.getId(),
+ editModeTheme.getId(),
+ publishedModeTheme.getId(),
+ AclPermission.MANAGE_APPLICATIONS
+ );
+ });
+ }
+
private Mono cloneApplicationDocument(Application application) {
if (!StringUtils.hasText(application.getName())) {
return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.NAME));
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
index a88099451b..0d1b8fc3c4 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java
@@ -37,7 +37,6 @@ import com.appsmith.server.repositories.DatasourceRepository;
import com.appsmith.server.repositories.NewActionRepository;
import com.appsmith.server.repositories.NewPageRepository;
import com.appsmith.server.repositories.PluginRepository;
-import com.appsmith.server.repositories.ThemeRepository;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.ApplicationPageService;
import com.appsmith.server.services.ApplicationService;
@@ -47,6 +46,7 @@ import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.OrganizationService;
import com.appsmith.server.services.SequenceService;
import com.appsmith.server.services.SessionUserService;
+import com.appsmith.server.services.ThemeService;
import com.appsmith.server.solutions.ExamplesOrganizationCloner;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -77,6 +77,7 @@ import static com.appsmith.server.acl.AclPermission.EXPORT_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
+import static com.appsmith.server.acl.AclPermission.READ_THEMES;
@Slf4j
@RequiredArgsConstructor
@@ -97,7 +98,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
private final ExamplesOrganizationCloner examplesOrganizationCloner;
private final ActionCollectionRepository actionCollectionRepository;
private final ActionCollectionService actionCollectionService;
- private final ThemeRepository themeRepository;
+ private final ThemeService themeService;
private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON);
private static final String INVALID_JSON_FILE = "invalid json file";
@@ -153,8 +154,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
return plugin;
})
.then(applicationMono)
- .flatMap(application -> themeRepository.findById(application.getEditModeThemeId())
- .zipWith(themeRepository.findById(application.getPublishedModeThemeId()))
+ .flatMap(application -> themeService.getThemeById(application.getEditModeThemeId(), READ_THEMES)
+ .zipWith(themeService.getThemeById(application.getPublishedModeThemeId(), READ_THEMES))
.map(themesTuple -> {
Theme editModeTheme = exportTheme(themesTuple.getT1());
Theme publishedModeTheme = exportTheme(themesTuple.getT2());
@@ -581,7 +582,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
pluginMap.put(pluginReference, plugin.getId());
return plugin;
})
- .then(importThemes(importedApplication, importedDoc))
.then(organizationService.findById(organizationId, AclPermission.ORGANIZATION_MANAGE_APPLICATIONS))
.switchIfEmpty(Mono.error(
new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, FieldName.ORGANIZATION, organizationId))
@@ -707,6 +707,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
.then(applicationService.save(importedApplication));
})
)
+ .flatMap(savedAPP -> importThemes(savedAPP, importedDoc))
.flatMap(savedApp -> {
importedApplication.setId(savedApp.getId());
if (savedApp.getGitApplicationMetadata() != null) {
@@ -1524,26 +1525,23 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica
}
private Mono importThemes(Application application, ApplicationJson importedApplicationJson) {
- Mono importedEditModeTheme = getOrSaveTheme(importedApplicationJson.getEditModeTheme());
- Mono importedPublishedModeTheme = getOrSaveTheme(importedApplicationJson.getPublishedTheme());
+ Mono importedEditModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getEditModeTheme(), application);
+ Mono importedPublishedModeTheme = themeService.getOrSaveTheme(importedApplicationJson.getPublishedTheme(), application);
- return Mono.zip(importedEditModeTheme, importedPublishedModeTheme).map(importedThemesTuple -> {
- application.setEditModeThemeId(importedThemesTuple.getT1().getId());
- application.setPublishedModeThemeId(importedThemesTuple.getT2().getId());
- return application;
+ return Mono.zip(importedEditModeTheme, importedPublishedModeTheme).flatMap(importedThemesTuple -> {
+ String editModeThemeId = importedThemesTuple.getT1().getId();
+ String publishedModeThemeId = importedThemesTuple.getT2().getId();
+
+ application.setEditModeThemeId(editModeThemeId);
+ application.setPublishedModeThemeId(publishedModeThemeId);
+ // this will update the theme id in DB
+ // also returning the updated application object so that theme id are available to the next pipeline
+ return applicationService.setAppTheme(
+ application.getId(), editModeThemeId, publishedModeThemeId, MANAGE_APPLICATIONS
+ ).thenReturn(application);
});
}
- private Mono getOrSaveTheme(Theme theme) {
- if(theme == null) { // this application was exported without theme, assign the legacy theme to it
- return themeRepository.getSystemThemeByName(Theme.LEGACY_THEME_NAME); // return the default theme
- } else if (theme.isSystemTheme()) {
- return themeRepository.getSystemThemeByName(theme.getName());
- } else {
- return themeRepository.save(theme);
- }
- }
-
private void removeUnwantedFieldsFromApplicationDuringExport(Application application) {
application.setOrganizationId(null);
application.setPages(null);
diff --git a/app/server/appsmith-server/src/main/resources/system-themes.json b/app/server/appsmith-server/src/main/resources/system-themes.json
index 60a4253e09..7a5c854c32 100644
--- a/app/server/appsmith-server/src/main/resources/system-themes.json
+++ b/app/server/appsmith-server/src/main/resources/system-themes.json
@@ -10,8 +10,7 @@
"appBorderRadius": {
"none": "0px",
"md": "0.375rem",
- "lg": "1.5rem",
- "full": "9999px"
+ "lg": "1.5rem"
}
},
"boxShadow": {
@@ -41,135 +40,216 @@
"AUDIO_RECORDER_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "CAMERA_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "boxShadow": "none"
},
"CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"CHECKBOX_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CHECKBOX_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CONTAINER_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
+ "CIRCULAR_PROGRESS_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "CURRENCY_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "PHONE_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"DATE_PICKER_WIDGET2": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"FILE_PICKER_WIDGET_V2": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"FORM_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"FORM_BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"ICON_BUTTON_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"IFRAME_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"IMAGE_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"INPUT_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "INPUT_WIDGET_V2": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"LIST_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MAP_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ },
+ "MAP_CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MENU_BUTTON_WIDGET": {
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MODAL_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"DROP_DOWN_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
+ "PROGRESSBAR_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "RATE_WIDGET": {
+ "activeColor": "{{appsmith.theme.colors.primaryColor}}"
+ },
"RADIO_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
},
"RICH_TEXT_EDITOR_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"STATBOX_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SWITCH_WIDGET": {
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
+ },
+ "SWITCH_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
},
+ "SELECT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"TABLE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TABS_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TEXT_WIDGET": {
},
"VIDEO_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SINGLE_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
}
},
"properties": {
@@ -199,8 +279,7 @@
"appBorderRadius": {
"none": "0px",
"md": "0.375rem",
- "lg": "1.5rem",
- "full": "9999px"
+ "lg": "1.5rem"
}
},
"boxShadow": {
@@ -230,135 +309,216 @@
"AUDIO_RECORDER_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "CAMERA_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "boxShadow": "none"
},
"CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"CHECKBOX_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CHECKBOX_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CONTAINER_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
+ "CIRCULAR_PROGRESS_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "CURRENCY_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "PHONE_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"DATE_PICKER_WIDGET2": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"FILE_PICKER_WIDGET_V2": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"FORM_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"FORM_BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"ICON_BUTTON_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"IFRAME_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"IMAGE_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"INPUT_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "INPUT_WIDGET_V2": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"LIST_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MAP_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ },
+ "MAP_CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MENU_BUTTON_WIDGET": {
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MODAL_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"DROP_DOWN_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
+ "PROGRESSBAR_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "RATE_WIDGET": {
+ "activeColor": "{{appsmith.theme.colors.primaryColor}}"
+ },
"RADIO_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
},
"RICH_TEXT_EDITOR_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"STATBOX_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SWITCH_WIDGET": {
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
+ },
+ "SWITCH_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
},
+ "SELECT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"TABLE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TABS_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TEXT_WIDGET": {
},
"VIDEO_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SINGLE_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
}
},
"properties": {
@@ -388,8 +548,7 @@
"appBorderRadius": {
"none": "0px",
"md": "0.375rem",
- "lg": "1.5rem",
- "full": "9999px"
+ "lg": "1.5rem"
}
},
"boxShadow": {
@@ -419,135 +578,216 @@
"AUDIO_RECORDER_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "CAMERA_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "boxShadow": "none"
},
"CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"CHECKBOX_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CHECKBOX_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CONTAINER_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
+ "CIRCULAR_PROGRESS_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "CURRENCY_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "PHONE_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"DATE_PICKER_WIDGET2": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"FILE_PICKER_WIDGET_V2": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"FORM_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"FORM_BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"ICON_BUTTON_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"IFRAME_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"IMAGE_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"INPUT_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "INPUT_WIDGET_V2": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"LIST_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MAP_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ },
+ "MAP_CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MENU_BUTTON_WIDGET": {
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MODAL_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"DROP_DOWN_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
+ "PROGRESSBAR_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "RATE_WIDGET": {
+ "activeColor": "{{appsmith.theme.colors.primaryColor}}"
+ },
"RADIO_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
},
"RICH_TEXT_EDITOR_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"STATBOX_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SWITCH_WIDGET": {
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
+ },
+ "SWITCH_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
},
+ "SELECT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"TABLE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TABS_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TEXT_WIDGET": {
},
"VIDEO_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SINGLE_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
}
},
"properties": {
@@ -577,8 +817,7 @@
"appBorderRadius": {
"none": "0px",
"md": "0.375rem",
- "lg": "1.5rem",
- "full": "9999px"
+ "lg": "1.5rem"
}
},
"boxShadow": {
@@ -608,135 +847,216 @@
"AUDIO_RECORDER_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"BUTTON_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "CAMERA_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "boxShadow": "none"
},
"CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"CHECKBOX_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CHECKBOX_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"CONTAINER_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
+ "CIRCULAR_PROGRESS_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "CURRENCY_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "PHONE_INPUT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"DATE_PICKER_WIDGET2": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"FILE_PICKER_WIDGET_V2": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"FORM_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"FORM_BUTTON_WIDGET": {
"buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"ICON_BUTTON_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "buttonColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"IFRAME_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"IMAGE_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"INPUT_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
+ "INPUT_WIDGET_V2": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
"LIST_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MAP_WIDGET": {
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ },
+ "MAP_CHART_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"MENU_BUTTON_WIDGET": {
"menuColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
- "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MODAL_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"MULTI_SELECT_WIDGET": {
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
},
"DROP_DOWN_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "none"
},
+ "PROGRESSBAR_WIDGET": {
+ "fillColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ },
+ "RATE_WIDGET": {
+ "activeColor": "{{appsmith.theme.colors.primaryColor}}"
+ },
"RADIO_GROUP_WIDGET": {
- "backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
},
"RICH_TEXT_EDITOR_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"STATBOX_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SWITCH_WIDGET": {
+ "backgroundColor": "{{appsmith.theme.colors.primaryColor}}",
+ "boxShadow": "none"
+ },
+ "SWITCH_GROUP_WIDGET": {
"backgroundColor": "{{appsmith.theme.colors.primaryColor}}"
},
+ "SELECT_WIDGET": {
+ "primaryColor": "{{appsmith.theme.colors.primaryColor}}",
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
+ },
"TABLE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TABS_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"TEXT_WIDGET": {
},
"VIDEO_WIDGET": {
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}"
},
"SINGLE_SELECT_TREE_WIDGET": {
"primaryColor": "{{appsmith.theme.colors.primaryColor}}",
- "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}"
+ "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
+ "fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
+ "boxShadow": "none"
}
},
"properties": {
@@ -745,7 +1065,7 @@
"backgroundColor": "#F6F6F6"
},
"borderRadius": {
- "appBorderRadius": "1rem"
+ "appBorderRadius": "1.5rem"
},
"boxShadow": {
"appBoxShadow": "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)"
@@ -755,4 +1075,4 @@
}
}
}
-]
+]
\ No newline at end of file
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/PolicyUtilsTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/PolicyUtilsTest.java
index bb6aec84bb..896c49624d 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/PolicyUtilsTest.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/PolicyUtilsTest.java
@@ -46,7 +46,7 @@ public class PolicyUtilsTest {
CommentThread commentThread = new CommentThread();
commentThread.setApplicationId(testApplicationId);
Map commentThreadPolicies = policyUtils.generatePolicyFromPermission(
- Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), "api_user"
+ Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), "api_user"
);
commentThread.setPolicies(Set.copyOf(commentThreadPolicies.values()));
Mono saveThreadMono = commentThreadRepository.save(commentThread);
@@ -54,7 +54,7 @@ public class PolicyUtilsTest {
// add a new user and update the policies of the new user
String newUserName = "new_test_user";
Map commentThreadPoliciesForNewUser = policyUtils.generatePolicyFromPermission(
- Set.of(AclPermission.COMMENT_ON_THREAD), newUserName
+ Set.of(AclPermission.COMMENT_ON_THREADS), newUserName
);
Flux updateCommentThreads = policyUtils.updateCommentThreadPermissions(
testApplicationId, commentThreadPoliciesForNewUser, newUserName, true
@@ -64,7 +64,7 @@ public class PolicyUtilsTest {
Mono> applicationCommentList = saveThreadMono
.thenMany(updateCommentThreads)
.collectList()
- .thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREAD))
+ .thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREADS))
.collectList();
StepVerifier.create(applicationCommentList)
@@ -72,12 +72,12 @@ public class PolicyUtilsTest {
assertThat(commentThreads.size()).isEqualTo(1);
CommentThread commentThread1 = commentThreads.get(0);
Set policies = commentThread1.getPolicies();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), newUserName)).isFalse();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), newUserName)).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), newUserName)).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), newUserName)).isFalse();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), newUserName)).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), newUserName)).isTrue();
})
.verifyComplete();
}
@@ -100,7 +100,7 @@ public class PolicyUtilsTest {
user2.setEmail(newUserName);
Map commentThreadPolicies = policyUtils.generatePolicyFromPermissionForMultipleUsers(
- Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), List.of(user1, user2)
+ Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), List.of(user1, user2)
);
commentThread.setPolicies(Set.copyOf(commentThreadPolicies.values()));
@@ -108,7 +108,7 @@ public class PolicyUtilsTest {
// remove an user and update the policies of the user
Map commentThreadPoliciesForNewUser = policyUtils.generatePolicyFromPermission(
- Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), newUserName
+ Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), newUserName
);
Flux updateCommentThreads = policyUtils.updateCommentThreadPermissions(
testApplicationId, commentThreadPoliciesForNewUser, newUserName, false
@@ -118,7 +118,7 @@ public class PolicyUtilsTest {
Mono> applicationCommentList = saveThreadMono
.thenMany(updateCommentThreads)
.collectList()
- .thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREAD))
+ .thenMany(commentThreadRepository.findByApplicationId(testApplicationId, AclPermission.READ_THREADS))
.collectList();
StepVerifier.create(applicationCommentList)
@@ -126,12 +126,12 @@ public class PolicyUtilsTest {
assertThat(commentThreads.size()).isEqualTo(1);
CommentThread commentThread1 = commentThreads.get(0);
Set policies = commentThread1.getPolicies();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREAD.getValue(), newUserName)).isFalse();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREAD.getValue(), newUserName)).isFalse();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), "api_user")).isTrue();
- assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREAD.getValue(), newUserName)).isFalse();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.MANAGE_THREADS.getValue(), newUserName)).isFalse();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.READ_THREADS.getValue(), newUserName)).isFalse();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), "api_user")).isTrue();
+ assertThat(policyUtils.isPermissionPresentForUser(policies, AclPermission.COMMENT_ON_THREADS.getValue(), newUserName)).isFalse();
})
.verifyComplete();
}
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/CustomCommentThreadRepositoryImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/CustomCommentThreadRepositoryImplTest.java
index 287e37ad91..caba6d1e25 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/CustomCommentThreadRepositoryImplTest.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/CustomCommentThreadRepositoryImplTest.java
@@ -40,7 +40,7 @@ public class CustomCommentThreadRepositoryImplTest {
User user = new User();
user.setEmail(userEmail);
- Map policyMap = policyUtils.generatePolicyFromPermission(Set.of(AclPermission.READ_THREAD), user);
+ Map policyMap = policyUtils.generatePolicyFromPermission(Set.of(AclPermission.READ_THREADS), user);
thread.setPolicies(Set.copyOf(policyMap.values()));
return thread;
}
@@ -54,7 +54,7 @@ public class CustomCommentThreadRepositoryImplTest {
user.setEmail(userEmail);
Map policyMap = policyUtils.generatePolicyFromPermission(
- Set.of(AclPermission.MANAGE_THREAD, AclPermission.COMMENT_ON_THREAD), user);
+ Set.of(AclPermission.MANAGE_THREADS, AclPermission.COMMENT_ON_THREADS), user);
HashSet policySet = new HashSet<>();
// not using Set.of here because the caller function may need to add more policies
@@ -167,7 +167,7 @@ public class CustomCommentThreadRepositoryImplTest {
CommentThreadFilterDTO filterDTO = new CommentThreadFilterDTO();
filterDTO.setApplicationId("sample-application-id-1");
filterDTO.setResolved(false);
- return commentThreadRepository.find(filterDTO, AclPermission.READ_THREAD).collectList();
+ return commentThreadRepository.find(filterDTO, AclPermission.READ_THREADS).collectList();
});
StepVerifier.create(listMono).assertNext(
@@ -255,7 +255,7 @@ public class CustomCommentThreadRepositoryImplTest {
Mono