From ddc2304e777664fa16d1391cb22bbfaf86bb13be Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 24 Jul 2024 12:10:09 +0300 Subject: [PATCH] fix: revert "fix: canvas resizing issues" (#35129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This reverts commit cbe1f5821d344c7f422afbc28a4cd0c5f44616a8. ## Automation /ok-to-test tags="@tag.All" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 9ab856d46e621ea2073decfb5eb96378a83bd2f7 > Cypress dashboard. > Tags: `@tag.All` > Spec: >
Wed, 24 Jul 2024 09:06:01 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No --- .../MainContainerResizer.tsx | 2 +- .../common/mainContainerResizer/constants.ts | 1 - .../{AppPage => }/AppPage.styled.tsx | 0 .../pages/AppViewer/{AppPage => }/AppPage.tsx | 55 +--- .../src/pages/AppViewer/AppPage/constants.ts | 1 - .../src/pages/AppViewer/AppPage/index.ts | 2 - .../src/pages/Editor/IDE/AppsmithIDE.test.tsx | 4 +- .../MainContainerWrapper.tsx | 17 +- .../MainContainerWrapper/hooks/constants.ts | 1 - .../MainContainerWrapper/hooks/index.ts | 1 - .../hooks/useCanvasWidthAutoResize.ts | 60 ---- .../components/MainContainerWrapper/index.ts | 2 - .../MainContainerWrapper/utils/index.ts | 1 - .../utils/resolveCanvasWidth.test.ts | 92 ------ .../utils/resolveCanvasWidth.ts | 26 -- .../NavigationAdjustedPageViewer.tsx | 2 +- .../src/utils/hooks/useDynamicAppLayout.tsx | 292 ++++++++++++++++++ .../ContainerWidget/widget/index.test.tsx | 5 +- 18 files changed, 319 insertions(+), 245 deletions(-) delete mode 100644 app/client/src/layoutSystems/common/mainContainerResizer/constants.ts rename app/client/src/pages/AppViewer/{AppPage => }/AppPage.styled.tsx (100%) rename app/client/src/pages/AppViewer/{AppPage => }/AppPage.tsx (51%) delete mode 100644 app/client/src/pages/AppViewer/AppPage/constants.ts delete mode 100644 app/client/src/pages/AppViewer/AppPage/index.ts rename app/client/src/pages/Editor/WidgetsEditor/components/{MainContainerWrapper => }/MainContainerWrapper.tsx (94%) delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/constants.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/index.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/useCanvasWidthAutoResize.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/index.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/index.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.test.ts delete mode 100644 app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.ts create mode 100644 app/client/src/utils/hooks/useDynamicAppLayout.tsx diff --git a/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx b/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx index 9a5af0a7f32b..a929e631583b 100644 --- a/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx +++ b/app/client/src/layoutSystems/common/mainContainerResizer/MainContainerResizer.tsx @@ -4,9 +4,9 @@ import { useDispatch, useSelector } from "react-redux"; import { getCurrentApplicationLayout } from "selectors/editorSelectors"; import { setAutoCanvasResizing } from "actions/autoLayoutActions"; import styled from "styled-components"; +import { AUTOLAYOUT_RESIZER_WIDTH_BUFFER } from "utils/hooks/useDynamicAppLayout"; import { importSvg } from "design-system-old"; import { CANVAS_VIEWPORT } from "constants/componentClassNameConstants"; -import { AUTOLAYOUT_RESIZER_WIDTH_BUFFER } from "./constants"; const CanvasResizerIcon = importSvg( async () => import("assets/icons/ads/app-icons/canvas-resizer.svg"), diff --git a/app/client/src/layoutSystems/common/mainContainerResizer/constants.ts b/app/client/src/layoutSystems/common/mainContainerResizer/constants.ts deleted file mode 100644 index a6c4ec0b5e43..000000000000 --- a/app/client/src/layoutSystems/common/mainContainerResizer/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const AUTOLAYOUT_RESIZER_WIDTH_BUFFER = 40; diff --git a/app/client/src/pages/AppViewer/AppPage/AppPage.styled.tsx b/app/client/src/pages/AppViewer/AppPage.styled.tsx similarity index 100% rename from app/client/src/pages/AppViewer/AppPage/AppPage.styled.tsx rename to app/client/src/pages/AppViewer/AppPage.styled.tsx diff --git a/app/client/src/pages/AppViewer/AppPage/AppPage.tsx b/app/client/src/pages/AppViewer/AppPage.tsx similarity index 51% rename from app/client/src/pages/AppViewer/AppPage/AppPage.tsx rename to app/client/src/pages/AppViewer/AppPage.tsx index bbb77309154c..1cf54e4e0783 100644 --- a/app/client/src/pages/AppViewer/AppPage/AppPage.tsx +++ b/app/client/src/pages/AppViewer/AppPage.tsx @@ -1,18 +1,15 @@ -import React, { useEffect, useMemo, useRef } from "react"; +import React, { useEffect, useMemo } from "react"; import AnalyticsUtil from "@appsmith/utils/AnalyticsUtil"; +import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout"; import type { CanvasWidgetStructure } from "WidgetProvider/constants"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import { getAppMode } from "@appsmith/selectors/applicationSelectors"; +import { PageView, PageViewWrapper } from "./AppPage.styled"; import { APP_MODE } from "entities/App"; import { renderAppsmithCanvas } from "layoutSystems/CanvasFactory"; import type { WidgetProps } from "widgets/BaseWidget"; import { useAppViewerSidebarProperties } from "utils/hooks/useAppViewerSidebarProperties"; import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors"; -import { debounce } from "lodash"; -import { updateCanvasLayoutAction } from "actions/editorActions"; - -import { PageView, PageViewWrapper } from "./AppPage.styled"; -import { RESIZE_DEBOUNCE_THRESHOLD } from "./constants"; interface AppPageProps { appName?: string; @@ -23,61 +20,35 @@ interface AppPageProps { } export function AppPage(props: AppPageProps) { - const { appName, canvasWidth, pageId, pageName, widgetsStructure } = props; - - const dispatch = useDispatch(); const appMode = useSelector(getAppMode); const isPublished = appMode === APP_MODE.PUBLISHED; const isAnvilLayout = useSelector(getIsAnvilLayout); const { hasSidebarPinned, sidebarWidth } = useAppViewerSidebarProperties(); const width: string = useMemo(() => { - return isAnvilLayout ? "100%" : `${canvasWidth}px`; - }, [isAnvilLayout, canvasWidth]); - - const pageViewWrapperRef = useRef(null); - useEffect(() => { - const wrapperElement = pageViewWrapperRef.current; - if (wrapperElement) { - const debouncedResize = debounce( - ([ - { - contentRect: { width }, - }, - ]) => { - dispatch(updateCanvasLayoutAction(width - sidebarWidth)); - }, - RESIZE_DEBOUNCE_THRESHOLD, - ); - - const resizeObserver = new ResizeObserver(debouncedResize); - resizeObserver.observe(wrapperElement); + return isAnvilLayout ? "100%" : `${props.canvasWidth}px`; + }, [isAnvilLayout, props.canvasWidth]); - return () => { - resizeObserver.unobserve(wrapperElement); - }; - } - }, [dispatch, sidebarWidth]); + useDynamicAppLayout(); useEffect(() => { AnalyticsUtil.logEvent("PAGE_LOAD", { - pageName: pageName, - pageId: pageId, - appName: appName, + pageName: props.pageName, + pageId: props.pageId, + appName: props.appName, mode: "VIEW", }); - }, [appName, pageId, pageName]); + }, [props.pageId, props.pageName]); return ( - {widgetsStructure.widgetId && - renderAppsmithCanvas(widgetsStructure as WidgetProps)} + {props.widgetsStructure.widgetId && + renderAppsmithCanvas(props.widgetsStructure as WidgetProps)} ); diff --git a/app/client/src/pages/AppViewer/AppPage/constants.ts b/app/client/src/pages/AppViewer/AppPage/constants.ts deleted file mode 100644 index d7900e08a440..000000000000 --- a/app/client/src/pages/AppViewer/AppPage/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const RESIZE_DEBOUNCE_THRESHOLD = 100; diff --git a/app/client/src/pages/AppViewer/AppPage/index.ts b/app/client/src/pages/AppViewer/AppPage/index.ts deleted file mode 100644 index 8762d6e74356..000000000000 --- a/app/client/src/pages/AppViewer/AppPage/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { AppPage as default } from "./AppPage"; -export { PageViewWrapper } from "./AppPage.styled"; diff --git a/app/client/src/pages/Editor/IDE/AppsmithIDE.test.tsx b/app/client/src/pages/Editor/IDE/AppsmithIDE.test.tsx index 4bd9f46f4b26..05036ada7953 100644 --- a/app/client/src/pages/Editor/IDE/AppsmithIDE.test.tsx +++ b/app/client/src/pages/Editor/IDE/AppsmithIDE.test.tsx @@ -22,7 +22,7 @@ import { UpdatedEditor } from "test/testMockedWidgets"; import { act, fireEvent, render } from "test/testUtils"; import { generateReactKey } from "utils/generators"; import { getAbsolutePixels } from "utils/helpers"; -import * as useCanvasWidthAutoResize from "pages/Editor/WidgetsEditor/components/MainContainerWrapper"; +import * as useDynamicAppLayoutHook from "utils/hooks/useDynamicAppLayout"; import * as widgetRenderUtils from "utils/widgetRenderUtils"; import GlobalHotKeys from "../GlobalHotKeys"; import * as uiSelectors from "selectors/ui"; @@ -106,7 +106,7 @@ describe("Drag and Drop widgets into Main container", () => { .spyOn(utilities, "computeMainContainerWidget") .mockImplementation((widget) => widget as any); jest - .spyOn(useCanvasWidthAutoResize, "useCanvasWidthAutoResize") + .spyOn(useDynamicAppLayoutHook, "useDynamicAppLayout") .mockImplementation(() => true); const pushState = jest.spyOn(window.history, "pushState"); diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/MainContainerWrapper.tsx b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper.tsx similarity index 94% rename from app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/MainContainerWrapper.tsx rename to app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper.tsx index 56f5ac553b41..a60692ce9941 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/MainContainerWrapper.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper.tsx @@ -1,5 +1,5 @@ import type { ReactNode } from "react"; -import React, { useEffect, useRef } from "react"; +import React, { useEffect } from "react"; import { useSelector } from "react-redux"; import { @@ -22,13 +22,13 @@ import { getSelectedAppTheme, } from "selectors/appThemingSelectors"; import { getCanvasWidgetsStructure } from "@appsmith/selectors/entitiesSelector"; +import { useDynamicAppLayout } from "utils/hooks/useDynamicAppLayout"; import Canvas from "pages/Editor/Canvas"; import type { AppState } from "@appsmith/reducers"; import { getIsAnonymousDataPopupVisible } from "selectors/onboardingSelectors"; import { MainContainerResizer } from "layoutSystems/common/mainContainerResizer/MainContainerResizer"; import { useMainContainerResizer } from "layoutSystems/common/mainContainerResizer/useMainContainerResizer"; import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors"; -import { useCanvasWidthAutoResize } from "./hooks"; interface MainCanvasWrapperProps { isPreviewMode: boolean; @@ -107,7 +107,7 @@ const Wrapper = styled.section<{ * @prop currentPageId, current page id in string * @returns */ -export function MainContainerWrapper(props: MainCanvasWrapperProps) { +function MainContainerWrapper(props: MainCanvasWrapperProps) { const { isAppSettingsPaneWithNavigationTabOpen, navigationHeight } = props; const dispatch = useDispatch(); const { @@ -128,10 +128,8 @@ export function MainContainerWrapper(props: MainCanvasWrapperProps) { const isAppThemeChanging = useSelector(getAppThemeIsChanging); const showCanvasTopSection = useSelector(showCanvasTopSectionSelector); const showAnonymousDataPopup = useSelector(getIsAnonymousDataPopupVisible); - - const wrapperRef = useRef(null); - const isCanvasInitialized = useCanvasWidthAutoResize(wrapperRef); - const isPageInitializing = isFetchingPage || !isCanvasInitialized; + const isLayoutingInitialized = useDynamicAppLayout(); + const isPageInitializing = isFetchingPage || !isLayoutingInitialized; const { canShowResizer, enableMainContainerResizer } = useMainContainerResizer(); const isAnvilLayout = useSelector(getIsAnvilLayout); @@ -140,7 +138,7 @@ export function MainContainerWrapper(props: MainCanvasWrapperProps) { return () => { dispatch(forceOpenWidgetPanel(false)); }; - }, [dispatch]); + }, []); const fontFamily = `${selectedTheme.properties.fontFamily.appFont}, sans-serif`; const isAutoCanvasResizing = useSelector( @@ -205,7 +203,6 @@ export function MainContainerWrapper(props: MainCanvasWrapperProps) { } isPreviewingNavigation={isPreviewingNavigation} navigationHeight={navigationHeight} - ref={wrapperRef} style={{ height: isPreviewMode ? `calc(100% - ${navigationHeight})` : "auto", fontFamily: fontFamily, @@ -235,3 +232,5 @@ export function MainContainerWrapper(props: MainCanvasWrapperProps) { ); } + +export default MainContainerWrapper; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/constants.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/constants.ts deleted file mode 100644 index d7900e08a440..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const RESIZE_DEBOUNCE_THRESHOLD = 100; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/index.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/index.ts deleted file mode 100644 index 432c42f69ab6..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useCanvasWidthAutoResize } from "./useCanvasWidthAutoResize"; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/useCanvasWidthAutoResize.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/useCanvasWidthAutoResize.ts deleted file mode 100644 index 969c782bc0b9..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/hooks/useCanvasWidthAutoResize.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { debounce } from "lodash"; - -import { updateCanvasLayoutAction } from "actions/editorActions"; -import { DefaultLayoutType } from "constants/WidgetConstants"; -import { getCurrentApplicationLayout } from "selectors/editorSelectors"; - -import { resolveCanvasWidth } from "../utils/resolveCanvasWidth"; -import { RESIZE_DEBOUNCE_THRESHOLD } from "./constants"; -import { getIsCanvasInitialized } from "selectors/mainCanvasSelectors"; - -export const useCanvasWidthAutoResize = (ref: React.RefObject) => { - const dispatch = useDispatch(); - - const isCanvasInitialized = useSelector(getIsCanvasInitialized); - const { type: appLayoutType = DefaultLayoutType } = useSelector( - getCurrentApplicationLayout, - ); - - useEffect(() => { - if (!isCanvasInitialized && ref.current) { - const resolvedCanvasWidth = resolveCanvasWidth({ - appLayoutType, - containerWidth: ref.current.offsetWidth, - }); - dispatch(updateCanvasLayoutAction(resolvedCanvasWidth)); - } - }, [appLayoutType, dispatch, isCanvasInitialized, ref]); - - useEffect(() => { - const canvasContainerElement = ref.current; - if (canvasContainerElement) { - const debouncedResize = debounce( - ([ - { - contentRect: { width }, - }, - ]) => { - const resolvedCanvasWidth = resolveCanvasWidth({ - appLayoutType, - containerWidth: width, - }); - - dispatch(updateCanvasLayoutAction(resolvedCanvasWidth)); - }, - RESIZE_DEBOUNCE_THRESHOLD, - ); - - const resizeObserver = new ResizeObserver(debouncedResize); - resizeObserver.observe(canvasContainerElement); - - return () => { - resizeObserver.unobserve(canvasContainerElement); - }; - } - }, [ref, dispatch, appLayoutType]); - - return isCanvasInitialized; -}; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/index.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/index.ts deleted file mode 100644 index 7df93d245be6..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MainContainerWrapper as default } from "./MainContainerWrapper"; -export { useCanvasWidthAutoResize } from "./hooks/useCanvasWidthAutoResize"; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/index.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/index.ts deleted file mode 100644 index b2d9ed1e5c5f..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { resolveCanvasWidth } from "./resolveCanvasWidth"; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.test.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.test.ts deleted file mode 100644 index 197cf4699a0d..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { layoutConfigurations } from "constants/WidgetConstants"; -import { resolveCanvasWidth } from "./resolveCanvasWidth"; -import type { SupportedLayouts } from "reducers/entityReducers/pageListReducer"; - -const layoutTestConfigs = Object.entries(layoutConfigurations) - .filter(([key]) => key !== "FLUID") - .map(([key, widths]) => { - const appLayoutType = key as SupportedLayouts; - return [ - appLayoutType, - { - ...widths, - }, - ] as const; - }); - -describe("resolveCanvasWidth", () => { - test.each(layoutTestConfigs)( - "results are within range for %s", - (appLayoutType, { maxWidth, minWidth }) => { - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: maxWidth, - }), - ).toBe(maxWidth); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: minWidth, - }), - ).toBe(minWidth); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: maxWidth - 1, - }), - ).toBe(maxWidth - 1); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: minWidth + 1, - }), - ).toBe(minWidth + 1); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: -1, - }), - ).toBe(minWidth); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: layoutConfigurations[appLayoutType].minWidth - 1, - }), - ).toBe(minWidth); - - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: Infinity, - }), - ).toBe(maxWidth); - }, - ); - - it("results are within range for FLUID", () => { - const appLayoutType = "FLUID"; - - const widths = { - min: 0, - sm: 576, - md: 768, - lg: 1200, - max: Infinity, - }; - - for (const width of Object.values(widths)) { - expect( - resolveCanvasWidth({ - appLayoutType, - containerWidth: width, - }), - ).toEqual(width); - } - }); -}); diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.ts b/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.ts deleted file mode 100644 index a5c2bc34945c..000000000000 --- a/app/client/src/pages/Editor/WidgetsEditor/components/MainContainerWrapper/utils/resolveCanvasWidth.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { layoutConfigurations } from "constants/WidgetConstants"; -import type { SupportedLayouts } from "reducers/entityReducers/pageListReducer"; - -interface CalculateCanvasWidthProps { - appLayoutType: SupportedLayouts; - containerWidth: number; -} - -export const resolveCanvasWidth = ({ - appLayoutType, - containerWidth, -}: CalculateCanvasWidthProps) => { - const { maxWidth, minWidth } = layoutConfigurations[appLayoutType]; - - switch (true) { - case maxWidth < 0: - case containerWidth >= minWidth && containerWidth <= maxWidth: - return containerWidth; - case containerWidth < minWidth: - return minWidth; - case containerWidth > maxWidth: - return maxWidth; - default: - return minWidth; - } -}; diff --git a/app/client/src/pages/Editor/WidgetsEditor/components/NavigationAdjustedPageViewer.tsx b/app/client/src/pages/Editor/WidgetsEditor/components/NavigationAdjustedPageViewer.tsx index 14c803677436..d1b0dbbc797d 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/components/NavigationAdjustedPageViewer.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/components/NavigationAdjustedPageViewer.tsx @@ -8,7 +8,7 @@ import { combinedPreviewModeSelector, getCurrentApplication, } from "selectors/editorSelectors"; -import { PageViewWrapper } from "pages/AppViewer/AppPage"; +import { PageViewWrapper } from "pages/AppViewer/AppPage.styled"; import classNames from "classnames"; import { APP_MODE } from "entities/App"; import { getAppMode } from "@appsmith/selectors/entitiesSelector"; diff --git a/app/client/src/utils/hooks/useDynamicAppLayout.tsx b/app/client/src/utils/hooks/useDynamicAppLayout.tsx new file mode 100644 index 000000000000..8d8632b1707c --- /dev/null +++ b/app/client/src/utils/hooks/useDynamicAppLayout.tsx @@ -0,0 +1,292 @@ +import { debounce, get } from "lodash"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { updateLayoutForMobileBreakpointAction } from "actions/autoLayoutActions"; +import { updateCanvasLayoutAction } from "actions/editorActions"; +import { APP_SIDEBAR_WIDTH } from "constants/AppConstants"; +import { + DefaultLayoutType, + layoutConfigurations, + MAIN_CONTAINER_WIDGET_ID, +} from "constants/WidgetConstants"; +import { APP_MODE } from "entities/App"; +import { LayoutSystemTypes } from "layoutSystems/types"; +import { + combinedPreviewModeSelector, + getCurrentApplicationLayout, + getCurrentPageId, + getMainCanvasProps, +} from "selectors/editorSelectors"; +import { getAppMode } from "@appsmith/selectors/entitiesSelector"; +import { getExplorerWidth } from "selectors/explorerSelector"; +import { getIsCanvasInitialized } from "selectors/mainCanvasSelectors"; +import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; +import { + getAppSidebarPinned, + getCurrentApplication, + getSidebarWidth, +} from "@appsmith/selectors/applicationSelectors"; +import { useIsMobileDevice } from "./useDeviceDetect"; +import { getPropertyPaneWidth } from "selectors/propertyPaneSelectors"; +import { scrollbarWidth } from "utils/helpers"; +import { useWindowSizeHooks } from "./dragResizeHooks"; +import type { AppState } from "@appsmith/reducers"; +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import { useLocation } from "react-router"; +import { CANVAS_VIEWPORT } from "constants/componentClassNameConstants"; +import { getLayoutSystemType } from "selectors/layoutSystemSelectors"; + +const GUTTER_WIDTH = 72; +export const AUTOLAYOUT_RESIZER_WIDTH_BUFFER = 40; + +export const useDynamicAppLayout = () => { + const dispatch = useDispatch(); + const explorerWidth = useSelector(getExplorerWidth); + const propertyPaneWidth = useSelector(getPropertyPaneWidth); + const appMode: APP_MODE | undefined = useSelector(getAppMode); + const { width: screenWidth } = useWindowSizeHooks(); + const mainCanvasProps = useSelector(getMainCanvasProps); + const isPreviewMode = useSelector(combinedPreviewModeSelector); + const currentPageId = useSelector(getCurrentPageId); + const isCanvasInitialized = useSelector(getIsCanvasInitialized); + const appLayout = useSelector(getCurrentApplicationLayout); + const layoutSystemType = useSelector(getLayoutSystemType); + const isAppSidebarPinned = useSelector(getAppSidebarPinned); + const sidebarWidth = useSelector(getSidebarWidth); + const isAppSettingsPaneWithNavigationTabOpen = useSelector( + getIsAppSettingsPaneWithNavigationTabOpen, + ); + const currentApplicationDetails = useSelector(getCurrentApplication); + const isMobile = useIsMobileDevice(); + const isAutoCanvasResizing = useSelector( + (state: AppState) => state.ui.widgetDragResize.isAutoCanvasResizing, + ); + const [isCanvasResizing, setIsCanvasResizing] = useState(false); + const { search } = useLocation(); + const queryParams = new URLSearchParams(search); + const isEmbed = queryParams.get("embed"); + const isNavbarVisibleInEmbeddedApp = queryParams.get("navbar"); + + const isPreviewing = isPreviewMode; + + /** + * app layout range i.e minWidth and maxWidth for the current layout + * if there is no config for the current layout, use default layout i.e desktop + */ + const layoutWidthRange = useMemo(() => { + let minWidth = -1; + let maxWidth = -1; + + if (appLayout) { + const { type } = appLayout; + const currentLayoutConfig = get( + layoutConfigurations, + type, + layoutConfigurations[DefaultLayoutType], + ); + + if (currentLayoutConfig.minWidth) minWidth = currentLayoutConfig.minWidth; + if (currentLayoutConfig.maxWidth) maxWidth = currentLayoutConfig.maxWidth; + } + + return { minWidth, maxWidth }; + }, [appLayout]); + + /** + * calculate the width for the canvas + * + * cases: + * - if max width is negative, use calculated width + * - if calculated width is in range of min/max widths of layout, use calculated width + * - if calculated width is less then min width, use min Width + * - if calculated width is larger than max width, use max width + * - by default use min width + * + * @returns + */ + const calculateCanvasWidth = () => { + let { maxWidth } = layoutWidthRange; + const { minWidth } = layoutWidthRange; + let calculatedWidth = screenWidth - scrollbarWidth(); + + const gutterWidth = + layoutSystemType === LayoutSystemTypes.AUTO ? 0 : GUTTER_WIDTH; + + // if preview mode is not on and the app setting pane is not opened, we need to subtract the width of the property pane + if (!isPreviewing && appMode === APP_MODE.EDIT) { + calculatedWidth -= propertyPaneWidth; + } + + // if explorer is closed or its preview mode, we don't need to subtract the EE width + if (!isPreviewing && appMode === APP_MODE.EDIT) { + calculatedWidth -= explorerWidth; + } + + if (appMode === APP_MODE.EDIT) { + calculatedWidth -= APP_SIDEBAR_WIDTH; + } + + /** + * If there is + * 1. a sidebar for navigation, + * 2. it is pinned, + * 3. device is not mobile + * 4. and it is not an embedded app + * we need to subtract the sidebar width as well in the following modes - + * 1. Preview + * 2. App settings open with navigation tab + * 3. Published + */ + const isEmbeddedAppWithNavVisible = isEmbed && isNavbarVisibleInEmbeddedApp; + if ( + (appMode === APP_MODE.PUBLISHED || + isPreviewing || + isAppSettingsPaneWithNavigationTabOpen) && + !isMobile && + (!isEmbed || isEmbeddedAppWithNavVisible) && + sidebarWidth + ) { + calculatedWidth -= sidebarWidth; + } + if (isMobile) { + maxWidth += sidebarWidth; + } + const ele: any = document.getElementById(CANVAS_VIEWPORT); + if ( + appMode === APP_MODE.EDIT && + appLayout?.type === "FLUID" && + ele && + calculatedWidth > ele.clientWidth + ) { + calculatedWidth = ele.clientWidth; + } + + switch (true) { + case maxWidth < 0: + case appLayout?.type === "FLUID": + case calculatedWidth < maxWidth && calculatedWidth > minWidth: + const totalWidthToSubtract = gutterWidth; + // NOTE: gutter + border width will be only substracted when theme mode and preview mode are off + return ( + calculatedWidth - + (appMode === APP_MODE.EDIT && + !isPreviewing && + !isAppSettingsPaneWithNavigationTabOpen + ? totalWidthToSubtract + : 0) + ); + case calculatedWidth < minWidth: + return minWidth; + case calculatedWidth > maxWidth: + return maxWidth; + default: + return minWidth; + } + }; + + /** + * resizes the layout based on the layout type + */ + const resizeToLayout = () => { + const calculatedWidth = calculateCanvasWidth(); + const { width: rightColumn } = mainCanvasProps || {}; + if (rightColumn !== calculatedWidth || !isCanvasInitialized) { + dispatch(updateCanvasLayoutAction(calculatedWidth)); + } + return calculatedWidth; + }; + + const debouncedResize = useRef(debounce(resizeToLayout, 250)); + const immediateDebouncedResize = useRef(debounce(resizeToLayout)); + + useEffect(() => { + const resizeObserver = new ResizeObserver(immediateDebouncedResize.current); + const canvasViewportElement = document.getElementById(CANVAS_VIEWPORT); + + if ( + canvasViewportElement && + canvasViewportElement instanceof HTMLElement && + appLayout?.type === "FLUID" + ) { + resizeObserver.observe(canvasViewportElement); + + return () => { + resizeObserver.unobserve(canvasViewportElement); + }; + } + }, [appLayout, currentPageId, immediateDebouncedResize, isPreviewing]); + + useEffect(() => { + if (isCanvasInitialized) debouncedResize.current(); + }, [isCanvasInitialized, screenWidth]); + + /** + * resize the layout if any of the following thing changes: + * - app layout + * - page + * - container right column + * - preview mode + * - explorer width + * - explorer is pinned + * - theme mode is turned on + * - sidebar pin/unpin + * - app settings pane open with navigation tab + * - any of the following navigation settings changes + * - orientation + * - nav style + * - device changes to/from mobile + */ + useEffect(() => { + resizeToLayout(); + }, [ + appLayout, + mainCanvasProps?.width, + isPreviewing, + isAppSettingsPaneWithNavigationTabOpen, + explorerWidth, + sidebarWidth, + propertyPaneWidth, + propertyPaneWidth, + isAppSidebarPinned, + currentApplicationDetails?.applicationDetail?.navigationSetting + ?.orientation, + currentApplicationDetails?.applicationDetail?.navigationSetting?.navStyle, + isMobile, + currentPageId, //TODO: preet - remove this after first merge. + ]); + + useEffect(() => { + dispatch( + updateLayoutForMobileBreakpointAction( + MAIN_CONTAINER_WIDGET_ID, + layoutSystemType === LayoutSystemTypes.AUTO + ? mainCanvasProps?.isMobile + : false, + calculateCanvasWidth(), + ), + ); + }, [mainCanvasProps?.isMobile]); + + useEffect(() => { + if (isAutoCanvasResizing) setIsCanvasResizing(true); + else if (isCanvasResizing) { + setIsCanvasResizing(false); + const canvasWidth: number = resizeToLayout(); + dispatch( + updateLayoutForMobileBreakpointAction( + MAIN_CONTAINER_WIDGET_ID, + layoutSystemType === LayoutSystemTypes.AUTO + ? mainCanvasProps?.isMobile + : false, + canvasWidth, + ), + ); + dispatch({ + type: ReduxActionTypes.PROCESS_AUTO_LAYOUT_DIMENSION_UPDATES, + }); + } + }, [isAutoCanvasResizing]); + + return isCanvasInitialized; +}; diff --git a/app/client/src/widgets/ContainerWidget/widget/index.test.tsx b/app/client/src/widgets/ContainerWidget/widget/index.test.tsx index a60bce302239..8888ae92453e 100644 --- a/app/client/src/widgets/ContainerWidget/widget/index.test.tsx +++ b/app/client/src/widgets/ContainerWidget/widget/index.test.tsx @@ -2,7 +2,7 @@ import GlobalHotKeys from "pages/Editor/GlobalHotKeys"; import React from "react"; import { MemoryRouter } from "react-router-dom"; import * as utilities from "selectors/editorSelectors"; -import * as useCanvasWidthAutoResize from "pages/Editor/WidgetsEditor/components/MainContainerWrapper"; +import * as useDynamicAppLayoutHook from "utils/hooks/useDynamicAppLayout"; import * as useCanvasDraggingHook from "layoutSystems/fixedlayout/editor/FixedLayoutCanvasArenas/hooks/useCanvasDragging"; import store from "store"; @@ -20,9 +20,8 @@ const pageId = "0123456789abcdef00000000"; describe("ContainerWidget tests", () => { const mockGetIsFetchingPage = jest.spyOn(utilities, "getIsFetchingPage"); jest - .spyOn(useCanvasWidthAutoResize, "useCanvasWidthAutoResize") + .spyOn(useDynamicAppLayoutHook, "useDynamicAppLayout") .mockImplementation(() => true); - const pushState = jest.spyOn(window.history, "pushState"); pushState.mockImplementation((state: any, title: any, url: any) => {