chore: Transitions for IDE (#35714)

## Description

Uses `AnimatedGridLayout` component to introduce transitions for the
IDE. This is behind a feature flag

Fixes #34538
Fixes #30863 
Fixes #34544 

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!CAUTION]
> 🔴 🔴 🔴 Some tests have failed.
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/10737363879>
> Commit: a912f5c52366abe48768727a1c605cd72b48752c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=10737363879&attempt=2&selectiontype=test&testsstatus=failed&specsstatus=fail"
target="_blank">Cypress dashboard</a>.
> Tags: @tag.All
> Spec: 
> The following are new failures, please fix them before merging the PR:
<ol>
>
<li>cypress/e2e/Regression/ServerSide/OnLoadTests/ExecuteAction_Spec.ts</ol>
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master"
target="_blank">List of identified flaky tests</a>.
> <hr>Fri, 06 Sep 2024 16:32:30 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Introduced two new layout components: `AnimatedLayout` and
`UnanimatedLayout` for improved editor interface structuring.
- Added TypeScript type definitions for the DOM View Transitions API to
enhance type safety and developer experience.
- Implemented custom hooks, `useGridLayoutTemplate` and
`useEditorStateLeftPaneWidth`, for dynamic grid management and left pane
width calculation in the IDE layout.

- **Improvements**
- Enhanced layout responsiveness with the addition of dynamic grid
management.
- Updated the `Editor` component to use a centralized constant for
height calculations, improving maintainability and consistency.
- Enhanced test accuracy by refining assertions in the Git Branch
Protection test suite.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Sagar Khalasi <sagar@appsmith.com>
This commit is contained in:
Hetu Nandu 2024-09-09 16:25:50 +05:30 committed by GitHub
parent 1b16be896d
commit 60dbda49e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 517 additions and 243 deletions

View File

@ -36,10 +36,7 @@ describe("Git Branch Protection", { tags: ["@tag.Git"] }, function () {
cy.wait("@gitProtectApi").then((res1) => { cy.wait("@gitProtectApi").then((res1) => {
_.agHelper.GetNClick(_.gitSync._closeGitSettingsModal); _.agHelper.GetNClick(_.gitSync._closeGitSettingsModal);
expect(res1.response).to.have.property("statusCode", 200); expect(res1.response).to.have.property("statusCode", 200);
_.agHelper.AssertElementVisibility( _.agHelper.AssertElementAbsence(AppSidebar.locators.sidebar);
AppSidebar.locators.sidebar,
false,
);
_.agHelper.AssertElementVisibility( _.agHelper.AssertElementVisibility(
PageLeftPane.locators.selector, PageLeftPane.locators.selector,
false, false,

View File

@ -269,6 +269,7 @@
"@types/codemirror": "^0.0.96", "@types/codemirror": "^0.0.96",
"@types/deep-diff": "^1.0.0", "@types/deep-diff": "^1.0.0",
"@types/dom-mediacapture-record": "^1.0.11", "@types/dom-mediacapture-record": "^1.0.11",
"@types/dom-view-transitions": "^1.0.5",
"@types/downloadjs": "^1.4.2", "@types/downloadjs": "^1.4.2",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/js-beautify": "^1.13.2", "@types/js-beautify": "^1.13.2",

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Divider, Flex } from "@appsmith/ads"; import { Divider, Flex } from "@appsmith/ads";
import { AppsmithLink } from "pages/Editor/AppsmithLink"; import { AppsmithLink } from "pages/Editor/AppsmithLink";
import { IDE_HEADER_HEIGHT } from "./constants";
interface ChildrenProps { interface ChildrenProps {
children: React.ReactNode | React.ReactNode[]; children: React.ReactNode | React.ReactNode[];
@ -59,7 +60,7 @@ const Header = (props: ChildrenProps) => {
alignItems="center" alignItems="center"
border="1px solid var(--ads-v2-color-border)" border="1px solid var(--ads-v2-color-border)"
className="t--editor-header" className="t--editor-header"
height="40px" height={IDE_HEADER_HEIGHT + "px"}
overflow="hidden" overflow="hidden"
width="100%" width="100%"
> >

View File

@ -0,0 +1 @@
export const IDE_HEADER_HEIGHT = 40;

View File

@ -9,6 +9,7 @@
* These are composable components that you can use to spread the content of the header * These are composable components that you can use to spread the content of the header
* It is possible to use the IDE Header without using these subsections * It is possible to use the IDE Header without using these subsections
*/ */
export { IDE_HEADER_HEIGHT } from "./Structure/constants";
export { default as IDEHeader } from "./Structure/Header"; export { default as IDEHeader } from "./Structure/Header";
/* ==================================================== /* ====================================================

View File

@ -45,6 +45,7 @@ export const FEATURE_FLAG = {
"ab_learnability_discoverability_collapse_all_except_data_enabled", "ab_learnability_discoverability_collapse_all_except_data_enabled",
release_layout_conversion_enabled: "release_layout_conversion_enabled", release_layout_conversion_enabled: "release_layout_conversion_enabled",
release_anvil_toggle_enabled: "release_anvil_toggle_enabled", release_anvil_toggle_enabled: "release_anvil_toggle_enabled",
release_ide_animations_enabled: "release_ide_animations_enabled",
} as const; } as const;
export type FeatureFlag = keyof typeof FEATURE_FLAG; export type FeatureFlag = keyof typeof FEATURE_FLAG;
@ -82,6 +83,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
ab_learnability_discoverability_collapse_all_except_data_enabled: true, ab_learnability_discoverability_collapse_all_except_data_enabled: true,
release_layout_conversion_enabled: false, release_layout_conversion_enabled: false,
release_anvil_toggle_enabled: false, release_anvil_toggle_enabled: false,
release_ide_animations_enabled: false,
}; };
export const AB_TESTING_EVENT_KEYS = { export const AB_TESTING_EVENT_KEYS = {

View File

@ -41,7 +41,7 @@ export function LayoutArea(props: LayoutAreaProps) {
> >
<div <div
style={{ style={{
minWidth: dimensions?.width || "100%", width: dimensions?.width || "100%",
height: "100%", height: "100%",
position: "absolute", position: "absolute",
overflow: "auto", overflow: "auto",

View File

@ -1,9 +1,11 @@
import { easings } from "@react-spring/web";
import type { AnimatedGridUnit } from "./types"; import type { AnimatedGridUnit } from "./types";
/** Default rows config. */ /** Default rows config. */
export const DEFAULT_ROWS: AnimatedGridUnit[] = ["1fr"]; export const DEFAULT_ROWS: AnimatedGridUnit[] = ["1fr"];
export const SPRING_ANIMATION_CONFIG = { export const SPRING_ANIMATION_CONFIG = {
easing: easings.easeInCirc, duration: 375,
friction: 32,
mass: 1,
tension: 205,
}; };

View File

@ -1,9 +1,10 @@
import styled from "styled-components"; import styled from "styled-components";
import { Layers } from "constants/Layers"; import { Layers } from "constants/Layers";
import { BOTTOM_BAR_HEIGHT } from "./constants";
export const Container = styled.div` export const Container = styled.div`
width: 100%; width: 100%;
height: ${(props) => props.theme.bottomBarHeight}; height: ${BOTTOM_BAR_HEIGHT}px;
display: flex; display: flex;
position: fixed; position: fixed;
justify-content: space-between; justify-content: space-between;

View File

@ -0,0 +1 @@
export const BOTTOM_BAR_HEIGHT = 37;

View File

@ -7,14 +7,18 @@ import { Button } from "@appsmith/ads";
import SwitchEnvironment from "ee/components/SwitchEnvironment"; import SwitchEnvironment from "ee/components/SwitchEnvironment";
import { Container, Wrapper } from "./components"; import { Container, Wrapper } from "./components";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getCurrentApplicationId } from "selectors/editorSelectors"; import {
getCurrentApplicationId,
previewModeSelector,
} from "selectors/editorSelectors";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { softRefreshActions } from "actions/pluginActionActions"; import { softRefreshActions } from "actions/pluginActionActions";
import { START_SWITCH_ENVIRONMENT } from "ee/constants/messages"; import { START_SWITCH_ENVIRONMENT } from "ee/constants/messages";
import { getIsAnvilEnabledInCurrentApplication } from "layoutSystems/anvil/integrations/selectors"; import { getIsAnvilEnabledInCurrentApplication } from "layoutSystems/anvil/integrations/selectors";
export default function BottomBar({ viewMode }: { viewMode: boolean }) { export default function BottomBar() {
const appId = useSelector(getCurrentApplicationId) || ""; const appId = useSelector(getCurrentApplicationId) || "";
const isPreviewMode = useSelector(previewModeSelector);
const dispatch = useDispatch(); const dispatch = useDispatch();
// We check if the current application is an Anvil application. // We check if the current application is an Anvil application.
// If it is an Anvil application, we remove the Git features from the bottomBar // If it is an Anvil application, we remove the Git features from the bottomBar
@ -28,17 +32,17 @@ export default function BottomBar({ viewMode }: { viewMode: boolean }) {
return ( return (
<Container> <Container>
<Wrapper> <Wrapper>
{!viewMode && ( {!isPreviewMode && (
<SwitchEnvironment <SwitchEnvironment
editorId={appId} editorId={appId}
onChangeEnv={onChangeEnv} onChangeEnv={onChangeEnv}
startSwitchEnvMessage={START_SWITCH_ENVIRONMENT} startSwitchEnvMessage={START_SWITCH_ENVIRONMENT}
viewMode={viewMode} viewMode={isPreviewMode}
/> />
)} )}
{!viewMode && !isAnvilEnabled && <QuickGitActions />} {!isPreviewMode && !isAnvilEnabled && <QuickGitActions />}
</Wrapper> </Wrapper>
{!viewMode && ( {!isPreviewMode && (
<Wrapper> <Wrapper>
<ManualUpgrades showTooltip> <ManualUpgrades showTooltip>
<Button <Button

View File

@ -1,51 +1,28 @@
import classNames from "classnames"; import React, {
memo,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import React, { memo, useContext, useEffect, useMemo, useRef } from "react";
import { getSelectedWidgets } from "selectors/ui"; import { getSelectedWidgets } from "selectors/ui";
import { tailwindLayers } from "constants/Layers";
import WidgetPropertyPane from "pages/Editor/PropertyPane"; import WidgetPropertyPane from "pages/Editor/PropertyPane";
import CanvasPropertyPane from "pages/Editor/CanvasPropertyPane"; import CanvasPropertyPane from "pages/Editor/CanvasPropertyPane";
import useHorizontalResize from "utils/hooks/useHorizontalResize";
import { getIsDraggingForSelection } from "selectors/canvasSelectors";
import MultiSelectPropertyPane from "pages/Editor/MultiSelectPropertyPane"; import MultiSelectPropertyPane from "pages/Editor/MultiSelectPropertyPane";
import { getIsDraggingOrResizing } from "selectors/widgetSelectors"; import { getIsDraggingOrResizing } from "selectors/widgetSelectors";
import { selectedWidgetsPresentInCanvas } from "selectors/propertyPaneSelectors"; import { selectedWidgetsPresentInCanvas } from "selectors/propertyPaneSelectors";
import styled from "styled-components";
import WalkthroughContext from "components/featureWalkthrough/walkthroughContext"; import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants"; import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
export const PROPERTY_PANE_ID = "t--property-pane-sidebar"; export const PROPERTY_PANE_ID = "t--property-pane-sidebar";
const StyledResizer = styled.div<{ resizing: boolean }>` export const PropertyPaneSidebar = memo(() => {
${(props) =>
props.resizing &&
`
& > div {
background-color: var(--ads-v2-color-outline);
}
`}
:hover {
& > div {
background-color: var(--ads-v2-color-bg-emphasis);
}
}
`;
interface Props {
width: number;
onDragEnd?: () => void;
onWidthChange: (width: number) => void;
}
export const PropertyPaneSidebar = memo((props: Props) => {
const sidebarRef = useRef<HTMLDivElement>(null); const sidebarRef = useRef<HTMLDivElement>(null);
const prevSelectedWidgetId = useRef<string | undefined>(); const prevSelectedWidgetId = useRef<string | undefined>();
const { onMouseDown, onMouseUp, onTouchStart, resizing } =
useHorizontalResize(sidebarRef, props.onWidthChange, props.onDragEnd, true);
const selectedWidgetIds = useSelector(getSelectedWidgets); const selectedWidgetIds = useSelector(getSelectedWidgets);
const isDraggingOrResizing = useSelector(getIsDraggingOrResizing); const isDraggingOrResizing = useSelector(getIsDraggingOrResizing);
const { isOpened: isWalkthroughOpened, popFeature } = const { isOpened: isWalkthroughOpened, popFeature } =
@ -58,17 +35,10 @@ export const PropertyPaneSidebar = memo((props: Props) => {
selectedWidgetIds[0] !== prevSelectedWidgetId.current) || selectedWidgetIds[0] !== prevSelectedWidgetId.current) ||
selectedWidgetIds[0] === MAIN_CONTAINER_WIDGET_ID; selectedWidgetIds[0] === MAIN_CONTAINER_WIDGET_ID;
// This is to keep the theming properties from changing,
// while dragging a widget when no other widgets were selected
const keepThemeWhileDragging =
prevSelectedWidgetId.current === undefined && shouldNotRenderPane;
const selectedWidgetsLength = useSelector( const selectedWidgetsLength = useSelector(
(state) => selectedWidgetsPresentInCanvas(state).length, (state) => selectedWidgetsPresentInCanvas(state).length,
); );
const isDraggingForSelection = useSelector(getIsDraggingForSelection);
prevSelectedWidgetId.current = prevSelectedWidgetId.current =
selectedWidgetIds.length === 1 ? selectedWidgetIds[0] : undefined; selectedWidgetIds.length === 1 ? selectedWidgetIds[0] : undefined;
@ -91,27 +61,24 @@ export const PropertyPaneSidebar = memo((props: Props) => {
default: default:
return <CanvasPropertyPane />; return <CanvasPropertyPane />;
} }
}, [ }, [selectedWidgetsLength, shouldNotRenderPane]);
selectedWidgetsLength,
isDraggingForSelection,
shouldNotRenderPane,
keepThemeWhileDragging,
]);
const closeWalkthrough = () => { const closeWalkthrough = useCallback(() => {
if (popFeature) { if (popFeature) {
popFeature("PROPERTY_PANE"); popFeature("PROPERTY_PANE");
sidebarRef.current?.removeEventListener("click", closeWalkthrough); sidebarRef.current?.removeEventListener("click", closeWalkthrough);
} }
}; }, [popFeature]);
useEffect(() => { useEffect(() => {
if (isWalkthroughOpened) const currentSidebar = sidebarRef.current;
sidebarRef.current?.addEventListener("click", closeWalkthrough); if (isWalkthroughOpened) {
currentSidebar?.addEventListener("click", closeWalkthrough);
}
return () => { return () => {
sidebarRef.current?.removeEventListener("click", closeWalkthrough); currentSidebar?.removeEventListener("click", closeWalkthrough);
}; };
}, [isWalkthroughOpened]); }, [closeWalkthrough, isWalkthroughOpened]);
return ( return (
<div className="relative h-full"> <div className="relative h-full">
@ -122,28 +89,7 @@ export const PropertyPaneSidebar = memo((props: Props) => {
id={PROPERTY_PANE_ID} id={PROPERTY_PANE_ID}
ref={sidebarRef} ref={sidebarRef}
> >
{/* RESIZER */} <div className={"h-full p-0 overflow-y-auto min-w-72 max-w-104 w-full"}>
<StyledResizer
className={`absolute top-0 left-0 w-2 h-full -ml-1 group cursor-ew-resize ${tailwindLayers.resizer}`}
onMouseDown={onMouseDown}
onTouchEnd={onMouseUp}
onTouchStart={onTouchStart}
resizing={resizing}
>
<div
className={classNames({
"w-1 h-full bg-transparent transform transition flex items-center":
true,
})}
/>
</StyledResizer>
<div
className={classNames({
"h-full p-0 overflow-y-auto min-w-72 max-w-104": true,
"transition-all duration-400": !resizing,
})}
style={{ width: props.width }}
>
{propertyPane} {propertyPane}
</div> </div>
</div> </div>

View File

@ -4,12 +4,12 @@ import React, { useEffect, useState } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Layers } from "constants/Layers"; import { Layers } from "constants/Layers";
import { theme } from "constants/DefaultTheme";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { getAppViewHeaderHeight } from "selectors/appViewSelectors"; import { getAppViewHeaderHeight } from "selectors/appViewSelectors";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { useMaxModalWidth } from "widgets/ModalWidget/component/useModalWidth"; import { useMaxModalWidth } from "widgets/ModalWidget/component/useModalWidth";
import { useAppViewerSidebarProperties } from "utils/hooks/useAppViewerSidebarProperties"; import { useAppViewerSidebarProperties } from "utils/hooks/useAppViewerSidebarProperties";
const Container = styled.div<{ const Container = styled.div<{
width?: number; width?: number;
height?: number; height?: number;
@ -22,7 +22,6 @@ const Container = styled.div<{
minSize?: number; minSize?: number;
isEditMode?: boolean; isEditMode?: boolean;
headerHeight?: number; headerHeight?: number;
smallHeaderHeight?: string;
leftSidebarWidth?: string; leftSidebarWidth?: string;
}>` }>`
&&& { &&& {
@ -152,7 +151,6 @@ export function ModalOverlayLayer(props: BaseWidgetProps) {
maxWidth={maxModalWidth} maxWidth={maxModalWidth}
minSize={props.minSize} minSize={props.minSize}
right={props.bottom} right={props.bottom}
smallHeaderHeight={theme.smallHeaderHeight}
top={props.top} top={props.top}
width={props.width} width={props.width}
zIndex={ zIndex={

View File

@ -1,6 +1,7 @@
import styled from "styled-components"; import styled from "styled-components";
import { Classes } from "@blueprintjs/core"; import { Classes } from "@blueprintjs/core";
import { Link, Text } from "@appsmith/ads"; import { Link, Text } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const Wrapper = styled.div` export const Wrapper = styled.div`
flex-basis: calc(100% - ${(props) => props.theme.homePage.leftPane.width}px); flex-basis: calc(100% - ${(props) => props.theme.homePage.leftPane.width}px);
@ -37,7 +38,7 @@ export const BottomSpace = styled.div`
export const ContentWrapper = styled.div``; export const ContentWrapper = styled.div``;
export const LoaderContainer = styled.div` export const LoaderContainer = styled.div`
height: ${(props) => `calc(100vh - ${props.theme.smallHeaderHeight})`}; height: calc(100vh - ${IDE_HEADER_HEIGHT}px);
display: flex; display: flex;
justify-content: center; justify-content: center;
width: 100%; width: 100%;

View File

@ -30,11 +30,12 @@ import {
StyledMenuContainer, StyledMenuContainer,
StyledSidebar, StyledSidebar,
} from "./Sidebar.styled"; } from "./Sidebar.styled";
import { getCurrentThemeDetails } from "selectors/themeSelectors";
import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors";
import NavigationLogo from "ee/pages/AppViewer/NavigationLogo"; import NavigationLogo from "ee/pages/AppViewer/NavigationLogo";
import MenuItemContainer from "./components/MenuItemContainer"; import MenuItemContainer from "./components/MenuItemContainer";
import BackToAppsButton from "./components/BackToAppsButton"; import BackToAppsButton from "./components/BackToAppsButton";
import { IDE_HEADER_HEIGHT } from "IDE";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
interface SidebarProps { interface SidebarProps {
currentApplicationDetails?: ApplicationPayload; currentApplicationDetails?: ApplicationPayload;
@ -80,7 +81,6 @@ export function Sidebar(props: SidebarProps) {
const isPinned = useSelector(getAppSidebarPinned); const isPinned = useSelector(getAppSidebarPinned);
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
const { x } = useMouse(); const { x } = useMouse();
const theme = useSelector(getCurrentThemeDetails);
const isPreviewMode = useSelector(combinedPreviewModeSelector); const isPreviewMode = useSelector(combinedPreviewModeSelector);
const isAppSettingsPaneWithNavigationTabOpen = useSelector( const isAppSettingsPaneWithNavigationTabOpen = useSelector(
getIsAppSettingsPaneWithNavigationTabOpen, getIsAppSettingsPaneWithNavigationTabOpen,
@ -127,10 +127,10 @@ export function Sidebar(props: SidebarProps) {
const suffix = ")"; const suffix = ")";
if (isPreviewMode) { if (isPreviewMode) {
prefix += `${theme.smallHeaderHeight} - ${theme.bottomBarHeight}`; prefix += `${IDE_HEADER_HEIGHT}px - ${BOTTOM_BAR_HEIGHT}px`;
} else if (isAppSettingsPaneWithNavigationTabOpen) { } else if (isAppSettingsPaneWithNavigationTabOpen) {
// We deduct 64px as well since it is the margin coming from "m-8" class from tailwind // We deduct 64px as well since it is the margin coming from "m-8" class from tailwind
prefix += `${theme.smallHeaderHeight} - ${theme.bottomBarHeight} - 64px`; prefix += `${IDE_HEADER_HEIGHT}px - ${BOTTOM_BAR_HEIGHT}px - 64px`;
} else { } else {
prefix += "0px"; prefix += "0px";
} }

View File

@ -94,7 +94,7 @@ const Canvas = (props: CanvasProps) => {
<Wrapper <Wrapper
$enableMainCanvasResizer={!!props.enableMainCanvasResizer} $enableMainCanvasResizer={!!props.enableMainCanvasResizer}
background={isAnvilLayout ? "" : backgroundForCanvas} background={isAnvilLayout ? "" : backgroundForCanvas}
className={`relative t--canvas-artboard ${paddingBottomClass} transition-all duration-400 ${marginHorizontalClass} ${getViewportClassName( className={`relative t--canvas-artboard ${paddingBottomClass} ${marginHorizontalClass} ${getViewportClassName(
canvasWidth, canvasWidth,
)}`} )}`}
data-testid={"t--canvas-artboard"} data-testid={"t--canvas-artboard"}

View File

@ -2,6 +2,7 @@ import styled from "styled-components";
import { Classes } from "@blueprintjs/core"; import { Classes } from "@blueprintjs/core";
import { getTypographyByKey } from "@appsmith/ads-old"; import { getTypographyByKey } from "@appsmith/ads-old";
import { Icon } from "@appsmith/ads"; import { Icon } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const Container = styled.div` export const Container = styled.div`
display: flex; display: flex;
@ -10,7 +11,7 @@ export const Container = styled.div`
background-color: var(--ads-v2-color-bg-subtle); background-color: var(--ads-v2-color-bg-subtle);
} }
& .${Classes.EDITABLE_TEXT} { & .${Classes.EDITABLE_TEXT} {
height: ${(props) => props.theme.smallHeaderHeight} !important; height: ${IDE_HEADER_HEIGHT} !important;
display: block; display: block;
cursor: pointer; cursor: pointer;
} }
@ -21,9 +22,9 @@ export const Container = styled.div`
&&&& .${Classes.EDITABLE_TEXT_CONTENT}, &&&& .${Classes.EDITABLE_TEXT_INPUT} { &&&& .${Classes.EDITABLE_TEXT_CONTENT}, &&&& .${Classes.EDITABLE_TEXT_INPUT} {
display: block; display: block;
${getTypographyByKey("h5")}; ${getTypographyByKey("h5")};
line-height: ${(props) => props.theme.smallHeaderHeight} !important; line-height: ${IDE_HEADER_HEIGHT} !important;
padding: 0 ${(props) => props.theme.spaces[2]}px; padding: 0 ${(props) => props.theme.spaces[2]}px;
height: ${(props) => props.theme.smallHeaderHeight} !important; height: ${IDE_HEADER_HEIGHT} !important;
} }
&&&& .${Classes.EDITABLE_TEXT_INPUT} { &&&& .${Classes.EDITABLE_TEXT_INPUT} {
margin-right: 20px; margin-right: 20px;

View File

@ -16,11 +16,11 @@ import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { import {
APP_SIDEBAR_WIDTH, APP_SIDEBAR_WIDTH,
DEFAULT_EXPLORER_PANE_WIDTH, DEFAULT_EXPLORER_PANE_WIDTH,
} from "../../../../constants/AppConstants"; } from "constants/AppConstants";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
const BindingContainerMaxHeight = 300; const BindingContainerMaxHeight = 300;
const EntityHeight = 36; const EntityHeight = 36;
const BottomBarHeight = 34;
const EntityInfoContainer = styled.div` const EntityInfoContainer = styled.div`
min-width: 220px; min-width: 220px;
@ -108,7 +108,7 @@ export function EntityProperties() {
let bottom; let bottom;
if ( if (
top + BindingContainerMaxHeight > top + BindingContainerMaxHeight >
window.innerHeight - BottomBarHeight window.innerHeight - BOTTOM_BAR_HEIGHT
) { ) {
bottom = window.innerHeight - rect?.bottom - EntityHeight; bottom = window.innerHeight - rect?.bottom - EntityHeight;
} }

View File

@ -11,6 +11,14 @@ import { QueryEditor } from "./Query";
import EditorTabs from "../EditorTabs"; import EditorTabs from "../EditorTabs";
import { useCurrentEditorState } from "../hooks"; import { useCurrentEditorState } from "../hooks";
import { EditorEntityTab } from "ee/entities/IDE/constants"; import { EditorEntityTab } from "ee/entities/IDE/constants";
import styled from "styled-components";
const Container = styled(Flex)`
// Animating using https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
& {
view-transition-name: editor-pane;
}
`;
const Editor = () => { const Editor = () => {
const { path } = useRouteMatch(); const { path } = useRouteMatch();
@ -19,7 +27,7 @@ const Editor = () => {
return null; return null;
} }
return ( return (
<Flex <Container
className="relative" className="relative"
flex={1} flex={1}
flexDirection="column" flexDirection="column"
@ -37,7 +45,7 @@ const Editor = () => {
path={querySegmentRoutes.map((route) => `${path}${route}`)} path={querySegmentRoutes.map((route) => `${path}${route}`)}
/> />
</Switch> </Switch>
</Flex> </Container>
); );
}; };

View File

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import { Flex } from "@appsmith/ads"; import { Flex } from "@appsmith/ads";
import { useEditorPaneWidth } from "../hooks";
import EditorPaneExplorer from "./Explorer"; import EditorPaneExplorer from "./Explorer";
import Editor from "./Editor"; import Editor from "./Editor";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@ -9,7 +8,6 @@ import { EditorViewMode } from "ee/entities/IDE/constants";
import EntityProperties from "pages/Editor/Explorer/Entity/EntityProperties"; import EntityProperties from "pages/Editor/Explorer/Entity/EntityProperties";
const EditorPane = () => { const EditorPane = () => {
const width = useEditorPaneWidth();
const ideViewMode = useSelector(getIDEViewMode); const ideViewMode = useSelector(getIDEViewMode);
return ( return (
@ -26,7 +24,7 @@ const EditorPane = () => {
// @ts-expect-error Fix this the next time the file is edited // @ts-expect-error Fix this the next time the file is edited
gap="spacing-2" gap="spacing-2"
height="100%" height="100%"
width={width} width={"100%"}
> >
{/** Entity Properties component is necessary to render {/** Entity Properties component is necessary to render
the Bindings popover in the context menu. the Bindings popover in the context menu.

View File

@ -11,24 +11,48 @@ import {
createMessage, createMessage,
} from "ee/constants/messages"; } from "ee/constants/messages";
import { setIdeEditorViewMode } from "actions/ideActions"; import { setIdeEditorViewMode } from "actions/ideActions";
import type { AppState } from "ee/reducers";
import { selectFeatureFlagCheck } from "ee/selectors/featureFlagsSelectors";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
export const ScreenModeToggle = () => { export const ScreenModeToggle = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const ideViewMode = useSelector(getIDEViewMode); const ideViewMode = useSelector(getIDEViewMode);
const isAnimatedIDEEnabled = useSelector((state: AppState) => {
return selectFeatureFlagCheck(
state,
FEATURE_FLAG.release_ide_animations_enabled,
);
});
const switchToFullScreen = useCallback(() => { const switchToFullScreen = useCallback(() => {
AnalyticsUtil.logEvent("EDITOR_MODE_CHANGE", { AnalyticsUtil.logEvent("EDITOR_MODE_CHANGE", {
to: EditorViewMode.FullScreen, to: EditorViewMode.FullScreen,
}); });
// Animating using https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
// this has limited availability right now
if ("startViewTransition" in document && isAnimatedIDEEnabled) {
document.startViewTransition(() => {
dispatch(setIdeEditorViewMode(EditorViewMode.FullScreen)); dispatch(setIdeEditorViewMode(EditorViewMode.FullScreen));
}, [dispatch]); });
} else {
dispatch(setIdeEditorViewMode(EditorViewMode.FullScreen));
}
}, [dispatch, isAnimatedIDEEnabled]);
const switchToSplitScreen = useCallback(() => { const switchToSplitScreen = useCallback(() => {
AnalyticsUtil.logEvent("EDITOR_MODE_CHANGE", { AnalyticsUtil.logEvent("EDITOR_MODE_CHANGE", {
to: EditorViewMode.SplitScreen, to: EditorViewMode.SplitScreen,
}); });
if ("startViewTransition" in document && isAnimatedIDEEnabled) {
document.startViewTransition(() => {
dispatch(setIdeEditorViewMode(EditorViewMode.SplitScreen)); dispatch(setIdeEditorViewMode(EditorViewMode.SplitScreen));
}, [dispatch]); });
} else {
dispatch(setIdeEditorViewMode(EditorViewMode.SplitScreen));
}
}, [dispatch, isAnimatedIDEEnabled]);
if (ideViewMode === EditorViewMode.SplitScreen) { if (ideViewMode === EditorViewMode.SplitScreen) {
return ( return (

View File

@ -0,0 +1,51 @@
import React from "react";
import { useGridLayoutTemplate } from "./hooks/useGridLayoutTemplate";
import EditorWrapperContainer from "pages/Editor/commons/EditorWrapperContainer";
import { AnimatedGridLayout, LayoutArea } from "components/AnimatedGridLayout";
import { useSelector } from "react-redux";
import BottomBar from "components/BottomBar";
import Sidebar from "../Sidebar";
import LeftPane from "../LeftPane";
import MainPane from "../MainPane";
import RightPane from "../RightPane";
import { Areas } from "./constants";
import ProtectedCallout from "../ProtectedCallout";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
function AnimatedLayout() {
const isProtectedMode = useSelector(protectedModeSelector);
const { areas, columns, rows } = useGridLayoutTemplate();
if (columns.length === 0) {
return null;
}
return (
<>
{isProtectedMode && <ProtectedCallout />}
<EditorWrapperContainer>
<AnimatedGridLayout
areas={areas}
columns={columns}
height="100%"
rows={rows}
width="100vw"
>
<LayoutArea name={Areas.Sidebar}>
<Sidebar />
</LayoutArea>
<LayoutArea name={Areas.Explorer}>
<LeftPane />
</LayoutArea>
<LayoutArea name={Areas.WidgetEditor}>
<MainPane id="app-body" />
</LayoutArea>
<LayoutArea name={Areas.PropertyPane}>
<RightPane />
</LayoutArea>
</AnimatedGridLayout>
</EditorWrapperContainer>
<BottomBar />
</>
);
}
export { AnimatedLayout };

View File

@ -0,0 +1,67 @@
import React from "react";
import { useSelector } from "react-redux";
import BottomBar from "components/BottomBar";
import EditorWrapperContainer from "../../commons/EditorWrapperContainer";
import Sidebar from "pages/Editor/IDE/Sidebar";
import LeftPane from "../LeftPane";
import MainPane from "../MainPane";
import RightPane from "../RightPane";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
import ProtectedCallout from "../ProtectedCallout";
import { useGridLayoutTemplate } from "./hooks/useGridLayoutTemplate";
import styled from "styled-components";
import { Areas } from "./constants";
const GridContainer = styled.div`
display: grid;
width: 100vw;
height: 100%;
`;
const LayoutContainer = styled.div<{ name: string }>`
position: relative;
grid-area: ${(props) => props.name};
`;
function UnanimatedLayout() {
const isProtectedMode = useSelector(protectedModeSelector);
const { areas, columns } = useGridLayoutTemplate();
const isSidebarVisible = columns[0] !== "0px";
return (
<>
{isProtectedMode && <ProtectedCallout />}
<EditorWrapperContainer>
<GridContainer
style={{
gridTemplateRows: "100%",
gridTemplateAreas: areas
.map((area) => `"${area.join(" ")}"`)
.join("\n"),
gridTemplateColumns: columns.join(" "),
}}
>
<LayoutContainer name={Areas.Sidebar}>
{isSidebarVisible ? <Sidebar /> : <div />}
</LayoutContainer>
<LayoutContainer name={Areas.Explorer}>
<LeftPane />
</LayoutContainer>
<LayoutContainer name={Areas.WidgetEditor}>
<MainPane id="app-body" />
</LayoutContainer>
<LayoutContainer name={Areas.PropertyPane}>
<RightPane />
</LayoutContainer>
</GridContainer>
</EditorWrapperContainer>
<BottomBar />
</>
);
}
const MemoUanimatedLayout = React.memo(UnanimatedLayout);
export { MemoUanimatedLayout as UnanimatedLayout };

View File

@ -0,0 +1,15 @@
import { APP_SIDEBAR_WIDTH } from "constants/AppConstants";
import type { AnimatedGridUnit } from "components/AnimatedGridLayout";
export const Areas = {
Sidebar: "Sidebar",
Explorer: "Explorer",
CodeEditor: "CodeEditor",
WidgetEditor: "WidgetEditor",
PropertyPane: "PropertyPane",
BottomBar: "BottomBar",
} as const;
export type Area = keyof typeof Areas;
export const SIDEBAR_WIDTH = (APP_SIDEBAR_WIDTH + "px") as AnimatedGridUnit;

View File

@ -0,0 +1,49 @@
import useWindowDimensions from "utils/hooks/useWindowDimensions";
import { useEffect, useState } from "react";
import {
APP_SIDEBAR_WIDTH,
DEFAULT_EXPLORER_PANE_WIDTH,
SPLIT_SCREEN_RATIO,
} from "constants/AppConstants";
import { useSelector } from "react-redux";
import { getIDEViewMode } from "selectors/ideSelectors";
import { getPropertyPaneWidth } from "selectors/propertyPaneSelectors";
import { EditorEntityTab, EditorViewMode } from "ee/entities/IDE/constants";
import { useCurrentEditorState } from "../../hooks";
import { previewModeSelector } from "selectors/editorSelectors";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
export const useEditorStateLeftPaneWidth = (): number => {
const [windowWidth] = useWindowDimensions();
const [width, setWidth] = useState(windowWidth - APP_SIDEBAR_WIDTH);
const editorMode = useSelector(getIDEViewMode);
const { segment } = useCurrentEditorState();
const propertyPaneWidth = useSelector(getPropertyPaneWidth);
const isPreviewMode = useSelector(previewModeSelector);
const isProtectedMode = useSelector(protectedModeSelector);
useEffect(
function updateWidth() {
if (isPreviewMode || isProtectedMode) {
setWidth(0);
} else if (segment !== EditorEntityTab.UI) {
if (editorMode === EditorViewMode.SplitScreen) {
setWidth(windowWidth * SPLIT_SCREEN_RATIO);
} else {
setWidth(windowWidth - APP_SIDEBAR_WIDTH);
}
} else {
setWidth(DEFAULT_EXPLORER_PANE_WIDTH);
}
},
[
editorMode,
segment,
propertyPaneWidth,
windowWidth,
isPreviewMode,
isProtectedMode,
],
);
return width;
};

View File

@ -0,0 +1,164 @@
import React from "react";
import type { AnimatedGridUnit } from "components/AnimatedGridLayout";
import { useSelector } from "react-redux";
import useWindowDimensions from "utils/hooks/useWindowDimensions";
import { useCurrentAppState, useCurrentEditorState } from "../../hooks";
import { getPropertyPaneWidth } from "selectors/propertyPaneSelectors";
import { previewModeSelector } from "selectors/editorSelectors";
import { getIDEViewMode } from "selectors/ideSelectors";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
import {
EditorEntityTab,
EditorState,
EditorViewMode,
} from "ee/entities/IDE/constants";
import {
APP_SETTINGS_PANE_WIDTH,
APP_SIDEBAR_WIDTH,
} from "constants/AppConstants";
import { useEditorStateLeftPaneWidth } from "./useEditorStateLeftPaneWidth";
import { type Area, Areas, SIDEBAR_WIDTH } from "../constants";
interface ReturnValue {
areas: Area[][];
rows: AnimatedGridUnit[];
columns: AnimatedGridUnit[];
}
function useGridLayoutTemplate(): ReturnValue {
const areas = React.useMemo(function initialiseAreas() {
return [
[Areas.Sidebar, Areas.Explorer, Areas.WidgetEditor, Areas.PropertyPane],
];
}, []);
const [columns, setColumns] = React.useState<AnimatedGridUnit[]>([]);
const [rows] = React.useState<AnimatedGridUnit[]>(["1fr"]);
const [windowWidth] = useWindowDimensions();
const editorStateLeftPaneWidth = useEditorStateLeftPaneWidth();
const PropertyPaneWidth = useSelector(getPropertyPaneWidth);
const { segment } = useCurrentEditorState();
const appState = useCurrentAppState();
const isPreviewMode = useSelector(previewModeSelector);
const editorMode = useSelector(getIDEViewMode);
const isProtectedMode = useSelector(protectedModeSelector);
React.useEffect(
function updateIDEColumns() {
switch (appState) {
case EditorState.DATA:
if (isPreviewMode || isProtectedMode) {
setColumns([
"0px",
"0px",
(windowWidth + "px") as AnimatedGridUnit,
"0px",
]);
} else {
setColumns([
SIDEBAR_WIDTH,
"300px",
(windowWidth - 300 - 50 + "px") as AnimatedGridUnit,
"0px",
]);
}
break;
case EditorState.SETTINGS:
if (isPreviewMode || isProtectedMode) {
setColumns([
"0px",
"0px",
(windowWidth + "px") as AnimatedGridUnit,
"0px",
]);
} else {
setColumns([
SIDEBAR_WIDTH,
(APP_SETTINGS_PANE_WIDTH + "px") as AnimatedGridUnit,
(windowWidth -
APP_SIDEBAR_WIDTH -
APP_SETTINGS_PANE_WIDTH +
"px") as AnimatedGridUnit,
"0px",
]);
}
break;
case EditorState.LIBRARIES:
if (isPreviewMode || isProtectedMode) {
setColumns([
"0px",
"0px",
(windowWidth + "px") as AnimatedGridUnit,
"0px",
]);
} else {
setColumns([
SIDEBAR_WIDTH,
"255px",
(windowWidth -
APP_SIDEBAR_WIDTH -
255 +
"px") as AnimatedGridUnit,
"0px",
]);
}
break;
case EditorState.EDITOR:
if (isPreviewMode || isProtectedMode) {
setColumns([
"0px",
(editorStateLeftPaneWidth + "px") as AnimatedGridUnit,
(windowWidth + "px") as AnimatedGridUnit,
"0px",
]);
} else if (segment !== EditorEntityTab.UI) {
if (editorMode === EditorViewMode.SplitScreen) {
setColumns([
SIDEBAR_WIDTH,
(editorStateLeftPaneWidth + "px") as AnimatedGridUnit,
(windowWidth -
APP_SIDEBAR_WIDTH -
editorStateLeftPaneWidth +
"px") as AnimatedGridUnit,
"0px",
]);
} else {
setColumns([
SIDEBAR_WIDTH,
(editorStateLeftPaneWidth + "px") as AnimatedGridUnit,
"0px",
"0px",
]);
}
} else {
setColumns([
SIDEBAR_WIDTH,
(editorStateLeftPaneWidth + "px") as AnimatedGridUnit,
(windowWidth -
APP_SIDEBAR_WIDTH -
editorStateLeftPaneWidth -
PropertyPaneWidth +
1 +
"px") as AnimatedGridUnit,
(PropertyPaneWidth + 1 + "px") as AnimatedGridUnit,
]);
}
}
},
[
appState,
isPreviewMode,
isProtectedMode,
editorStateLeftPaneWidth,
PropertyPaneWidth,
segment,
editorMode,
windowWidth,
],
);
return { areas, columns, rows };
}
export { useGridLayoutTemplate };

View File

@ -0,0 +1,2 @@
export { AnimatedLayout } from "./AnimatedLayout";
export { UnanimatedLayout } from "./UnanimatedLayout";

View File

@ -98,7 +98,7 @@ const DataSidePane = (props: DataSidePaneProps) => {
borderRight="1px solid var(--ads-v2-color-border)" borderRight="1px solid var(--ads-v2-color-border)"
flexDirection="column" flexDirection="column"
height="100%" height="100%"
width="300px" width="100%"
> >
<PaneHeader <PaneHeader
rightIcon={ rightIcon={

View File

@ -6,7 +6,6 @@ import { selectLibrariesForExplorer } from "ee/selectors/entitiesSelector";
import { animated, useTransition } from "react-spring"; import { animated, useTransition } from "react-spring";
import { LibraryEntity } from "pages/Editor/Explorer/Libraries"; import { LibraryEntity } from "pages/Editor/Explorer/Libraries";
import { Flex } from "@appsmith/ads"; import { Flex } from "@appsmith/ads";
import { DEFAULT_EXPLORER_PANE_WIDTH } from "constants/AppConstants";
const LibrarySidePane = () => { const LibrarySidePane = () => {
const libraries = useSelector(selectLibrariesForExplorer); const libraries = useSelector(selectLibrariesForExplorer);
@ -21,7 +20,7 @@ const LibrarySidePane = () => {
borderRight="1px solid var(--ads-v2-color-border)" borderRight="1px solid var(--ads-v2-color-border)"
flexDirection="column" flexDirection="column"
height="100%" height="100%"
width={DEFAULT_EXPLORER_PANE_WIDTH + "px"} width={"100%"}
> >
<PaneHeader <PaneHeader
rightIcon={<AddLibraryPopover />} rightIcon={<AddLibraryPopover />}

View File

@ -20,6 +20,7 @@ export const LeftPaneContainer = styled.div<{ showRightBorder?: boolean }>`
border-right: ${({ showRightBorder = true }) => border-right: ${({ showRightBorder = true }) =>
showRightBorder ? "1px solid var(--ads-v2-color-border)" : "none"}; showRightBorder ? "1px solid var(--ads-v2-color-border)" : "none"};
background: var(--ads-v2-color-bg); background: var(--ads-v2-color-bg);
overflow: hidden;
`; `;
const LeftPane = () => { const LeftPane = () => {

View File

@ -12,7 +12,7 @@ export const MainPane = (props: { id: string }) => {
return ( return (
<div <div
className="relative flex flex-col flex-1 overflow-auto z-2" className="relative flex flex-col flex-1 overflow-auto z-2 h-full"
data-testid="t--ide-main-pane" data-testid="t--ide-main-pane"
id={props.id} id={props.id}
> >

View File

@ -7,12 +7,15 @@ import IDE from ".";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import store from "store";
// TODO: Fix this the next time the file is edited // TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const getMockStore = (override: Record<string, any> = {}): any => { const getMockStore = (override: Record<string, any> = {}): any => {
const initialState = store.getState();
const slice = { const slice = {
ui: { ui: {
...initialState.ui,
applications: { applications: {
currentApplication: { currentApplication: {
gitApplicationMetadata: { gitApplicationMetadata: {
@ -32,6 +35,7 @@ const getMockStore = (override: Record<string, any> = {}): any => {
const mockStore = configureStore([]); const mockStore = configureStore([]);
const newSlice = merge(slice, override); const newSlice = merge(slice, override);
return mockStore({ return mockStore({
...initialState,
...newSlice, ...newSlice,
}); });
}; };

View File

@ -19,8 +19,10 @@ import {
createMessage, createMessage,
} from "ee/constants/messages"; } from "ee/constants/messages";
export const PROTECTED_CALLOUT_HEIGHT = 70;
const StyledCallout = styled(Callout)` const StyledCallout = styled(Callout)`
height: 70px; height: ${PROTECTED_CALLOUT_HEIGHT}px;
overflow-y: hidden; overflow-y: hidden;
`; `;

View File

@ -4,13 +4,10 @@ import {
EditorEntityTab, EditorEntityTab,
EditorEntityTabState, EditorEntityTabState,
EditorState, EditorState,
EditorViewMode,
} from "ee/entities/IDE/constants"; } from "ee/entities/IDE/constants";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity"; import { FocusEntity, identifyEntityFromPath } from "navigation/FocusEntity";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { getIDEViewMode } from "selectors/ideSelectors";
import { getPropertyPaneWidth } from "selectors/propertyPaneSelectors";
import history, { NavigationMethod } from "utils/history"; import history, { NavigationMethod } from "utils/history";
import { import {
builderURL, builderURL,
@ -20,11 +17,6 @@ import {
} from "ee/RouteBuilder"; } from "ee/RouteBuilder";
import { getCurrentFocusInfo } from "selectors/focusHistorySelectors"; import { getCurrentFocusInfo } from "selectors/focusHistorySelectors";
import { getCurrentGitBranch } from "selectors/gitSyncSelectors"; import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
import {
APP_SIDEBAR_WIDTH,
DEFAULT_EXPLORER_PANE_WIDTH,
SPLIT_SCREEN_RATIO,
} from "constants/AppConstants";
import { getIsAltFocusWidget, getWidgetSelectionBlock } from "selectors/ui"; import { getIsAltFocusWidget, getWidgetSelectionBlock } from "selectors/ui";
import { altFocusWidget, setWidgetSelectionBlock } from "actions/widgetActions"; import { altFocusWidget, setWidgetSelectionBlock } from "actions/widgetActions";
import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks"; import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks";
@ -36,7 +28,6 @@ import { closeJSActionTab } from "actions/jsActionActions";
import { closeQueryActionTab } from "actions/pluginActionActions"; import { closeQueryActionTab } from "actions/pluginActionActions";
import { getCurrentBasePageId } from "selectors/editorSelectors"; import { getCurrentBasePageId } from "selectors/editorSelectors";
import { getCurrentEntityInfo } from "../utils"; import { getCurrentEntityInfo } from "../utils";
import useWindowDimensions from "../../../utils/hooks/useWindowDimensions";
export const useCurrentAppState = () => { export const useCurrentAppState = () => {
const [appState, setAppState] = useState(EditorState.EDITOR); const [appState, setAppState] = useState(EditorState.EDITOR);
@ -75,32 +66,6 @@ export const useCurrentEditorState = () => {
}; };
}; };
export const useEditorPaneWidth = (): string => {
const [windowWidth] = useWindowDimensions();
const [width, setWidth] = useState(windowWidth - APP_SIDEBAR_WIDTH + "px");
const editorMode = useSelector(getIDEViewMode);
const { segment } = useCurrentEditorState();
const propertyPaneWidth = useSelector(getPropertyPaneWidth);
useEffect(() => {
if (editorMode === EditorViewMode.SplitScreen) {
if (segment !== EditorEntityTab.UI) {
// 1px is propertypane border width
setWidth(windowWidth * SPLIT_SCREEN_RATIO + "px");
} else {
setWidth(DEFAULT_EXPLORER_PANE_WIDTH + "px");
}
} else {
if (segment !== EditorEntityTab.UI) {
setWidth(windowWidth - APP_SIDEBAR_WIDTH + "px");
} else {
setWidth(DEFAULT_EXPLORER_PANE_WIDTH + "px");
}
}
}, [editorMode, segment, propertyPaneWidth, windowWidth]);
return width;
};
export const useSegmentNavigation = (): { export const useSegmentNavigation = (): {
onSegmentChange: (value: string) => void; onSegmentChange: (value: string) => void;
} => { } => {

View File

@ -1,61 +1,28 @@
import React from "react"; import React from "react";
import { selectFeatureFlagCheck } from "ee/selectors/featureFlagsSelectors";
import { AnimatedLayout, UnanimatedLayout } from "./Layout";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import type { AppState } from "ee/reducers";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import BottomBar from "components/BottomBar"; const checkAnimatedIDEFlagValue = (state: AppState) => {
import { return selectFeatureFlagCheck(
combinedPreviewModeSelector, state,
previewModeSelector, FEATURE_FLAG.release_ide_animations_enabled,
} from "selectors/editorSelectors"; );
import EditorWrapperContainer from "../commons/EditorWrapperContainer"; };
import Sidebar from "pages/Editor/IDE/Sidebar";
import LeftPane from "./LeftPane";
import MainPane from "./MainPane";
import RightPane from "./RightPane";
import classNames from "classnames";
import { tailwindLayers } from "constants/Layers";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
import ProtectedCallout from "./ProtectedCallout";
/** /**
* OldName: MainContainer * OldName: MainContainer
*/ */
function IDE() { function IDE() {
const isPreviewMode = useSelector(previewModeSelector); const isAnimatedIDEEnabled = useSelector(checkAnimatedIDEFlagValue);
const isCombinedPreviewMode = useSelector(combinedPreviewModeSelector); if (isAnimatedIDEEnabled) {
const isProtectedMode = useSelector(protectedModeSelector); return <AnimatedLayout />;
}
return ( return <UnanimatedLayout />;
<>
{isProtectedMode && <ProtectedCallout />}
<EditorWrapperContainer>
<div
className={classNames({
[`transition-transform transform duration-400 flex h-full ${tailwindLayers.entityExplorer}`]:
true,
relative: !isCombinedPreviewMode,
"-translate-x-full fixed": isCombinedPreviewMode,
})}
>
<Sidebar />
<LeftPane />
</div>
<MainPane id="app-body" />
<div
className={classNames({
[`transition-transform transform duration-400 h-full ${tailwindLayers.propertyPane}`]:
true,
relative: !isCombinedPreviewMode,
"translate-x-full fixed right-0": isCombinedPreviewMode,
})}
>
<RightPane />
</div>
</EditorWrapperContainer>
<BottomBar viewMode={isPreviewMode} />
</>
);
} }
IDE.displayName = "AppsmithIDE"; IDE.displayName = "AppIDE";
export default React.memo(IDE); export default React.memo(IDE);

View File

@ -6,6 +6,7 @@ import {
RUN_GUTTER_ID, RUN_GUTTER_ID,
} from "./constants"; } from "./constants";
import { thinScrollbar } from "constants/DefaultTheme"; import { thinScrollbar } from "constants/DefaultTheme";
import { IDE_HEADER_HEIGHT } from "IDE";
export const CodeEditorWithGutterStyles = css` export const CodeEditorWithGutterStyles = css`
.${RUN_GUTTER_ID} { .${RUN_GUTTER_ID} {
@ -31,7 +32,7 @@ export const CodeEditorWithGutterStyles = css`
`; `;
export const FormWrapper = styled.div` export const FormWrapper = styled.div`
height: ${({ theme }) => `calc(100vh - ${theme.smallHeaderHeight})`}; height: calc(100vh - ${IDE_HEADER_HEIGHT}px);
overflow: hidden; overflow: hidden;
.${JS_OBJECT_HOTKEYS_CLASSNAME} { .${JS_OBJECT_HOTKEYS_CLASSNAME} {
width: 100%; width: 100%;

View File

@ -19,7 +19,10 @@ import useInteractionAnalyticsEvent from "utils/hooks/useInteractionAnalyticsEve
import type { WidgetType } from "constants/WidgetConstants"; import type { WidgetType } from "constants/WidgetConstants";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants"; import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { getIsCurrentWidgetRecentlyAdded } from "selectors/propertyPaneSelectors"; import {
getIsCurrentWidgetRecentlyAdded,
getPropertyPaneWidth,
} from "selectors/propertyPaneSelectors";
interface PropertyPaneTitleProps { interface PropertyPaneTitleProps {
title: string; title: string;
@ -63,6 +66,7 @@ const PropertyPaneTitle = memo(function PropertyPaneTitle(
const isCurrentWidgetRecentlyAdded = useSelector( const isCurrentWidgetRecentlyAdded = useSelector(
getIsCurrentWidgetRecentlyAdded, getIsCurrentWidgetRecentlyAdded,
); );
const width = useSelector(getPropertyPaneWidth);
const { dispatchInteractionAnalyticsEvent, eventEmitterRef } = const { dispatchInteractionAnalyticsEvent, eventEmitterRef } =
useInteractionAnalyticsEvent<HTMLDivElement>(); useInteractionAnalyticsEvent<HTMLDivElement>();
@ -160,8 +164,9 @@ const PropertyPaneTitle = memo(function PropertyPaneTitle(
return props.widgetId || props.isPanelTitle ? ( return props.widgetId || props.isPanelTitle ? (
<div <div
className="flex items-center w-full px-4 py-3 space-x-1 fixed bg-white z-3" className="flex items-center px-4 py-3 space-x-1 fixed bg-white z-3"
ref={eventEmitterRef} ref={eventEmitterRef}
style={{ width: width + "px" }}
> >
{/* BACK BUTTON */} {/* BACK BUTTON */}
{props.isPanelTitle && ( {props.isPanelTitle && (

View File

@ -7,6 +7,13 @@ import { useSelector } from "react-redux";
import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors";
import { EditorState } from "ee/entities/IDE/constants"; import { EditorState } from "ee/entities/IDE/constants";
import { RenderModes } from "constants/WidgetConstants"; import { RenderModes } from "constants/WidgetConstants";
import styled from "styled-components";
import { IDE_HEADER_HEIGHT } from "IDE";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
const Container = styled.div`
height: calc(100vh - ${IDE_HEADER_HEIGHT} - ${BOTTOM_BAR_HEIGHT});
`;
/** /**
* WidgetEditorContainer * WidgetEditorContainer
@ -21,7 +28,7 @@ export const WidgetEditorContainer = (props: { children: ReactNode }) => {
appState === EditorState.SETTINGS && isNavigationSelectedInSettings; appState === EditorState.SETTINGS && isNavigationSelectedInSettings;
return ( return (
<EditorContextProvider renderMode={RenderModes.CANVAS}> <EditorContextProvider renderMode={RenderModes.CANVAS}>
<div className="relative flex flex-row h-full w-full overflow-hidden"> <Container className="relative flex flex-row h-full w-full overflow-hidden">
<div <div
className={classNames({ className={classNames({
"relative flex flex-col w-full overflow-hidden": true, "relative flex flex-col w-full overflow-hidden": true,
@ -31,7 +38,7 @@ export const WidgetEditorContainer = (props: { children: ReactNode }) => {
> >
{props.children} {props.children}
</div> </div>
</div> </Container>
</EditorContextProvider> </EditorContextProvider>
); );
}; };

View File

@ -1,8 +1,5 @@
import { setPropertyPaneWidthAction } from "actions/propertyPaneActions"; import React from "react";
import PropertyPaneSidebar from "components/editorComponents/PropertyPaneSidebar"; import PropertyPaneSidebar from "components/editorComponents/PropertyPaneSidebar";
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPropertyPaneWidth } from "selectors/propertyPaneSelectors";
import { CreateNewQueryModal } from "pages/Editor/IDE/RightPane/components/CreateNewQueryModal"; import { CreateNewQueryModal } from "pages/Editor/IDE/RightPane/components/CreateNewQueryModal";
/** /**
@ -12,32 +9,9 @@ import { CreateNewQueryModal } from "pages/Editor/IDE/RightPane/components/Creat
* It is used to handle the width of the property pane sidebar. * It is used to handle the width of the property pane sidebar.
*/ */
function PropertyPaneWrapper() { function PropertyPaneWrapper() {
const dispatch = useDispatch();
const propertyPaneWidth = useSelector(getPropertyPaneWidth);
/**
* on property pane sidebar drag end
*
* @return void
*/
const onRightSidebarDragEnd = useCallback(() => {
dispatch(setPropertyPaneWidthAction(propertyPaneWidth));
}, [propertyPaneWidth]);
/**
* on property pane sidebar width change
*/
const onRightSidebarWidthChange = useCallback((newWidth) => {
dispatch(setPropertyPaneWidthAction(newWidth));
}, []);
return ( return (
<> <>
<PropertyPaneSidebar <PropertyPaneSidebar />
onDragEnd={onRightSidebarDragEnd}
onWidthChange={onRightSidebarWidthChange}
width={propertyPaneWidth}
/>
<CreateNewQueryModal /> <CreateNewQueryModal />
</> </>
); );

View File

@ -1,6 +1,7 @@
import styled from "styled-components"; import styled from "styled-components";
import { Profile } from "pages/common/ProfileImage"; import { Profile } from "pages/common/ProfileImage";
import { getTypographyByKey } from "@appsmith/ads-old"; import { getTypographyByKey } from "@appsmith/ads-old";
import { IDE_HEADER_HEIGHT } from "IDE";
export const HeaderWrapper = styled.div` export const HeaderWrapper = styled.div`
width: 100%; width: 100%;
@ -10,7 +11,7 @@ export const HeaderWrapper = styled.div`
flex-direction: row; flex-direction: row;
box-shadow: none; box-shadow: none;
border-bottom: 1px solid var(--ads-v2-color-border); border-bottom: 1px solid var(--ads-v2-color-border);
height: ${(props) => props.theme.smallHeaderHeight}; height: ${IDE_HEADER_HEIGHT}px;
& .editable-application-name { & .editable-application-name {
${getTypographyByKey("h4")} ${getTypographyByKey("h4")}
color: ${(props) => props.theme.colors.header.appName}; color: ${(props) => props.theme.colors.header.appName};

View File

@ -1,4 +1,3 @@
import { APP_SETTINGS_PANE_WIDTH } from "constants/AppConstants";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import PaneHeader from "../IDE/LeftPane/PaneHeader"; import PaneHeader from "../IDE/LeftPane/PaneHeader";
@ -10,7 +9,7 @@ type EditorSettingsPaneContainerProps = React.PropsWithChildren<{
const SettingsPageWrapper = styled.div` const SettingsPageWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: ${APP_SETTINGS_PANE_WIDTH}px; width: 100%;
&:nth-child(2) { &:nth-child(2) {
height: 100%; height: 100%;

View File

@ -4,6 +4,9 @@ import classNames from "classnames";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { combinedPreviewModeSelector } from "../../../selectors/editorSelectors"; import { combinedPreviewModeSelector } from "../../../selectors/editorSelectors";
import { protectedModeSelector } from "selectors/gitSyncSelectors"; import { protectedModeSelector } from "selectors/gitSyncSelectors";
import { IDE_HEADER_HEIGHT } from "../../../IDE";
import { BOTTOM_BAR_HEIGHT } from "../../../components/BottomBar/constants";
import { PROTECTED_CALLOUT_HEIGHT } from "../IDE/ProtectedCallout";
interface EditorWrapperContainerProps { interface EditorWrapperContainerProps {
children: React.ReactNode; children: React.ReactNode;
@ -14,9 +17,9 @@ const Wrapper = styled.div<{
}>` }>`
display: flex; display: flex;
height: calc( height: calc(
100vh - ${(props) => props.theme.smallHeaderHeight} - 100vh - ${IDE_HEADER_HEIGHT}px - ${BOTTOM_BAR_HEIGHT}px -
${(props) => props.theme.bottomBarHeight} - ${(props) =>
${(props) => (props.isProtectedMode ? "70px" : "0px")} props.isProtectedMode ? PROTECTED_CALLOUT_HEIGHT + "px" : "0px"}
); );
background-color: ${(props) => props.theme.appBackground}; background-color: ${(props) => props.theme.appBackground};
`; `;

View File

@ -3,13 +3,14 @@ import styled from "styled-components";
import { snipingModeSelector } from "selectors/editorSelectors"; import { snipingModeSelector } from "selectors/editorSelectors";
import { retryPromise } from "utils/AppsmithUtils"; import { retryPromise } from "utils/AppsmithUtils";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { IDE_HEADER_HEIGHT } from "IDE";
const BindingBanner = styled.div` const BindingBanner = styled.div`
position: fixed; position: fixed;
width: 199px; width: 199px;
height: 36px; height: 36px;
left: 50%; left: 50%;
top: ${(props) => props.theme.smallHeaderHeight}; top: ${IDE_HEADER_HEIGHT}px;
transform: translate(-50%, 0); transform: translate(-50%, 0);
text-align: center; text-align: center;
background: var(--ads-v2-color-fg-information); background: var(--ads-v2-color-fg-information);

View File

@ -17,6 +17,7 @@ import { Colors } from "constants/Colors";
import ConflictInfo from "../components/ConflictInfo"; import ConflictInfo from "../components/ConflictInfo";
import { getCurrentAppGitMetaData } from "ee/selectors/applicationSelectors"; import { getCurrentAppGitMetaData } from "ee/selectors/applicationSelectors";
import { Button } from "@appsmith/ads"; import { Button } from "@appsmith/ads";
import { BOTTOM_BAR_HEIGHT } from "../../../../components/BottomBar/constants";
const StyledGitErrorPopup = styled.div` const StyledGitErrorPopup = styled.div`
& { & {
@ -31,7 +32,7 @@ const StyledGitErrorPopup = styled.div`
.${Classes.OVERLAY_CONTENT} { .${Classes.OVERLAY_CONTENT} {
overflow: hidden; overflow: hidden;
bottom: ${(props) => bottom: ${(props) =>
`calc(${props.theme.bottomBarHeight} + ${props.theme.spaces[3]}px)`}; `calc(${BOTTOM_BAR_HEIGHT}px + ${props.theme.spaces[3]}px)`};
left: ${(props) => props.theme.spaces[3]}px; left: ${(props) => props.theme.spaces[3]}px;
background-color: ${Colors.WHITE}; background-color: ${Colors.WHITE};
} }

View File

@ -50,6 +50,7 @@ import { PartialExportModal } from "components/editorComponents/PartialImportExp
import { PartialImportModal } from "components/editorComponents/PartialImportExport/PartialImportModal"; import { PartialImportModal } from "components/editorComponents/PartialImportExport/PartialImportModal";
import type { Page } from "entities/Page"; import type { Page } from "entities/Page";
import { AppCURLImportModal } from "ee/pages/Editor/CurlImport"; import { AppCURLImportModal } from "ee/pages/Editor/CurlImport";
import { IDE_HEADER_HEIGHT } from "IDE";
interface EditorProps { interface EditorProps {
currentApplicationId?: string; currentApplicationId?: string;
@ -175,7 +176,7 @@ class Editor extends Component<Props> {
if (!this.props.isEditorInitialized || this.props.loadingGuidedTour) { if (!this.props.isEditorInitialized || this.props.loadingGuidedTour) {
return ( return (
<CenteredWrapper <CenteredWrapper
style={{ height: `calc(100vh - ${theme.smallHeaderHeight})` }} style={{ height: `calc(100vh - ${IDE_HEADER_HEIGHT}px)` }}
> >
<Spinner size="lg" /> <Spinner size="lg" />
</CenteredWrapper> </CenteredWrapper>

View File

@ -10631,6 +10631,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/dom-view-transitions@npm:^1.0.5":
version: 1.0.5
resolution: "@types/dom-view-transitions@npm:1.0.5"
checksum: 4190915a3d4c63d4c590d5e64d14ca1d1140496a75adb5af64b0ef13e075cc7676bff926f5c5c60305c4f6d46965a196c119a7960cbe0ff40e95e17e82334c22
languageName: node
linkType: hard
"@types/dom4@npm:^2.0.1": "@types/dom4@npm:^2.0.1":
version: 2.0.2 version: 2.0.2
resolution: "@types/dom4@npm:2.0.2" resolution: "@types/dom4@npm:2.0.2"
@ -13214,6 +13221,7 @@ __metadata:
"@types/d3-geo": ^3.1.0 "@types/d3-geo": ^3.1.0
"@types/deep-diff": ^1.0.0 "@types/deep-diff": ^1.0.0
"@types/dom-mediacapture-record": ^1.0.11 "@types/dom-mediacapture-record": ^1.0.11
"@types/dom-view-transitions": ^1.0.5
"@types/downloadjs": ^1.4.2 "@types/downloadjs": ^1.4.2
"@types/google.maps": ^3.51.0 "@types/google.maps": ^3.51.0
"@types/jest": ^27.4.1 "@types/jest": ^27.4.1