diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_Footer_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_Footer_spec.js index 11cc4a2e221e..c6bfa82588ab 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_Footer_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_Footer_spec.js @@ -46,7 +46,7 @@ describe("JSONForm Footer spec", () => { cy.get(".t--jsonform-footer").then(($footer) => { const gap = $footer.prop("offsetTop") - $body.prop("scrollHeight"); - expect(gap).equals(0); + expect(gap).equals(1); }); }); }); @@ -72,7 +72,7 @@ describe("JSONForm Footer spec", () => { $footer.prop("offsetHeight") - $form.prop("offsetHeight"); - expect(gap).equals(0); + expect(gap).equals(1); }); }); }); @@ -91,7 +91,7 @@ describe("JSONForm Footer spec", () => { $footer.prop("offsetHeight") - $form.prop("scrollHeight"); - expect(gap).equals(0); + expect(gap).equals(1); }); }); }); diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Multiselect/MultiSelect3_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Multiselect/MultiSelect3_spec.js index 8e96a9ffb024..f0a48b3ea920 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Multiselect/MultiSelect3_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Multiselect/MultiSelect3_spec.js @@ -28,7 +28,7 @@ describe("Dropdown Widget Functionality", function () { .find(widgetLocators.menuButton) .invoke("outerWidth") .then((width) => { - expect(parseInt(width)).to.equal(147); + expect(parseInt(width)).to.equal(146); }); cy.get(formWidgetsPage.menuButtonWidget) .find(widgetLocators.menuButton) @@ -40,7 +40,7 @@ describe("Dropdown Widget Functionality", function () { cy.get(".menu-button-popover") .invoke("outerWidth") .then((width) => { - expect(parseInt(width)).to.equal(147); + expect(parseInt(width)).to.equal(146); }); // MultiSelect diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Widgets_Labels_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Widgets_Labels_spec.js index a2738d4d040c..70642df2991f 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Widgets_Labels_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/Widgets/Widgets_Labels_spec.js @@ -8,8 +8,7 @@ describe("Label feature", () => { it("CheckboxGroupWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "checkboxgroupwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='checkboxgroup-container']", isCompact: true, labelText: "Name", @@ -22,8 +21,7 @@ describe("Label feature", () => { it("CurrencyInputWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "currencyinputwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='input-container']", isCompact: true, labelText: "Name", @@ -36,8 +34,7 @@ describe("Label feature", () => { it("DatePickerWidget2 label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "datepickerwidget2", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='datepicker-container']", isCompact: true, labelText: "Name", @@ -50,8 +47,7 @@ describe("Label feature", () => { it("InputWidgetV2 label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "inputwidgetv2", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='input-container']", isCompact: true, labelText: "Name", @@ -64,8 +60,7 @@ describe("Label feature", () => { it("MultiSelectTreeWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "multiselecttreewidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='multitreeselect-container']", isCompact: true, labelText: "Name", @@ -78,8 +73,7 @@ describe("Label feature", () => { it("MultiSelectWidgetV2 label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "multiselectwidgetv2", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='multiselect-container']", isCompact: true, labelText: "Name", @@ -92,8 +86,7 @@ describe("Label feature", () => { it("PhoneInputWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "phoneinputwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='input-container']", isCompact: true, labelText: "Name", @@ -106,8 +99,7 @@ describe("Label feature", () => { it("RadioGroupWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "radiogroupwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='radiogroup-container']", isCompact: true, labelText: "Name", @@ -120,8 +112,7 @@ describe("Label feature", () => { it("RichTextEditorWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "richtexteditorwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='rte-container']", isCompact: false, labelText: "Name", @@ -134,8 +125,7 @@ describe("Label feature", () => { it("SelectWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "selectwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='select-container']", isCompact: true, labelText: "Name", @@ -148,8 +138,7 @@ describe("Label feature", () => { it("SingleSelectTreeWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "singleselecttreewidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='treeselect-container']", isCompact: true, labelText: "Name", @@ -162,8 +151,7 @@ describe("Label feature", () => { it("SwitchGroupWidget label properties: Text, Position, Alignment, Width", () => { const options = { widgetName: "switchgroupwidget", - // parentColumnSpace: 11.90625, - parentColumnSpace: 11.9375, + parentColumnSpace: 11.90625, containerSelector: "[data-testid='switchgroup-container']", isCompact: true, labelText: "Name", diff --git a/app/client/package.json b/app/client/package.json index 7bf8fd2f43f7..766460db6037 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -75,7 +75,7 @@ "cypress-log-to-output": "^1.1.2", "dayjs": "^1.10.6", "deep-diff": "^1.0.2", - "design-system-old": "npm:@appsmithorg/design-system-old@1.0.52", + "design-system-old": "npm:@appsmithorg/design-system-old@1.0.53-alpha.1", "downloadjs": "^1.4.7", "exceljs": "^4.3.0", "fast-deep-equal": "^3.1.3", @@ -329,4 +329,4 @@ "trim": "0.0.3", "webpack": "5.76.0" } -} +} \ No newline at end of file diff --git a/app/client/src/actions/autoLayoutActions.ts b/app/client/src/actions/autoLayoutActions.ts index 45c0ea55b541..453db8beb77d 100644 --- a/app/client/src/actions/autoLayoutActions.ts +++ b/app/client/src/actions/autoLayoutActions.ts @@ -1,14 +1,88 @@ import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import type { AppPositioningTypes } from "reducers/entityReducers/pageListReducer"; +import type { + CONVERSION_STATES, + SnapShotDetails, +} from "reducers/uiReducers/layoutConversionReducer"; +/** + * Calculate size and position changes owing to minSizes and flex wrap. + * This function is triggered the first time mobile viewport (480px) is encountered. + * It is also called when increasing viewport size from mobile to desktop. + */ export const updateLayoutForMobileBreakpointAction = ( parentId: string, isMobile: boolean, canvasWidth: number, -) => ({ - type: ReduxActionTypes.RECALCULATE_COLUMNS, - payload: { - parentId, - isMobile, - canvasWidth, - }, -}); +) => { + return { + type: ReduxActionTypes.RECALCULATE_COLUMNS, + payload: { + parentId, + isMobile, + canvasWidth, + }, + }; +}; + +export const updateLayoutPositioning = ( + positioningType: AppPositioningTypes, +) => { + return { + type: ReduxActionTypes.UPDATE_LAYOUT_POSITIONING, + payload: positioningType, + }; +}; + +export const setLayoutConversionStateAction = ( + conversionState: CONVERSION_STATES, + error?: Error, +) => { + return { + type: ReduxActionTypes.SET_LAYOUT_CONVERSION_STATE, + payload: { conversionState, error }, + }; +}; + +export const updateSnapshotDetails = ( + snapShotDetails: SnapShotDetails | undefined, +) => { + return { + type: ReduxActionTypes.UPDATE_SNAPSHOT_DETAILS, + payload: snapShotDetails, + }; +}; +export function updateWidgetDimensionAction( + widgetId: string, + width: number, + height: number, +) { + return { + type: ReduxActionTypes.UPDATE_WIDGET_DIMENSIONS, + payload: { + widgetId, + width, + height, + }, + }; +} + +export const setConversionStart = (conversionState: CONVERSION_STATES) => { + return { + type: ReduxActionTypes.START_CONVERSION_FLOW, + payload: conversionState, + }; +}; + +export const setConversionStop = () => { + return { + type: ReduxActionTypes.STOP_CONVERSION_FLOW, + }; +}; + +export const setAutoCanvasResizing = (isAutoCanvasResizing: boolean) => { + return { + type: ReduxActionTypes.SET_AUTO_CANVAS_RESIZING, + payload: isAutoCanvasResizing, + }; +}; diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index da1c0b78fe87..ea1c471f28b0 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -185,6 +185,24 @@ export const createPage = ( }; }; +export const createNewPageFromEntities = ( + applicationId: string, + pageName: string, + blockNavigation?: boolean, +) => { + AnalyticsUtil.logEvent("CREATE_PAGE", { + pageName, + }); + return { + type: ReduxActionTypes.CREATE_NEW_PAGE_FROM_ENTITIES, + payload: { + applicationId, + name: pageName, + blockNavigation, + }, + }; +}; + /** * action to clone page * @@ -282,10 +300,14 @@ export type MultipleWidgetDeletePayload = { export type WidgetResize = { widgetId: string; parentId: string; - leftColumn: number; - rightColumn: number; - topRow: number; - bottomRow: number; + leftColumn?: number; + rightColumn?: number; + topRow?: number; + bottomRow?: number; + mobileLeftColumn?: number; + mobileRightColumn?: number; + mobileTopRow?: number; + mobileBottomRow?: number; snapColumnSpace: number; snapRowSpace: number; }; diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index a378a71f7f25..0be898d124dc 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -3,10 +3,14 @@ import type { ApiResponse } from "./ApiResponses"; import type { AxiosPromise } from "axios"; import type { AppColorCode } from "constants/DefaultTheme"; import type { AppIconName } from "design-system-old"; -import type { AppLayoutConfig } from "reducers/entityReducers/pageListReducer"; +import type { + AppLayoutConfig, + AppPositioningTypeConfig, +} from "reducers/entityReducers/pageListReducer"; import type { APP_MODE } from "entities/App"; import type { ApplicationVersion } from "actions/applicationActions"; import type { Datasource } from "entities/Datasource"; +import { getSnapShotAPIRoute } from "ce/constants/ApiConstants"; export type EvaluationVersion = number; @@ -110,6 +114,9 @@ export type UpdateApplicationPayload = { currentApp?: boolean; appLayout?: AppLayoutConfig; applicationVersion?: number; + applicationDetail?: { + appPositioning?: AppPositioningTypeConfig; + }; embedSetting?: AppEmbedSetting; }; @@ -208,6 +215,10 @@ export interface PageDefaultMeta { default: boolean; } +export interface snapShotApplicationRequest { + applicationId: string; +} + class ApplicationApi extends Api { static baseURL = "v1/applications"; static publishURLPath = (applicationId: string) => @@ -337,6 +348,22 @@ class ApplicationApi extends Api { }, ); } + + static createApplicationSnapShot(request: snapShotApplicationRequest) { + return Api.post(getSnapShotAPIRoute(request.applicationId)); + } + + static getSnapShotDetails(request: snapShotApplicationRequest) { + return Api.get(getSnapShotAPIRoute(request.applicationId)); + } + + static restoreApplicationFromSnapshot(request: snapShotApplicationRequest) { + return Api.post(getSnapShotAPIRoute(request.applicationId) + "/restore"); + } + + static deleteApplicationSnapShot(request: snapShotApplicationRequest) { + return Api.delete(getSnapShotAPIRoute(request.applicationId)); + } } export default ApplicationApi; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index 822b1aed3258..1ad7e3a1f18c 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -39,6 +39,14 @@ export type PageLayout = { layoutOnLoadActionErrors?: LayoutOnLoadActionErrors[]; }; +export interface PageLayoutsRequest { + layoutId: string; + pageId: string; + layout: { + dsl: DSLWidget; + }; +} + export type FetchPageResponseData = { id: string; name: string; @@ -179,6 +187,10 @@ class PageApi extends Api { return !!bustCache ? url + "?v=" + +new Date() : url; }; + static getSaveAllPagesURL = (applicationId: string) => { + return `v1/layouts/application/${applicationId}`; + }; + static updatePageUrl = (pageId: string) => `${PageApi.url}/${pageId}`; static setPageOrderUrl = ( applicationId: string, @@ -212,6 +224,15 @@ class PageApi extends Api { ); } + static saveAllPages( + applicationId: string, + pageLayouts: PageLayoutsRequest[], + ) { + return Api.put(PageApi.getSaveAllPagesURL(applicationId), { + pageLayouts, + }); + } + static fetchPublishedPage( pageRequest: FetchPublishedPageRequest, ): AxiosPromise { diff --git a/app/client/src/ce/constants/ApiConstants.tsx b/app/client/src/ce/constants/ApiConstants.tsx index 2d4bdbf547df..fd095b272973 100644 --- a/app/client/src/ce/constants/ApiConstants.tsx +++ b/app/client/src/ce/constants/ApiConstants.tsx @@ -41,3 +41,6 @@ export const SUPER_USER_SUBMIT_PATH = `${SIGNUP_SUBMIT_PATH}/super`; export const getExportAppAPIRoute = (applicationId: string) => `/api/v1/applications/export/${applicationId}`; + +export const getSnapShotAPIRoute = (applicationId: string) => + `/v1/applications/snapshot/${applicationId}`; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 939c5ebca217..6e4be96a09a8 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -10,8 +10,12 @@ import type { LayoutOnLoadActionErrors, PageAction, } from "constants/AppsmithActionConstants/ActionConstants"; -import type { AppLayoutConfig } from "reducers/entityReducers/pageListReducer"; +import type { + AppLayoutConfig, + AppPositioningTypeConfig, +} from "reducers/entityReducers/pageListReducer"; import type { WidgetCardProps, WidgetProps } from "widgets/BaseWidget"; +import type { DSLWidget } from "widgets/constants"; export const ReduxSagaChannels = { WEBSOCKET_APP_LEVEL_WRITE_CHANNEL: "WEBSOCKET_APP_LEVEL_WRITE_CHANNEL", @@ -280,6 +284,7 @@ export const ReduxActionTypes = { CHANGE_APPVIEW_ACCESS_INIT: "CHANGE_APPVIEW_ACCESS_INIT", CHANGE_APPVIEW_ACCESS_SUCCESS: "CHANGE_APPVIEW_ACCESS_SUCCESS", CREATE_PAGE_INIT: "CREATE_PAGE_INIT", + CREATE_NEW_PAGE_FROM_ENTITIES: "CREATE_NEW_PAGE_FROM_ENTITIES", CREATE_PAGE_SUCCESS: "CREATE_PAGE_SUCCESS", FETCH_PAGE_LIST_INIT: "FETCH_PAGE_LIST_INIT", FETCH_PAGE_LIST_SUCCESS: "FETCH_PAGE_LIST_SUCCESS", @@ -356,6 +361,7 @@ export const ReduxActionTypes = { SET_DRAGGING_CANVAS: "SET_DRAGGING_CANVAS", SET_NEW_WIDGET_DRAGGING: "SET_NEW_WIDGET_DRAGGING", SET_WIDGET_RESIZING: "SET_WIDGET_RESIZING", + SET_AUTO_CANVAS_RESIZING: "SET_AUTO_CANVAS_RESIZING", ADD_SUGGESTED_WIDGET: "ADD_SUGGESTED_WIDGET", MODIFY_META_WIDGETS: "MODIFY_META_WIDGETS", DELETE_META_WIDGETS: "DELETE_META_WIDGETS", @@ -760,6 +766,21 @@ export const ReduxActionTypes = { AUTOLAYOUT_REORDER_WIDGETS: "AUTOLAYOUT_REORDER_WIDGETS", AUTOLAYOUT_ADD_NEW_WIDGETS: "AUTOLAYOUT_ADD_NEW_WIDGETS", RECALCULATE_COLUMNS: "RECALCULATE_COLUMNS", + UPDATE_LAYOUT_POSITIONING: "UPDATE_LAYOUT_POSITIONING", + SET_LAYOUT_CONVERSION_STATE: "SET_LAYOUT_CONVERSION_STATE", + START_CONVERSION_FLOW: "START_CONVERSION_FLOW", + STOP_CONVERSION_FLOW: "STOP_CONVERSION_FLOW", + UPDATE_SNAPSHOT_DETAILS: "UPDATE_SNAPSHOT_DETAILS", + CONVERT_AUTO_TO_FIXED: "CONVERT_AUTO_TO_FIXED", + CONVERT_FIXED_TO_AUTO: "CONVERT_FIXED_TO_AUTO", + REFRESH_THE_APP: "REFRESH_THE_APP", + LOG_LAYOUT_CONVERSION_ERROR: "LOG_LAYOUT_CONVERSION_ERROR", + RESTORE_SNAPSHOT: "RESTORE_SNAPSHOT", + FETCH_SNAPSHOT: "FETCH_SNAPSHOT", + DELETE_SNAPSHOT: "DELETE_SNAPSHOT", + UPDATE_WIDGET_DIMENSIONS: "UPDATE_WIDGET_DIMENSIONS", + PROCESS_AUTO_LAYOUT_DIMENSION_UPDATES: + "PROCESS_AUTO_LAYOUT_DIMENSION_UPDATES", SET_GSHEET_TOKEN: "SET_GSHEET_TOKEN", FILE_PICKER_CALLBACK_ACTION: "FILE_PICKER_CALLBACK_ACTION", }; @@ -1016,6 +1037,7 @@ export interface UpdateCanvasPayload { currentPageId: string; currentPageName: string; currentApplicationId: string; + dsl: Partial; pageActions: PageAction[][]; updatedWidgetIds?: string[]; layoutOnLoadActionErrors?: LayoutOnLoadActionErrors[]; @@ -1061,6 +1083,9 @@ export interface ApplicationPayload { slug: string; forkingEnabled?: boolean; appLayout?: AppLayoutConfig; + applicationDetail?: { + appPositioning?: AppPositioningTypeConfig; + }; gitApplicationMetadata?: GitApplicationMetadata; lastDeployedAt?: string; applicationId?: string; diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 4de2b0b42b0d..53e592fb6946 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -424,6 +424,7 @@ export const NAVIGATE_TO = () => `Navigate to`; export const SHOW_MESSAGE = () => `Show message`; export const OPEN_MODAL = () => `Open modal`; export const CLOSE_MODAL = () => `Close modal`; +export const CLOSE = () => `CLOSE`; export const STORE_VALUE = () => `Store value`; export const REMOVE_VALUE = () => `Remove value`; export const CLEAR_STORE = () => `Clear store`; @@ -1466,6 +1467,68 @@ export const SAVE_BUTTON_TEXT = () => "SAVE"; export const SAVE_AND_AUTHORIZE_BUTTON_TEXT = () => "SAVE AND AUTHORIZE"; export const DISCARD_POPUP_DONT_SAVE_BUTTON_TEXT = () => "DON'T SAVE"; +//Layout Conversion flow +export const CONVERT = () => "CONVERT"; +export const BUILD_RESPONSIVE = () => "Build Responsive Apps"; +export const BUILD_RESPONSIVE_TEXT = () => + "Appsmith will convert your application's UI to auto layout, a new mode designed for building mobile-friendly apps in no time"; +export const BUILD_FIXED_LAYOUT = () => "Use Fixed Layout"; +export const BUILD_FIXED_LAYOUT_TEXT = () => + "Appsmith will convert your application’s UI to fixed layout, the default mode."; +export const USE_SNAPSHOT = () => "USE SNAPSHOT"; +export const USE_SNAPSHOT_HEADER = () => "Use Snapshot"; +export const DISCARD_SNAPSHOT_HEADER = () => "Discarding A Snapshot"; +export const SAVE_SNAPSHOT = () => "We Save a Snapshot of Your App"; +export const SAVE_SNAPSHOT_TEXT = () => + "We will create a snapshot of your whole app before the conversion, so that you can go back if auto layout is just not right for you"; +export const CREATE_SNAPSHOT = () => "Creating a snapshot"; +export const CONVERTING_APP = () => "Converting your app"; +export const RESTORING_SNAPSHOT = () => "Removing changes made"; +export const REFRESH_THE_APP = () => "REFRESH THE APP"; +export const CONVERT_ANYWAYS = () => "CONVERT ANYWAYS"; +export const CONVERSION_SUCCESS_HEADER = () => "All done"; +export const DISCARD_SNAPSHOT_TEXT = () => + "You are about to discard this snapshot:"; +export const CONVERSION_SUCCESS_TEXT = () => + "Check all your pages and start using your new layout"; +export const CONVERSION_WARNING_HEADER = () => + "All done, some adjustments needed"; +export const CONVERSION_WARNING_TEXT = () => + "You might need to manually position some of the widgets your layout contains"; +export const CONVERSION_ERROR_HEADER = () => "Conversion Failed"; +export const CONVERSION_ERROR = () => + "Appsmith ran into a critical error while trying to convert to auto layout"; +export const SEND_REPORT = () => "SEND US A REPORT"; +export const CONVERSION_ERROR_TEXT = () => "No changes were made to your app"; +export const DROPDOWN_LABEL_TEXT = () => "Target canvas size"; +export const CONVERSION_WARNING = () => "Conversion will change your layout"; +export const SNAPSHOT_LABEL = () => + "To revert back to the original state use this snapshot"; +export const USE_SNAPSHOT_TEXT = () => + "Your app will look and work exactly like it used to before the conversion. Widgets, datasources, queries, JS objects added and any changes you made after conversion will not be present."; +export const CONVERT_TO_FIXED_TITLE = () => "Convert to Fixed Layout"; +export const CONVERT_TO_FIXED_BUTTON = () => "CONVERT TO Fixed-LAYOUT"; +export const CONVERT_TO_AUTO_TITLE = () => "Convert to Auto Layout"; +export const CONVERT_TO_AUTO_BUTTON = () => "CONVERT TO AUTO-LAYOUT"; +export const SNAPSHOT_BANNER_MESSAGE = () => + "Confirm all the pages are converted correctly before editing. Use the snapshot to go back. Discard only after reviewing all the pages."; +export const USE_SNAPSHOT_CTA = () => "USE THE SNAPSHOT"; +export const DISCARD_SNAPSHOT_CTA = () => "DISCARD THE SNAPSHOT"; +export const MORE_DETAILS = () => "More details"; +export const CONVERSION_ERROR_MESSAGE_HEADER = () => + "To resolve this error please:"; +export const CONVERSION_ERROR_MESSAGE_TEXT_ONE = () => + "Check your internet connection."; +export const CONVERSION_ERROR_MESSAGE_TEXT_TWO = () => + "Send us a report. Sending a report will only inform us that the failure happened and will give us your email address to reach out to."; +export const SNAPSHOT_TIME_FROM_MESSAGE = ( + timeSince: string, + readableDate: string, +) => `Snapshot from ${timeSince} ago (${readableDate})`; +export const SNAPSHOT_TIME_TILL_EXPIRATION_MESSAGE = ( + timeTillExpiration: string, +) => `Snapshot expires in ${timeTillExpiration}`; +export const DISCARD = () => "DISCARD"; // Alert options and labels for showMessage types export const ALERT_STYLE_OPTIONS = [ { label: "Info", value: "'info'", id: "info" }, diff --git a/app/client/src/ce/pages/Applications/index.tsx b/app/client/src/ce/pages/Applications/index.tsx index cfd533b8e7d0..c1a6ce061da7 100644 --- a/app/client/src/ce/pages/Applications/index.tsx +++ b/app/client/src/ce/pages/Applications/index.tsx @@ -62,6 +62,7 @@ import { } from "design-system-old"; import { duplicateApplication, + setShowAppInviteUsersDialog, updateApplication, } from "actions/applicationActions"; import { Position } from "@blueprintjs/core/lib/esm/common/position"; @@ -649,6 +650,10 @@ export function ApplicationsSection(props: any) { }); }; + const handleFormOpenOrClose = useCallback((isOpen: boolean) => { + dispatch(setShowAppInviteUsersDialog(isOpen)); + }, []); + let updatedWorkspaces; if (!isFetchingApplications) { updatedWorkspaces = userWorkspaces; @@ -747,6 +752,7 @@ export function ApplicationsSection(props: any) { INVITE_USERS_MESSAGE, cloudHosting, )} + onOpenOrClose={handleFormOpenOrClose} placeholder={createMessage( INVITE_USERS_PLACEHOLDER, cloudHosting, diff --git a/app/client/src/ce/reducers/index.tsx b/app/client/src/ce/reducers/index.tsx index c0033e053461..5b72869d8d80 100644 --- a/app/client/src/ce/reducers/index.tsx +++ b/app/client/src/ce/reducers/index.tsx @@ -74,6 +74,7 @@ import type { AutoHeightUIState } from "reducers/uiReducers/autoHeightReducer"; import type { AnalyticsReduxState } from "reducers/uiReducers/analyticsReducer"; import type { MultiPaneReduxState } from "reducers/uiReducers/multiPaneReducer"; import type { MetaWidgetsReduxState } from "reducers/entityReducers/metaWidgetsReducer"; +import type { layoutConversionReduxState } from "reducers/uiReducers/layoutConversionReducer"; export const reducerObject = { entities: entityReducer, @@ -135,6 +136,7 @@ export interface AppState { libraries: LibraryState; autoHeightUI: AutoHeightUIState; multiPaneConfig: MultiPaneReduxState; + layoutConversion: layoutConversionReduxState; }; entities: { canvasWidgetsStructure: CanvasWidgetStructure; diff --git a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx index 70597fe3195d..639ee69715af 100644 --- a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx @@ -383,10 +383,17 @@ export const handlers = { if (action.payload.name) { isSavingAppName = true; } + if (state.currentApplication && action.payload.applicationDetail) { + state.currentApplication.applicationDetail = + action.payload.applicationDetail; + } return { ...state, isSavingAppName: isSavingAppName, isErrorSavingAppName: false, + ...(action.payload.applicationDetail + ? { applicationDetail: action.payload.applicationDetail } + : {}), }; }, [ReduxActionTypes.UPDATE_APPLICATION_SUCCESS]: ( diff --git a/app/client/src/ce/sagas/index.tsx b/app/client/src/ce/sagas/index.tsx index 8548dad43cbd..0c119b7dba8f 100644 --- a/app/client/src/ce/sagas/index.tsx +++ b/app/client/src/ce/sagas/index.tsx @@ -29,6 +29,7 @@ import initSagas from "sagas/InitSagas"; import { watchJSActionSagas } from "sagas/JSActionSagas"; import JSLibrarySaga from "sagas/JSLibrarySaga"; import jsPaneSagas from "sagas/JSPaneSagas"; +import layoutConversionSagas from "sagas/layoutConversionSagas"; import LintingSaga from "sagas/LintingSagas"; import modalSagas from "sagas/ModalSagas"; import onboardingSagas from "sagas/OnboardingSagas"; @@ -39,6 +40,7 @@ import providersSagas from "sagas/ProvidersSaga"; import queryPaneSagas from "sagas/QueryPaneSagas"; import replaySaga from "sagas/ReplaySaga"; import saaSPaneSagas from "sagas/SaaSPaneSagas"; +import snapshotSagas from "sagas/SnapshotSagas"; import snipingModeSagas from "sagas/SnipingModeSagas"; import templateSagas from "sagas/TemplatesSagas"; import themeSagas from "sagas/ThemeSaga"; @@ -96,4 +98,6 @@ export const sagas = [ LintingSaga, autoLayoutUpdateSagas, autoLayoutDraggingSagas, + layoutConversionSagas, + snapshotSagas, ]; diff --git a/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutDimensionObeserver.tsx b/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutDimensionObeserver.tsx new file mode 100644 index 000000000000..02d96827020e --- /dev/null +++ b/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutDimensionObeserver.tsx @@ -0,0 +1,79 @@ +import { FLEXBOX_PADDING } from "constants/WidgetConstants"; +import React, { useState } from "react"; +import { useEffect, useRef } from "react"; +import type { PropsWithChildren } from "react"; +import styled from "styled-components"; + +const SimpleContainer = styled.div` + width: fit-content; + &.fill-widget { + width: 100%; + } +`; + +interface AutoLayoutDimensionObserverProps { + onDimensionUpdate: (width: number, height: number) => void; + width: number; + height: number; + isFillWidget: boolean; +} + +export default function AutoLayoutDimensionObserver( + props: PropsWithChildren, +) { + const { onDimensionUpdate } = props; + const [currentDimension, setCurrentDimension] = useState({ + width: 0, + height: 0, + }); + + const ref = useRef(null); + + const observer = useRef( + new ResizeObserver((entries) => { + const width = entries[0].contentRect.width; + const height = entries[0].contentRect.height; + if (width === 0 || height === 0) return; + setCurrentDimension({ width, height }); + }), + ); + + useEffect(() => { + if (currentDimension.width === 0 || currentDimension.height === 0) return; + const widthDiff = Math.abs( + props.width - 2 * FLEXBOX_PADDING - currentDimension.width, + ); + const heightDiff = Math.abs( + props.height - 2 * FLEXBOX_PADDING - currentDimension.height, + ); + if (widthDiff >= 1 || heightDiff >= 1) { + onDimensionUpdate(currentDimension.width, currentDimension.height); + } + }, [ + props.width, + props.height, + currentDimension.width, + currentDimension.height, + ]); + + useEffect(() => { + if (ref.current) { + observer.current.observe(ref.current); + } + + return () => { + observer.current.disconnect(); + }; + }, []); + + return ( + + {props.children} + + ); +} diff --git a/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutLayer.tsx b/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutLayer.tsx index 588c9493773b..154cc187e746 100644 --- a/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutLayer.tsx +++ b/app/client/src/components/designSystems/appsmith/autoLayout/AutoLayoutLayer.tsx @@ -1,3 +1,4 @@ +import { GridDefaults } from "constants/WidgetConstants"; import type { ReactNode } from "react"; import React from "react"; import styled from "styled-components"; @@ -22,6 +23,9 @@ export interface AutoLayoutLayerProps { wrapCenter: boolean; wrapEnd: boolean; wrapLayer: boolean; + startColumns: number; + centerColumns: number; + endColumns: number; } const LayoutLayerContainer = styled.div<{ @@ -60,20 +64,43 @@ const CenterWrapper = styled(SubWrapper)` `; function AutoLayoutLayer(props: AutoLayoutLayerProps) { + const renderChildren = () => { + const { + center, + centerColumns, + end, + endColumns, + isMobile, + start, + startColumns, + } = props; + const arr: (JSX.Element | null)[] = [ + + {start} + , + + {center} + , + + {end} + , + ]; + const isFull = + startColumns + centerColumns + endColumns === + GridDefaults.DEFAULT_GRID_COLUMNS && !isMobile; + if (isFull) { + if (startColumns === 0) arr[0] = null; + if (centerColumns === 0) arr[1] = null; + if (endColumns === 0) arr[2] = null; + } + return arr.filter((item) => item !== null); + }; return ( - - {props.start} - - - {props.center} - - - {props.end} - + {renderChildren()} ); } diff --git a/app/client/src/components/designSystems/appsmith/autoLayout/FlexBoxComponent.tsx b/app/client/src/components/designSystems/appsmith/autoLayout/FlexBoxComponent.tsx index ceef6f34caeb..573273bcb5dc 100644 --- a/app/client/src/components/designSystems/appsmith/autoLayout/FlexBoxComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/autoLayout/FlexBoxComponent.tsx @@ -17,15 +17,16 @@ import type { FlexLayer, } from "utils/autoLayout/autoLayoutTypes"; import { getColumnsForAllLayers } from "selectors/autoLayoutSelectors"; +import { WidgetNameComponentHeight } from "components/editorComponents/WidgetNameComponent"; export interface FlexBoxProps { - direction?: LayoutDirection; + direction: LayoutDirection; stretchHeight: boolean; useAutoLayout: boolean; children?: ReactNode; widgetId: string; flexLayers: FlexLayer[]; - isMobile?: boolean; + isMobile: boolean; } export const DEFAULT_HIGHLIGHT_SIZE = 4; @@ -104,12 +105,15 @@ function FlexBoxComponent(props: FlexBoxProps) { return ( GridDefaults.DEFAULT_GRID_COLUMNS} wrapEnd={endColumns > GridDefaults.DEFAULT_GRID_COLUMNS} @@ -134,7 +138,7 @@ function FlexBoxComponent(props: FlexBoxProps) { height: props.stretchHeight ? "100%" : "auto", overflow: "hidden", padding: leaveSpaceForWidgetName - ? `${FLEXBOX_PADDING}px ${FLEXBOX_PADDING}px 22px ${FLEXBOX_PADDING}px` + ? `${FLEXBOX_PADDING}px ${FLEXBOX_PADDING}px ${WidgetNameComponentHeight}px ${FLEXBOX_PADDING}px` : "0px", }; }, [ diff --git a/app/client/src/components/designSystems/appsmith/autoLayout/FlexComponent.tsx b/app/client/src/components/designSystems/appsmith/autoLayout/FlexComponent.tsx index 27fcc3dc3128..bcf90292058c 100644 --- a/app/client/src/components/designSystems/appsmith/autoLayout/FlexComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/autoLayout/FlexComponent.tsx @@ -2,10 +2,13 @@ import type { CSSProperties, ReactNode } from "react"; import React, { useCallback, useMemo } from "react"; import styled from "styled-components"; -import type { WidgetType } from "constants/WidgetConstants"; +import type { RenderMode, WidgetType } from "constants/WidgetConstants"; import { WIDGET_PADDING } from "constants/WidgetConstants"; import { useSelector } from "react-redux"; -import { snipingModeSelector } from "selectors/editorSelectors"; +import { + previewModeSelector, + snipingModeSelector, +} from "selectors/editorSelectors"; import { getIsResizing } from "selectors/widgetSelectors"; import type { FlexVerticalAlignment, @@ -16,14 +19,14 @@ import { useClickToSelectWidget } from "utils/hooks/useClickToSelectWidget"; import { usePositionedContainerZIndex } from "utils/hooks/usePositionedContainerZIndex"; import { widgetTypeClassname } from "widgets/WidgetUtils"; import { checkIsDropTarget } from "utils/WidgetFactoryHelpers"; +import { RESIZE_BORDER_BUFFER } from "resizable/common"; export type AutoLayoutProps = { children: ReactNode; componentHeight: number; componentWidth: number; - direction?: LayoutDirection; + direction: LayoutDirection; focused?: boolean; - minWidth?: number; parentId?: string; responsiveBehavior?: ResponsiveBehavior; selected?: boolean; @@ -32,13 +35,20 @@ export type AutoLayoutProps = { widgetType: WidgetType; parentColumnSpace: number; flexVerticalAlignment: FlexVerticalAlignment; - isMobile?: boolean; + isMobile: boolean; + renderMode: RenderMode; }; const FlexWidget = styled.div` position: relative; `; +const WIDGETS_WITH_MARGIN = [ + "BUTTON_WIDGET", + "BUTTON_GROUP_WIDGET", + "CHECKBOX_WIDGET", +]; + export function FlexComponent(props: AutoLayoutProps) { const isSnipingMode = useSelector(snipingModeSelector); @@ -71,21 +81,37 @@ export function FlexComponent(props: AutoLayoutProps) { )} t--widget-${props.widgetName.toLowerCase()}`, [props.parentId, props.widgetId, props.widgetType, props.widgetName], ); + const isPreviewMode = useSelector(previewModeSelector); const isResizing = useSelector(getIsResizing); - + const widgetDimensionsViewCss = { + width: props.componentWidth - WIDGET_PADDING * 2, + height: props.componentHeight - WIDGET_PADDING * 2, + margin: WIDGET_PADDING + "px", + transform: `translate3d(${WIDGET_PADDING}px, ${WIDGET_PADDING}px, 0px)`, + }; + const widgetDimensionsEditCss = { + width: isResizing + ? "auto" + : `${props.componentWidth - WIDGET_PADDING * 2 + RESIZE_BORDER_BUFFER}px`, + height: isResizing + ? "auto" + : `${ + props.componentHeight - WIDGET_PADDING * 2 + RESIZE_BORDER_BUFFER + }px`, + margin: WIDGET_PADDING / 2 + "px", + marginBottom: WIDGETS_WITH_MARGIN.includes(props.widgetType) + ? "6px" + : "0px", + }; const flexComponentStyle: CSSProperties = useMemo(() => { return { display: "flex", zIndex, - width: isResizing - ? "auto" - : `${props.componentWidth - WIDGET_PADDING * 2}px`, - height: isResizing - ? "auto" - : props.componentHeight - WIDGET_PADDING * 2 + "px", + ...(props.renderMode === "PAGE" || isPreviewMode + ? widgetDimensionsViewCss + : widgetDimensionsEditCss), minHeight: "30px", - margin: WIDGET_PADDING + "px", alignSelf: props.flexVerticalAlignment, "&:hover": { zIndex: onHoverZIndex + " !important", diff --git a/app/client/src/components/editorComponents/EditorContextProvider.test.tsx b/app/client/src/components/editorComponents/EditorContextProvider.test.tsx index d22867fd9017..68491991facb 100644 --- a/app/client/src/components/editorComponents/EditorContextProvider.test.tsx +++ b/app/client/src/components/editorComponents/EditorContextProvider.test.tsx @@ -39,6 +39,7 @@ describe("EditorContextProvider", () => { "updateWidget", "updateWidgetProperty", "updateWidgetAutoHeight", + "updateWidgetDimension", "checkContainersForAutoHeight", ].sort(); @@ -71,6 +72,7 @@ describe("EditorContextProvider", () => { "syncUpdateWidgetMetaProperty", "triggerEvalOnMetaUpdate", "updateWidgetAutoHeight", + "updateWidgetDimension", "checkContainersForAutoHeight", ].sort(); diff --git a/app/client/src/components/editorComponents/EditorContextProvider.tsx b/app/client/src/components/editorComponents/EditorContextProvider.tsx index 59016934dbe5..9fab32781012 100644 --- a/app/client/src/components/editorComponents/EditorContextProvider.tsx +++ b/app/client/src/components/editorComponents/EditorContextProvider.tsx @@ -41,6 +41,7 @@ import { } from "actions/autoHeightActions"; import type { WidgetSelectionRequest } from "actions/widgetSelectionActions"; import { selectWidgetInitAction } from "actions/widgetSelectionActions"; +import { updateWidgetDimensionAction } from "actions/autoLayoutActions"; export type EditorContextType = { executeAction?: (triggerPayload: ExecuteTriggerPayload) => void; @@ -70,6 +71,11 @@ export type EditorContextType = { propertyValue: any, ) => void; updateWidgetAutoHeight?: (widgetId: string, height: number) => void; + updateWidgetDimension?: ( + widgetId: string, + width: number, + height: number, + ) => void; checkContainersForAutoHeight?: () => void; modifyMetaWidgets?: (modifications: ModifyMetaWidgetPayload) => void; setWidgetCache?: ( @@ -104,6 +110,7 @@ const COMMON_API_METHODS: EditorContextTypeKey[] = [ "syncUpdateWidgetMetaProperty", "triggerEvalOnMetaUpdate", "updateWidgetAutoHeight", + "updateWidgetDimension", "checkContainersForAutoHeight", "selectWidgetRequest", ]; @@ -197,6 +204,7 @@ const mapDispatchToProps = { batchUpdateWidgetProperty: batchUpdatePropertyAction, triggerEvalOnMetaUpdate: triggerEvalOnMetaUpdate, updateWidgetAutoHeight: updateWidgetAutoHeightAction, + updateWidgetDimension: updateWidgetDimensionAction, checkContainersForAutoHeight: checkContainersForAutoHeightAction, modifyMetaWidgets, updateMetaWidgetProperty, diff --git a/app/client/src/components/editorComponents/ResizableComponent.tsx b/app/client/src/components/editorComponents/ResizableComponent.tsx index a16352f827b5..7398cc2fe30e 100644 --- a/app/client/src/components/editorComponents/ResizableComponent.tsx +++ b/app/client/src/components/editorComponents/ResizableComponent.tsx @@ -2,13 +2,16 @@ import type { AppState } from "@appsmith/reducers"; import { batchUpdateMultipleWidgetProperties } from "actions/controlActions"; import { focusWidget } from "actions/widgetActions"; import { EditorContext } from "components/editorComponents/EditorContextProvider"; -import { GridDefaults } from "constants/WidgetConstants"; +import type { OccupiedSpace } from "constants/CanvasEditorConstants"; +import { DefaultDimensionMap, GridDefaults } from "constants/WidgetConstants"; import { get, omit } from "lodash"; import type { XYCord } from "pages/common/CanvasArenas/hooks/useRenderBlocksOnCanvas"; import React, { memo, useContext, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; -import Resizable from "resizable/resizenreflow"; +import { ReflowResizable as AutoLayoutResizable } from "resizable/autolayoutresize"; +import { ReflowResizable as FixedLayoutResizable } from "resizable/resizenreflow"; import { SelectionRequestType } from "sagas/WidgetSelectUtils"; +import { getIsAutoLayout } from "selectors/canvasSelectors"; import { previewModeSelector, snipingModeSelector, @@ -32,14 +35,14 @@ import { useWidgetDragResize, } from "utils/hooks/dragResizeHooks"; import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; -import { NonResizableWidgets } from "utils/layoutPropertiesUtils"; -import { getSnapColumns } from "utils/WidgetPropsUtils"; import type { WidgetProps, WidgetRowCols } from "widgets/BaseWidget"; import { WidgetOperations } from "widgets/BaseWidget"; +import { getSnapColumns } from "utils/WidgetPropsUtils"; import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils"; import { DropTargetContext } from "./DropTargetComponent"; import type { UIElementSize } from "./ResizableUtils"; -import { computeFinalRowCols, computeRowCols } from "./ResizableUtils"; +import { computeFinalRowCols } from "./ResizableUtils"; +import { computeFinalAutoLayoutRowCols } from "./ResizableUtils"; import { BottomHandleStyles, BottomLeftHandleStyles, @@ -62,7 +65,9 @@ export const ResizableComponent = memo(function ResizableComponent( // Fetch information from the context const { updateWidget } = useContext(EditorContext); const dispatch = useDispatch(); + const isAutoLayout = useSelector(getIsAutoLayout); + const Resizable = isAutoLayout ? AutoLayoutResizable : FixedLayoutResizable; const isSnipingMode = useSelector(snipingModeSelector); const isPreviewMode = useSelector(previewModeSelector); @@ -107,15 +112,7 @@ export const ResizableComponent = memo(function ResizableComponent( 2 * props.paddingOffset, }; // onResize handler - const getResizedPositions = ( - newDimensions: UIElementSize, - position: XYCord, - ) => { - const delta: UIElementSize = { - height: newDimensions.height - dimensions.height, - width: newDimensions.width - dimensions.width, - }; - const newRowCols: WidgetRowCols = computeRowCols(delta, position, props); + const getResizedPositions = (resizedPositions: OccupiedSpace) => { let canResizeVertically = true; let canResizeHorizontally = true; @@ -127,27 +124,21 @@ export const ResizableComponent = memo(function ResizableComponent( }; if ( - newRowCols && - (newRowCols.rightColumn > getSnapColumns() || - newRowCols.leftColumn < 0 || - newRowCols.rightColumn - newRowCols.leftColumn < 2) + resizedPositions && + (resizedPositions.right > getSnapColumns() || + resizedPositions.left < 0 || + resizedPositions.right - resizedPositions.left < 2) ) { canResizeHorizontally = false; } if ( - newRowCols && - (newRowCols.topRow < 0 || newRowCols.bottomRow - newRowCols.topRow < 4) + resizedPositions && + (resizedPositions.top < 0 || + resizedPositions.bottom - resizedPositions.top < 4) ) { canResizeVertically = false; } - const resizedPositions = { - id: props.widgetId, - left: newRowCols.leftColumn, - top: newRowCols.topRow, - bottom: newRowCols.bottomRow, - right: newRowCols.rightColumn, - }; if (isAutoHeightEnabledForWidget(props)) { canResizeVertically = false; @@ -159,7 +150,6 @@ export const ResizableComponent = memo(function ResizableComponent( return { canResizeHorizontally, canResizeVertically, - resizedPositions, }; }; @@ -168,26 +158,60 @@ export const ResizableComponent = memo(function ResizableComponent( // 1) There is no collision // 2) There is a change in widget size // Update widget, if both of the above are true. - const updateSize = (newDimensions: UIElementSize, position: XYCord) => { + const updateSize = ( + newDimensions: UIElementSize, + position: XYCord, + dimensionMap = DefaultDimensionMap, + ) => { // Get the difference in size of the widget, before and after resizing. const delta: UIElementSize = { height: newDimensions.height - dimensions.height, width: newDimensions.width - dimensions.width, }; + const { + bottomRow: bottomRowMap, + leftColumn: leftColumnMap, + rightColumn: rightColumnMap, + topRow: topRowMap, + } = dimensionMap; + const { + parentColumnSpace, + parentRowSpace, + [bottomRowMap]: bottomRow, + [leftColumnMap]: leftColumn, + [rightColumnMap]: rightColumn, + [topRowMap]: topRow, + } = props as any; + // Get the updated Widget rows and columns props // False, if there is collision // False, if none of the rows and cols have changed. - const newRowCols: WidgetRowCols | false = computeFinalRowCols( - delta, - position, - props, - ); + const newRowCols: WidgetRowCols | false = isAutoLayout + ? computeFinalAutoLayoutRowCols(delta, position, { + bottomRow, + topRow, + leftColumn, + rightColumn, + parentColumnSpace, + parentRowSpace, + }) + : computeFinalRowCols(delta, position, { + bottomRow, + topRow, + leftColumn, + rightColumn, + parentColumnSpace, + parentRowSpace, + }); if (newRowCols) { updateWidget && updateWidget(WidgetOperations.RESIZE, props.widgetId, { - ...newRowCols, + [leftColumnMap]: newRowCols.leftColumn, + [rightColumnMap]: newRowCols.rightColumn, + [topRowMap]: newRowCols.topRow, + [bottomRowMap]: newRowCols.bottomRow, parentId: props.parentId, snapColumnSpace: props.parentColumnSpace, snapRowSpace: props.parentRowSpace, @@ -271,8 +295,12 @@ export const ResizableComponent = memo(function ResizableComponent( const handlesToOmit = get(props, "disabledResizeHandles", []); return omit(allHandles, handlesToOmit); }, [props]); + const isAutoCanvasResizing = useSelector( + (state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing, + ); const isEnabled = + !isAutoCanvasResizing && !isDragging && isWidgetFocused && !props.resizeDisabled && @@ -317,12 +345,14 @@ export const ResizableComponent = memo(function ResizableComponent( !isAutoHeightEnabledForWidget(props) || !props.isCanvas; - const allowResize: boolean = - !(NonResizableWidgets.includes(props.type) || isMultiSelected) || - !props.isFlexChild; + const allowResize: boolean = !isMultiSelected || !props.isFlexChild; + const isHovered = isFocused && !isSelected; const showResizeBoundary = - !isPreviewMode && !isDragging && (isHovered || isSelected); + !isAutoCanvasResizing && + !isPreviewMode && + !isDragging && + (isHovered || isSelected); return ( { + return { + leftColumn: Math.round( + props.leftColumn + position.x / props.parentColumnSpace, + ), + topRow: Math.round(props.topRow + position.y / props.parentRowSpace), + rightColumn: Math.round( + props.rightColumn + delta.width / props.parentColumnSpace, + ), + bottomRow: Math.round( + props.bottomRow + delta.height / props.parentRowSpace, + ), + }; +}; + export const computeRowCols = ( delta: UIElementSize, position: XYCord, @@ -54,7 +73,7 @@ export const computeBoundedRowCols = (rowCols: WidgetRowCols) => { export const hasRowColsChanged = ( newRowCols: WidgetRowCols, - props: WidgetProps, + props: WidgetPosition, ) => { return ( props.leftColumn !== newRowCols.leftColumn || @@ -67,13 +86,29 @@ export const hasRowColsChanged = ( export const computeFinalRowCols = ( delta: UIElementSize, position: XYCord, - props: WidgetProps, + widgetPositionProps: WidgetPosition, ): WidgetRowCols | false => { const newRowCols = computeBoundedRowCols( - computeRowCols(delta, position, props), + computeRowCols(delta, position, widgetPositionProps), ); - return hasRowColsChanged(newRowCols, props) ? newRowCols : false; + return hasRowColsChanged(newRowCols, widgetPositionProps) + ? newRowCols + : false; +}; + +export const computeFinalAutoLayoutRowCols = ( + delta: UIElementSize, + position: XYCord, + widgetPositionProps: WidgetPosition, +): WidgetRowCols | false => { + const newRowCols = computeBoundedRowCols( + computeAutoLayoutRowCols(delta, position, widgetPositionProps), + ); + + return hasRowColsChanged(newRowCols, widgetPositionProps) + ? newRowCols + : false; }; /** @@ -90,8 +125,6 @@ export function isHandleResizeAllowed( horizontalEnabled: boolean, verticalEnabled: boolean, direction?: ReflowDirection, - isFlexChild?: boolean, - responsiveBehavior?: ResponsiveBehavior, ): boolean { if (direction === ReflowDirection.TOP || direction === ReflowDirection.BOTTOM) return verticalEnabled; @@ -99,9 +132,38 @@ export function isHandleResizeAllowed( direction === ReflowDirection.LEFT || direction === ReflowDirection.RIGHT ) { - return isFlexChild && responsiveBehavior === ResponsiveBehavior.Fill - ? false - : horizontalEnabled; + return horizontalEnabled; } return true; } + +export function isResizingDisabled( + handles: { horizontal?: boolean; vertical?: boolean } = {}, + direction?: ReflowDirection, + isFlexChild?: boolean, + responsiveBehavior?: ResponsiveBehavior, +) { + const { horizontal = false, vertical = false } = handles; + + if ( + (direction === ReflowDirection.TOP || + direction === ReflowDirection.BOTTOM || + direction === ReflowDirection.BOTTOMLEFT || + direction === ReflowDirection.BOTTOMRIGHT) && + vertical + ) + return true; + + if ( + direction === ReflowDirection.RIGHT || + direction === ReflowDirection.LEFT + ) { + if ( + horizontal || + (isFlexChild && responsiveBehavior === ResponsiveBehavior.Fill) + ) + return true; + } + + return false; +} diff --git a/app/client/src/components/editorComponents/ResizeStyledComponents.tsx b/app/client/src/components/editorComponents/ResizeStyledComponents.tsx index ceb3361323bd..670283c16e5e 100644 --- a/app/client/src/components/editorComponents/ResizeStyledComponents.tsx +++ b/app/client/src/components/editorComponents/ResizeStyledComponents.tsx @@ -1,4 +1,5 @@ -import { invisible, theme } from "constants/DefaultTheme"; +import { Colors } from "constants/Colors"; +import { invisible } from "constants/DefaultTheme"; import { WIDGET_PADDING } from "constants/WidgetConstants"; import styled, { css } from "styled-components"; @@ -20,21 +21,49 @@ export const VisibilityContainer = styled.div<{ `} `; -const ResizeIndicatorStyle = css<{ +const VerticalResizeIndicators = css<{ showLightBorder: boolean; + isHovered: boolean; }>` &::after { position: absolute; content: ""; - width: 6px; - height: 6px; - border-radius: 50%; - background: ${(props) => - props.showLightBorder - ? theme.colors.widgetLightBorder - : theme.colors.widgetBorder}; - top: calc(50% - 2px); - left: calc(50% - 2px); + width: 7px; + height: 16px; + border-radius: 50%/16%; + background: ${Colors.GREY_1}; + top: calc(50% - 8px); + left: calc(50% - 2.5px); + border: ${(props) => { + return `1px solid ${props.isHovered ? Colors.WATUSI : "#F86A2B"}`; + }}; + outline: 1px solid ${Colors.GREY_1}; + } + &:hover::after { + background: #f86a2b; + } +`; + +const HorizontalResizeIndicators = css<{ + showLightBorder: boolean; + isHovered: boolean; +}>` + &::after { + position: absolute; + content: ""; + width: 16px; + height: 7px; + border-radius: 16%/50%; + border: ${(props) => { + return `1px solid ${props.isHovered ? Colors.WATUSI : "#F86A2B"}`; + }}; + background: ${Colors.GREY_1}; + top: calc(50% - 2.5px); + left: calc(50% - 8px); + outline: 1px solid ${Colors.GREY_1}; + } + &:hover::after { + background: #f86a2b; } `; @@ -42,29 +71,27 @@ export const EdgeHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; disableDot: boolean; + isHovered: boolean; }>` position: absolute; width: ${EDGE_RESIZE_HANDLE_WIDTH}px; height: ${EDGE_RESIZE_HANDLE_WIDTH}px; &::before { position: absolute; - background: ${(props) => { - if (props.showLightBorder) return theme.colors.widgetLightBorder; - if (props.showAsBorder) return theme.colors.widgetMultiSelectBorder; - return theme.colors.widgetBorder; - }}; + background: "transparent"; content: ""; } - ${(props) => - props.showAsBorder || props.disableDot ? "" : ResizeIndicatorStyle} `; export const VerticalHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; disableDot: boolean; + isHovered: boolean; }>` ${EdgeHandleStyles} + ${(props) => + props.showAsBorder || props.disableDot ? "" : VerticalResizeIndicators} top:${~(WIDGET_PADDING - 1) + 1}px; height: calc(100% + ${2 * WIDGET_PADDING - 1}px); ${(props) => @@ -81,8 +108,11 @@ export const HorizontalHandleStyles = css<{ showAsBorder: boolean; showLightBorder: boolean; disableDot: boolean; + isHovered: boolean; }>` ${EdgeHandleStyles} + ${(props) => + props.showAsBorder || props.disableDot ? "" : HorizontalResizeIndicators} left: ${~WIDGET_PADDING + 1}px; width: calc(100% + ${2 * WIDGET_PADDING}px); ${(props) => @@ -97,23 +127,23 @@ export const HorizontalHandleStyles = css<{ export const LeftHandleStyles = styled.div` ${VerticalHandleStyles} - left: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING}px; + left: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING + 1.5}px; `; export const RightHandleStyles = styled.div` ${VerticalHandleStyles}; - right: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING + 1}px; + right: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING + 3.5}px; height: calc(100% + ${2 * WIDGET_PADDING}px); `; export const TopHandleStyles = styled.div` ${HorizontalHandleStyles}; - top: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING}px; + top: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING + 1.5}px; `; export const BottomHandleStyles = styled.div` ${HorizontalHandleStyles}; - bottom: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING}px; + bottom: ${-EDGE_RESIZE_HANDLE_WIDTH / 2 - WIDGET_PADDING + 3.5}px; `; export const CornerHandleStyles = css` diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx index f1c800910ab9..b54a580a7542 100644 --- a/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx +++ b/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx @@ -21,10 +21,13 @@ const StyledTooltip = styled(Tooltip)<{ height: 100%; } `; -const SettingsWrapper = styled.div` +const WidgetNameBoundary = 1; +const BORDER_RADIUS = 4; +const SettingsWrapper = styled.div<{ widgetWidth: number; inverted: boolean }>` justify-self: flex-end; height: 100%; - padding: 0 10px; + padding: 0 5px; + margin-left: 0px; display: flex; justify-content: space-between; align-items: center; @@ -36,12 +39,24 @@ const SettingsWrapper = styled.div` line-height: ${(props) => props.theme.fontSizes[3] - 1}px; } } - border-radius: 2px; + border: ${WidgetNameBoundary}px solid ${Colors.GREY_1}; + ${(props) => { + if (props.inverted) { + return `border-bottom-left-radius: ${BORDER_RADIUS}px; + border-bottom-right-radius: ${BORDER_RADIUS}px; + border-top: none;`; + } else { + return `border-top-left-radius: ${BORDER_RADIUS}px; + border-top-right-radius: ${BORDER_RADIUS}px; + border-bottom: none;`; + } + }} `; const WidgetName = styled.span` - margin-right: ${(props) => props.theme.spaces[1] + 1}px; - margin-left: ${(props) => props.theme.spaces[3]}px; + width: inherit; + overflow-x: hidden; + text-overflow: ellipsis; white-space: nowrap; `; @@ -61,10 +76,11 @@ type SettingsControlProps = { activity: Activities; name: string; errorCount: number; + inverted: boolean; + widgetWidth: number; }; const BindDataIcon = ControlIcons.BIND_DATA_CONTROL; -const SettingsIcon = ControlIcons.SETTINGS_CONTROL; const getStyles = ( activity: Activities, @@ -92,7 +108,7 @@ const getStyles = ( case Activities.HOVERING: return { background: Colors.WATUSI, - color: Colors.BLACK_PEARL, + color: Colors.WHITE, }; case Activities.SELECTED: return { @@ -104,19 +120,6 @@ const getStyles = ( export function SettingsControl(props: SettingsControlProps) { const isSnipingMode = useSelector(snipingModeSelector); - const settingsIcon = ( - - ); const errorIcon = ( {!!props.errorCount && !isSnipingMode && ( <> @@ -153,10 +158,9 @@ export function SettingsControl(props: SettingsControlProps) { {isSnipingMode ? `Bind to ${props.name}` : props.name} - {!isSnipingMode && settingsIcon} ); } -export default SettingsControl; +export default React.memo(SettingsControl); diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx index df6134fca89d..d96f58e1ff35 100644 --- a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx +++ b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx @@ -6,6 +6,7 @@ import { useDispatch, useSelector } from "react-redux"; import { SelectionRequestType } from "sagas/WidgetSelectUtils"; import { hideErrors } from "selectors/debuggerSelectors"; import { + getCurrentAppPositioningType, previewModeSelector, snipingModeSelector, } from "selectors/editorSelectors"; @@ -13,25 +14,37 @@ import { getIsPropertyPaneVisible } from "selectors/propertyPaneSelectors"; import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors"; import styled from "styled-components"; import AnalyticsUtil from "utils/AnalyticsUtil"; -import { useShowTableFilterPane } from "utils/hooks/dragResizeHooks"; -import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; import WidgetFactory from "utils/WidgetFactory"; +import { useShowTableFilterPane } from "utils/hooks/dragResizeHooks"; +import { useWidgetSelection } from "utils/hooks/useWidgetSelection"; import SettingsControl, { Activities } from "./SettingsControl"; +import { theme } from "constants/DefaultTheme"; +import { isCurrentWidgetFocused } from "selectors/widgetSelectors"; +import { AppPositioningTypes } from "reducers/entityReducers/pageListReducer"; +import { RESIZE_BORDER_BUFFER } from "resizable/common"; +import { Layers } from "constants/Layers"; const WidgetTypes = WidgetFactory.widgetTypes; +export const WidgetNameComponentHeight = theme.spaces[10]; -const PositionStyle = styled.div<{ topRow: number; isSnipingMode: boolean }>` +const PositionStyle = styled.div<{ + positionOffset: [number, number]; + topRow: number; + isSnipingMode: boolean; +}>` position: absolute; top: ${(props) => - props.topRow > 2 ? `${-1 * props.theme.spaces[10]}px` : "calc(100%)"}; - height: ${(props) => props.theme.spaces[10]}px; - ${(props) => (props.isSnipingMode ? "left: -7px" : "right: 0")}; + props.topRow > 2 + ? `${-1 * WidgetNameComponentHeight + 1 + props.positionOffset[0]}px` + : `calc(100% - ${1 + props.positionOffset[0]}px)`}; + height: ${WidgetNameComponentHeight}px; + ${(props) => `margin-left: ${props.positionOffset[1]}px`}; display: flex; - padding: 0 4px; cursor: pointer; + z-index: ${Layers.widgetName}; `; const ControlGroup = styled.div` @@ -54,6 +67,7 @@ type WidgetNameComponentProps = { showControls?: boolean; topRow: number; errorCount: number; + widgetWidth: number; }; export function WidgetNameComponent(props: WidgetNameComponentProps) { @@ -61,6 +75,11 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { const isSnipingMode = useSelector(snipingModeSelector); const isPreviewMode = useSelector(previewModeSelector); const showTableFilterPane = useShowTableFilterPane(); + const isAutoCanvasResizing = useSelector( + (state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing, + ); + const appPositioningType = useSelector(getCurrentAppPositioningType); + const isAutoLayout = appPositioningType === AppPositioningTypes.AUTO; // Dispatch hook handy to set a widget as focused/selected const { selectWidget } = useWidgetSelection(); const isPropPaneVisible = useSelector(getIsPropertyPaneVisible); @@ -70,10 +89,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { const selectedWidgets = useSelector( (state: AppState) => state.ui.widgetDragResize.selectedWidgets, ); - const focusedWidget = useSelector( - (state: AppState) => state.ui.widgetDragResize.focusedWidget, - ); - + const isFocused = useSelector(isCurrentWidgetFocused(props.widgetId)); const isResizing = useSelector( (state: AppState) => state.ui.widgetDragResize.isResizing, ); @@ -130,16 +146,18 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { selectedWidgets && selectedWidgets.length > 1 && selectedWidgets.includes(props.widgetId); + // True when any widget is dragging or resizing, including this one + const isResizingOrDragging = !!isResizing || !!isDragging; const shouldShowWidgetName = () => { return ( + !isAutoCanvasResizing && + !isResizingOrDragging && !isPreviewMode && !isMultiSelectedWidget && (isSnipingMode - ? focusedWidget === props.widgetId + ? isFocused : props.showControls || - ((focusedWidget === props.widgetId || showAsSelected) && - !isDragging && - !isResizing)) + ((isFocused || showAsSelected) && !isDragging && !isResizing)) ); }; @@ -152,7 +170,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { props.type === WidgetTypes.MODAL_WIDGET ? Activities.HOVERING : Activities.NONE; - if (focusedWidget === props.widgetId) currentActivity = Activities.HOVERING; + if (isFocused) currentActivity = Activities.HOVERING; if (showAsSelected) currentActivity = Activities.SELECTED; if ( showAsSelected && @@ -161,19 +179,27 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { ) currentActivity = Activities.ACTIVE; + // bottom offset is RESIZE_BORDER_BUFFER - 1 because bottom border is none for the widget name + const positionOffset: [number, number] = isAutoLayout + ? [-RESIZE_BORDER_BUFFER / 2, -RESIZE_BORDER_BUFFER / 2] + : [0, -RESIZE_BORDER_BUFFER]; return showWidgetName ? ( diff --git a/app/client/src/components/editorComponents/form/FormDialogComponent.tsx b/app/client/src/components/editorComponents/form/FormDialogComponent.tsx index 470efbc19cd0..0efebad31d97 100644 --- a/app/client/src/components/editorComponents/form/FormDialogComponent.tsx +++ b/app/client/src/components/editorComponents/form/FormDialogComponent.tsx @@ -1,8 +1,6 @@ import type { ReactNode } from "react"; import React, { useState, useEffect } from "react"; import { isPermitted } from "@appsmith/utils/permissionHelpers"; -import { useDispatch } from "react-redux"; -import { setShowAppInviteUsersDialog } from "actions/applicationActions"; import type { TabProp, IconName } from "design-system-old"; import { DialogComponent as Dialog, @@ -41,6 +39,8 @@ const TabCloseBtnContainer = styled.div` type FormDialogComponentProps = { isOpen?: boolean; canOutsideClickClose?: boolean; + canEscapeKeyClose?: boolean; + isCloseButtonShown?: boolean; noModalBodyMarginTop?: boolean; workspaceId?: string; title?: string; @@ -48,6 +48,7 @@ type FormDialogComponentProps = { Form: any; trigger: ReactNode; onClose?: () => void; + onOpenOrClose?: (isOpen: boolean) => void; customProps?: any; permissionRequired?: string; permissions?: string[]; @@ -63,6 +64,7 @@ type FormDialogComponentProps = { tabs?: any[]; options?: any[]; placeholder?: string; + getHeader?: () => ReactNode; }; const getTabs = ( @@ -107,7 +109,6 @@ const getTabs = ( export function FormDialogComponent(props: FormDialogComponentProps) { const [isOpen, setIsOpenState] = useState(!!props.isOpen); const [selectedTabIndex, setSelectedTabIndex] = useState(0); - const dispatch = useDispatch(); useEffect(() => { setIsOpen(!!props.isOpen); @@ -115,7 +116,7 @@ export function FormDialogComponent(props: FormDialogComponentProps) { const setIsOpen = (isOpen: boolean) => { setIsOpenState(isOpen); - dispatch(setShowAppInviteUsersDialog(isOpen)); + props.onOpenOrClose && props.onOpenOrClose(isOpen); }; const onCloseHandler = () => { @@ -139,8 +140,11 @@ export function FormDialogComponent(props: FormDialogComponentProps) { return ( { + dispatch(setShowAppInviteUsersDialog(isOpen)); + }, []); + if (hideHeader) return ; return ( @@ -139,6 +146,7 @@ export function AppViewerHeader(props: AppViewerHeaderProps) { }} isOpen={showAppInviteUsersDialog} message={createMessage(INVITE_USERS_MESSAGE, cloudHosting)} + onOpenOrClose={handleFormOpenOrClose} placeholder={createMessage( INVITE_USERS_PLACEHOLDER, cloudHosting, diff --git a/app/client/src/pages/AppViewer/PageMenu.tsx b/app/client/src/pages/AppViewer/PageMenu.tsx index cfa04aecdbff..3649cfe7b7a3 100644 --- a/app/client/src/pages/AppViewer/PageMenu.tsx +++ b/app/client/src/pages/AppViewer/PageMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; import type { ApplicationPayload, Page, @@ -8,7 +8,7 @@ import { getAppMode, showAppInviteUsersDialogSelector, } from "selectors/applicationSelectors"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import classNames from "classnames"; import PrimaryCTA from "./PrimaryCTA"; import Button from "./AppViewerButton"; @@ -29,6 +29,7 @@ import { INVITE_USERS_PLACEHOLDER, } from "@appsmith/constants/messages"; import { getAppsmithConfigs } from "@appsmith/configs"; +import { setShowAppInviteUsersDialog } from "actions/applicationActions"; const { cloudHosting } = getAppsmithConfigs(); @@ -53,6 +54,8 @@ export function PageMenu(props: AppViewerHeaderProps) { const [query, setQuery] = useState(""); const { hideWatermark } = getAppsmithConfigs(); + const dispatch = useDispatch(); + // hide menu on click outside useOnClickOutside( [menuRef, headerRef as React.RefObject], @@ -78,6 +81,10 @@ export function PageMenu(props: AppViewerHeaderProps) { }); } + const handleFormOpenOrClose = useCallback((isOpen: boolean) => { + dispatch(setShowAppInviteUsersDialog(isOpen)); + }, []); + return ( <> {/* BG OVERLAY */} @@ -121,6 +128,7 @@ export function PageMenu(props: AppViewerHeaderProps) { }} isOpen={showAppInviteUsersDialog} message={createMessage(INVITE_USERS_MESSAGE, cloudHosting)} + onOpenOrClose={handleFormOpenOrClose} placeholder={createMessage( INVITE_USERS_PLACEHOLDER, cloudHosting, diff --git a/app/client/src/pages/Editor/AppPositionTypeControl.tsx b/app/client/src/pages/Editor/AppPositionTypeControl.tsx deleted file mode 100644 index eea58ef3d54e..000000000000 --- a/app/client/src/pages/Editor/AppPositionTypeControl.tsx +++ /dev/null @@ -1,175 +0,0 @@ -// import classNames from "classnames"; -import React from "react"; -import { useSelector } from "react-redux"; -import styled from "styled-components"; - -// import { batchUpdateMultipleWidgetProperties } from "actions/controlActions"; -// import { ReactComponent as DesktopIcon } from "assets/icons/ads/app-icons/monitor-alt.svg"; -// import { ReactComponent as MultiDeviceIcon } from "assets/icons/ads/app-icons/monitor-smartphone-alt.svg"; -import { Colors } from "constants/Colors"; -// import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; -// import { IconName } from "design-system-old"; -import type { AppPositioningTypeConfig } from "reducers/entityReducers/pageListReducer"; -import { AppPositioningTypes } from "reducers/entityReducers/pageListReducer"; -import { - getCurrentAppPositioningType, - // isAutoLayoutEnabled, -} from "selectors/editorSelectors"; -// import { LayoutDirection, Positioning } from "utils/autoLayout/constants"; -import { MainContainerLayoutControl } from "./MainContainerLayoutControl"; -// interface ApplicationPositionTypeConfigOption { -// name: string; -// type: AppPositioningTypes; -// icon?: IconName; -// } -// const IconObj: any = { -// fluid: , -// desktop: , -// }; -export const AppsmithDefaultPositionType: AppPositioningTypeConfig = { - type: AppPositioningTypes.FIXED, -}; - -// const AppsmithLayoutTypes: ApplicationPositionTypeConfigOption[] = [ -// { -// name: "Fixed Layout", -// type: AppPositioningTypes.FIXED, -// icon: "desktop", -// }, -// { -// name: "Auto Layout", -// type: AppPositioningTypes.AUTO, -// icon: "fluid", -// }, -// ]; - -export const Title = styled.p` - color: ${Colors.GRAY_800}; -`; - -export function AppPositionTypeControl() { - // const dispatch = useDispatch(); - // const buttonRefs: Array = []; - const selectedOption = useSelector(getCurrentAppPositioningType); - // const isAutoLayoutActive = useSelector(isAutoLayoutEnabled); - /** - * return selected layout index. if there is no app - * layout, use the default one ( fluid ) - */ - // const selectedIndex = useMemo(() => { - // return AppsmithLayoutTypes.findIndex( - // (each) => - // each.type === (selectedOption || AppsmithDefaultPositionType.type), - // ); - // }, [selectedOption]); - - // const [focusedIndex, setFocusedIndex] = React.useState(selectedIndex); - - // useEffect(() => { - // if (!isAutoLayoutActive) { - // /** - // * if feature flag is disabled, set the layout to fixed. - // */ - // updateAppPositioningLayout(AppsmithLayoutTypes[0]); - // } - // }, [isAutoLayoutActive]); - - // const updateAppPositioningLayout = ( - // layoutOption: ApplicationPositionTypeConfigOption, - // ) => { - // const selectedType = - // layoutOption.type !== AppPositioningTypes.AUTO - // ? Positioning.Fixed - // : Positioning.Vertical; - // dispatch( - // batchUpdateMultipleWidgetProperties([ - // { - // widgetId: MAIN_CONTAINER_WIDGET_ID, - // updates: { - // modify: { - // positioning: selectedType, - // useAutoLayout: selectedType !== Positioning.Fixed, - // direction: LayoutDirection.Vertical, - // }, - // }, - // }, - // ]), - // ); - // }; - - // const handleKeyDown = (event: React.KeyboardEvent, index: number) => { - // if (!buttonRefs.length) return; - - // switch (event.key) { - // case "ArrowRight": - // case "Right": - // const rightIndex = index === buttonRefs.length - 1 ? 0 : index + 1; - // buttonRefs[rightIndex]?.focus(); - // setFocusedIndex(rightIndex); - // break; - // case "ArrowLeft": - // case "Left": - // const leftIndex = index === 0 ? buttonRefs.length - 1 : index - 1; - // buttonRefs[leftIndex]?.focus(); - // setFocusedIndex(leftIndex); - // break; - // } - // }; - - return ( - <> - {/* {isAutoLayoutActive ? ( - <> - App Positioning Type -
-
setFocusedIndex(selectedIndex)} - > - {AppsmithLayoutTypes.map((layoutOption: any, index: number) => { - return ( - - - - ); - })} -
-
- - ) : null} */} - {selectedOption === AppPositioningTypes.FIXED && ( - <> - Canvas Size - - - )} - - ); -} diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx index 6e428b27cbbf..1466a4d96965 100644 --- a/app/client/src/pages/Editor/Canvas.tsx +++ b/app/client/src/pages/Editor/Canvas.tsx @@ -10,21 +10,24 @@ import { useSelector } from "react-redux"; import { getSelectedAppTheme } from "selectors/appThemingSelectors"; import { previewModeSelector } from "selectors/editorSelectors"; import useWidgetFocus from "utils/hooks/useWidgetFocus"; +import { getViewportClassName } from "utils/autoLayout/AutoLayoutUtils"; interface CanvasProps { widgetsStructure: CanvasWidgetStructure; pageId: string; canvasWidth: number; + isAutoLayout?: boolean; } const Container = styled.section<{ background: string; width: number; + $isAutoLayout: boolean; }>` background: ${({ background }) => background}; - width: ${(props) => props.width}px; + width: ${({ $isAutoLayout, width }) => + $isAutoLayout ? `100%` : `${width}px`}; `; - const Canvas = (props: CanvasProps) => { const { canvasWidth } = props; const isPreviewMode = useSelector(previewModeSelector); @@ -43,11 +46,16 @@ const Canvas = (props: CanvasProps) => { const focusRef = useWidgetFocus(); + const marginHorizontalClass = props.isAutoLayout ? `mx-0` : `mx-auto`; + try { return ( { + if (isOpen) { + dispatch(setConversionStart(CONVERSION_STATES.START)); + } else { + dispatch(setConversionStop()); + } + }, []); + + const header = () => { + return ( +
+ {createMessage(titleText)} + +
+ ); + }; + + return ( + (useConversionForm, { + isAutoLayout, + })} + canEscapeKeyClose={false} + canOutsideClickClose={false} + getHeader={header} + isCloseButtonShown={false} + onOpenOrClose={onOpenOrClose} + trigger={ +