From 65568a4e1392b4d9df13cf8f8151df2a4b212c58 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 11 Mar 2021 07:51:48 +0530 Subject: [PATCH] Add more devices in AppLayout options (#3451) * Remove width and introduce more AppLayout type enums * Fix: Adding min width to layout options. Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> --- .../DynamicLayout/DynamicLayout_spec.js | 4 +- app/client/src/constants/AppConstants.ts | 3 - app/client/src/constants/WidgetConstants.tsx | 23 ++++++ .../Editor/MainContainerLayoutControl.tsx | 37 ++++----- .../entityReducers/pageListReducer.tsx | 10 ++- .../src/utils/hooks/useDynamicAppLayout.tsx | 20 +++-- .../appsmith/server/domains/Application.java | 24 ++++-- .../server/migrations/DatabaseChangelog.java | 77 +++++++++++++++++++ .../services/ApplicationServiceTest.java | 2 +- 9 files changed, 155 insertions(+), 45 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicLayout/DynamicLayout_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicLayout/DynamicLayout_spec.js index 1eb49a99dd..a7ad843916 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicLayout/DynamicLayout_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DynamicLayout/DynamicLayout_spec.js @@ -17,7 +17,7 @@ describe("Dynamic Layout Functionality", function() { .click({ force: true }); cy.get(commonlocators.canvas) .invoke("width") - .should("be.eq", 720); + .should("be.eq", 450); }); it("Dynamic Layout - New Page should have selected Layout", function() { cy.get(pages.AddPage) @@ -25,6 +25,6 @@ describe("Dynamic Layout Functionality", function() { .click(); cy.get(commonlocators.canvas) .invoke("width") - .should("be.eq", 720); + .should("be.eq", 450); }); }); diff --git a/app/client/src/constants/AppConstants.ts b/app/client/src/constants/AppConstants.ts index daec1ea6bc..7aa316c662 100644 --- a/app/client/src/constants/AppConstants.ts +++ b/app/client/src/constants/AppConstants.ts @@ -1,8 +1,5 @@ import localStorage from "utils/localStorage"; -export const CANVAS_DEFAULT_WIDTH_PX = 1224; -export const CANVAS_TABLET_WIDTH_PX = 1024; -export const CANVAS_MOBILE_WIDTH_PX = 720; export const CANVAS_DEFAULT_HEIGHT_PX = 1292; export const CANVAS_DEFAULT_GRID_HEIGHT_PX = 1; export const CANVAS_DEFAULT_GRID_WIDTH_PX = 1; diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index 5c3123eddf..02d73f8c81 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -1,3 +1,5 @@ +import { SupportedLayouts } from "reducers/entityReducers/pageListReducer"; + export enum WidgetTypes { BUTTON_WIDGET = "BUTTON_WIDGET", TEXT_WIDGET = "TEXT_WIDGET", @@ -71,6 +73,27 @@ export const CSSUnits: { [id: string]: CSSUnit } = { RELATIVE_PARENT: "%", }; +interface LayoutConfig { + minWidth: number; + maxWidth: number; +} + +type LayoutConfigurations = Record; +export const DefaultLayoutType: SupportedLayouts = "DESKTOP"; +export const layoutConfigurations: LayoutConfigurations = { + TABLET_LARGE: { + minWidth: 960, + maxWidth: 1080, + }, + MOBILE: { + minWidth: 350, + maxWidth: 450, + }, + DESKTOP: { minWidth: 1160, maxWidth: 1280 }, + TABLET: { minWidth: 650, maxWidth: 800 }, + FLUID: { minWidth: -1, maxWidth: -1 }, +}; + export const GridDefaults = { DEFAULT_CELL_SIZE: 1, DEFAULT_WIDGET_WIDTH: 200, diff --git a/app/client/src/pages/Editor/MainContainerLayoutControl.tsx b/app/client/src/pages/Editor/MainContainerLayoutControl.tsx index 0f3cda3333..9b5e58c7a9 100644 --- a/app/client/src/pages/Editor/MainContainerLayoutControl.tsx +++ b/app/client/src/pages/Editor/MainContainerLayoutControl.tsx @@ -1,18 +1,13 @@ import { updateApplicationLayout } from "actions/applicationActions"; import Dropdown from "components/ads/Dropdown"; import Icon, { IconName, IconSize } from "components/ads/Icon"; -import { - CANVAS_DEFAULT_WIDTH_PX, - CANVAS_MOBILE_WIDTH_PX, - CANVAS_TABLET_WIDTH_PX, -} from "constants/AppConstants"; import { Colors } from "constants/Colors"; import React from "react"; import { useDispatch } from "react-redux"; import { AppState } from "reducers"; import { AppLayoutConfig, - AppLayoutType, + SupportedLayouts, } from "reducers/entityReducers/pageListReducer"; import { getCurrentApplicationId, @@ -23,17 +18,14 @@ import { useSelector } from "store"; import styled, { ThemeProvider } from "styled-components"; import { noop } from "utils/AppsmithUtils"; -type SupportedLayouts = "Desktop" | "Tablet" | "Mobile Device" | "Fluid Width"; interface AppsmithLayoutConfigOption { - name: SupportedLayouts; - type: AppLayoutType; - width: number; + name: string; + type: SupportedLayouts; icon?: IconName; } export const AppsmithDefaultLayout: AppLayoutConfig = { - type: "FLUID", - width: CANVAS_DEFAULT_WIDTH_PX, + type: "DESKTOP", }; const AppsmithLayouts: AppsmithLayoutConfigOption[] = [ @@ -42,22 +34,24 @@ const AppsmithLayouts: AppsmithLayoutConfigOption[] = [ ...AppsmithDefaultLayout, icon: "desktop", }, + { + name: "Tablet(Large)", + type: "TABLET_LARGE", + icon: "tablet", + }, { name: "Tablet", - type: "FLUID", - width: CANVAS_TABLET_WIDTH_PX, + type: "TABLET", icon: "tablet", }, { name: "Mobile Device", - type: "FLUID", - width: CANVAS_MOBILE_WIDTH_PX, + type: "MOBILE", icon: "mobile", }, { name: "Fluid Width", type: "FLUID", - width: -1, icon: "fluid", }, ]; @@ -100,15 +94,11 @@ export const MainContainerLayoutControl: React.FC = () => { onSelect: () => updateAppLayout({ type: each.type, - width: each.width, }), }; }); const selectedLayout = appLayout - ? layoutOptions.find( - (each) => - each.type === appLayout.type && each.width === appLayout.width, - ) + ? layoutOptions.find((each) => each.type === appLayout.type) : layoutOptions[0]; const dispatch = useDispatch(); const lightTheme = useSelector((state: AppState) => @@ -116,12 +106,11 @@ export const MainContainerLayoutControl: React.FC = () => { ); const updateAppLayout = (layoutConfig: AppLayoutConfig) => { - const { type, width } = layoutConfig; + const { type } = layoutConfig; dispatch( updateApplicationLayout(appId || "", { appLayout: { type, - width, }, }), ); diff --git a/app/client/src/reducers/entityReducers/pageListReducer.tsx b/app/client/src/reducers/entityReducers/pageListReducer.tsx index fea48ffc42..0230fd3403 100644 --- a/app/client/src/reducers/entityReducers/pageListReducer.tsx +++ b/app/client/src/reducers/entityReducers/pageListReducer.tsx @@ -105,10 +105,14 @@ export const pageListReducer = createReducer(initialState, { }, }); -export type AppLayoutType = "FIXED" | "FLUID"; +export type SupportedLayouts = + | "DESKTOP" + | "TABLET_LARGE" + | "TABLET" + | "MOBILE" + | "FLUID"; export interface AppLayoutConfig { - type: AppLayoutType; - width: number; + type: SupportedLayouts; } export interface PageListReduxState { diff --git a/app/client/src/utils/hooks/useDynamicAppLayout.tsx b/app/client/src/utils/hooks/useDynamicAppLayout.tsx index 6a42469897..d3f5394c7c 100644 --- a/app/client/src/utils/hooks/useDynamicAppLayout.tsx +++ b/app/client/src/utils/hooks/useDynamicAppLayout.tsx @@ -1,5 +1,9 @@ import { theme } from "constants/DefaultTheme"; import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { + DefaultLayoutType, + layoutConfigurations, +} from "constants/WidgetConstants"; import { debounce } from "lodash"; import { AppsmithDefaultLayout } from "pages/Editor/MainContainerLayoutControl"; import { useCallback, useEffect } from "react"; @@ -41,13 +45,17 @@ export const useDynamicAppLayout = () => { screenWidth: number, appLayout = AppsmithDefaultLayout, ) => { - const { type, width: layoutMaxWidth } = appLayout; - const layoutWidth = - type === "FLUID" - ? calculateFluidMaxWidth(screenWidth, layoutMaxWidth) - : layoutMaxWidth; + const { type } = appLayout; + const { minWidth = -1, maxWidth = -1 } = + layoutConfigurations[type] || layoutConfigurations[DefaultLayoutType]; + const calculatedMinWidth = + appMode === "EDIT" ? minWidth - parseInt(theme.sidebarWidth) : minWidth; + const layoutWidth = calculateFluidMaxWidth(screenWidth, maxWidth); const { rightColumn } = mainContainer; - if (rightColumn !== layoutWidth) { + if ( + (type === "FLUID" || calculatedMinWidth <= layoutWidth) && + rightColumn !== layoutWidth + ) { dispatch({ type: ReduxActionTypes.UPDATE_CANVAS_LAYOUT, payload: { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java index 6c523d5abc..31f7c8dca4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java @@ -71,10 +71,8 @@ public class Application extends BaseDomain { this.clonedFromApplicationId = application.getId(); this.color = application.getColor(); this.icon = application.getIcon(); - this.unpublishedAppLayout = application.getUnpublishedAppLayout() == null ? null - : new AppLayout(application.getUnpublishedAppLayout().type, application.getUnpublishedAppLayout().getWidth()); - this.publishedAppLayout = application.getPublishedAppLayout() == null ? null - : new AppLayout(application.getPublishedAppLayout().type, application.getPublishedAppLayout().getWidth()); + this.unpublishedAppLayout = application.getUnpublishedAppLayout() == null ? null : new AppLayout(application.getUnpublishedAppLayout().type); + this.publishedAppLayout = application.getPublishedAppLayout() == null ? null : new AppLayout(application.getPublishedAppLayout().type); } public List getPages() { @@ -98,10 +96,24 @@ public class Application extends BaseDomain { @AllArgsConstructor public static class AppLayout implements Serializable { Type type; - Integer width; + + /** + * @deprecated The following field is deprecated and now removed, because it's needed in a migration. After the + * migration has been run, it may be removed (along with the migration or there'll be compile errors there). + */ + @JsonIgnore + @Deprecated(forRemoval = true) + Integer width = null; + + public AppLayout(Type type) { + this.type = type; + } public enum Type { - FIXED, + DESKTOP, + TABLET_LARGE, + TABLET, + MOBILE, FLUID, } } 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 e1816df99f..25f18a0037 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 @@ -45,6 +45,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; import com.google.gson.Gson; +import com.mongodb.MongoClient; import com.mongodb.MongoException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; @@ -60,11 +61,13 @@ import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.core.CollectionCallback; +import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.index.CompoundIndexDefinition; import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.index.IndexOperations; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; @@ -1916,4 +1919,78 @@ public class DatabaseChangelog { mongoTemplate.save(datasource); }); } + + @ChangeSet(order = "059", id = "change-applayout-type-definition", author = "") + public void changeAppLayoutTypeDefinition(MongoOperations mongoOperations, MongoClient mongoClient) { + // Unset an old version of this field, that is no longer used. + mongoOperations.updateMulti( + query(where("appLayout").exists(true)), + new Update().unset("appLayout"), + Application.class + ); + + // For the published and unpublished app layouts, migrate the old way of specifying the device width to the new + // way of doing it. Table of migrations: + // Desktop: Old - 1224, New 1160 - 1280 + // Tablet L: Old - NA, New 960 - 1080 + // Tablet: Old - 1024, New 650 - 800 + // Mobile: Old - 720, New 350 - 450 + final Criteria criteria = new Criteria().orOperator( + where(fieldName(QApplication.application.unpublishedAppLayout)).exists(true), + where(fieldName(QApplication.application.publishedAppLayout)).exists(true) + ); + + final Query query = query(criteria); + query.fields() + .include(fieldName(QApplication.application.unpublishedAppLayout)) + .include(fieldName(QApplication.application.publishedAppLayout)); + + List apps = mongoOperations.find(query, Application.class); + + for (final Application app : apps) { + final Integer unpublishedWidth = app.getUnpublishedAppLayout() == null ? null : app.getUnpublishedAppLayout().getWidth(); + final Integer publishedWidth = app.getPublishedAppLayout() == null ? null : app.getPublishedAppLayout().getWidth(); + final Update update = new Update().unset("unpublishedAppLayout.width").unset("publishedAppLayout.width"); + + if (unpublishedWidth != null) { + final String typeField = "unpublishedAppLayout.type"; + if (unpublishedWidth == -1) { + update.set(typeField, Application.AppLayout.Type.FLUID.name()); + } else { + if (unpublishedWidth == 1024) { + update.set(typeField, Application.AppLayout.Type.TABLET.name()); + } else if (unpublishedWidth == 720) { + update.set(typeField, Application.AppLayout.Type.MOBILE.name()); + } else { + // Default to Desktop. + update.set(typeField, Application.AppLayout.Type.DESKTOP.name()); + } + } + } + + if (publishedWidth != null) { + final String typeField = "publishedAppLayout.type"; + if (publishedWidth == -1) { + update.set(typeField, Application.AppLayout.Type.FLUID.name()); + } else { + if (publishedWidth == 1024) { + update.set(typeField, Application.AppLayout.Type.TABLET.name()); + } else if (publishedWidth == 720) { + update.set(typeField, Application.AppLayout.Type.MOBILE.name()); + } else { + // Default to Desktop. + update.set(typeField, Application.AppLayout.Type.DESKTOP.name()); + } + } + } + + mongoOperations.updateFirst( + query(where(fieldName(QApplication.application.id)).is(app.getId())), + update, + Application.class + ); + + } + } + } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java index 3dbfe28bf8..dbff513123 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java @@ -697,7 +697,7 @@ public class ApplicationServiceTest { Application testApplication = new Application(); String appName = "ApplicationServiceTest Publish Application"; testApplication.setName(appName); - testApplication.setAppLayout(new Application.AppLayout(Application.AppLayout.Type.FIXED, 1024)); + testApplication.setAppLayout(new Application.AppLayout(Application.AppLayout.Type.DESKTOP)); Mono applicationMono = applicationPageService.createApplication(testApplication, orgId) .flatMap(application -> applicationPageService.publish(application.getId())) .then(applicationService.findByName(appName, MANAGE_APPLICATIONS))