diff --git a/app/client/cypress/e2e/Regression/ClientSide/ExplorerTests/Pin_spec.js b/app/client/cypress/e2e/Regression/ClientSide/ExplorerTests/Pin_spec.js index 314be437e3..0a8ab45e2e 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/ExplorerTests/Pin_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/ExplorerTests/Pin_spec.js @@ -4,6 +4,7 @@ import { locators, draggableWidgets, installer, + homePage, } from "../../../../support/Objects/ObjectsCore"; const ExplorerMenu = { @@ -98,6 +99,7 @@ describe("Entity explorer tests related to pinning and unpinning", function () { }); cy.get(locators._canvas).trigger("mousemove", 500, 400); agHelper.AssertElementVisible(entityExplorer._entityExplorer); + entityExplorer.PinUnpinEntityExplorer(false); }, ); @@ -122,6 +124,21 @@ describe("Entity explorer tests related to pinning and unpinning", function () { }); cy.get(locators._canvas).trigger("mousemove", 500, 400); agHelper.AssertElementVisible(entityExplorer._entityExplorer); + entityExplorer.PinUnpinEntityExplorer(false); }, ); + + it("5. Explorer should be visible by default on a new application", function () { + agHelper.AssertElementVisible(entityExplorer._entityExplorer); + entityExplorer.PinUnpinEntityExplorer(true); + agHelper.GetElement(locators._canvas).trigger("mousemove", 500, 100, { + force: true, + }); + agHelper + .GetElement(entityExplorer._entityExplorer) + .should("not.be.visible"); + homePage.NavigateToHome(); + homePage.CreateNewApplication(); + agHelper.AssertElementVisible(entityExplorer._entityExplorer); + }); }); diff --git a/app/client/src/pages/Editor/EditorHeader.test.tsx b/app/client/src/pages/Editor/EditorHeader.test.tsx new file mode 100644 index 0000000000..cc0e0373e3 --- /dev/null +++ b/app/client/src/pages/Editor/EditorHeader.test.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import EditorHeader from "./EditorHeader"; +import { render, screen } from "test/testUtils"; +import type { AppState } from "@appsmith/reducers"; + +function renderComponent(initialState?: Partial) { + render(, { + initialState, + }); +} + +describe("EditorHeader", () => { + it("Show sidebar nav button by default", () => { + renderComponent(); + const wrapper = screen.queryAllByTestId("sidebar-nav-button"); + expect(wrapper.length).toBe(1); + }); + it("sidebar nav button should be hidden when signposting is enabled", () => { + const initialState: any = { + entities: { + pageList: { + applicationId: "1", + }, + }, + ui: { + onBoarding: { + firstTimeUserOnboardingApplicationIds: ["1"], + }, + }, + }; + + renderComponent(initialState); + const wrapper = screen.queryAllByTestId("sidebar-nav-button"); + expect(wrapper.length).toBe(0); + }); +}); diff --git a/app/client/src/pages/Editor/EditorHeader.tsx b/app/client/src/pages/Editor/EditorHeader.tsx index 668363a319..1482e26fd5 100644 --- a/app/client/src/pages/Editor/EditorHeader.tsx +++ b/app/client/src/pages/Editor/EditorHeader.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useEffect, useState, lazy, Suspense } from "react"; import styled, { ThemeProvider } from "styled-components"; import classNames from "classnames"; -import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstants"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { APPLICATIONS_URL } from "constants/routes"; import AppInviteUsersForm from "pages/workspace/AppInviteUsersForm"; @@ -19,6 +18,7 @@ import { getAllUsers, getCurrentWorkspaceId, } from "@appsmith/selectors/workspaceSelectors"; +import type { ConnectedProps } from "react-redux"; import { connect, useDispatch, useSelector } from "react-redux"; import DeployLinkButtonDialog from "components/designSystems/appsmith/header/DeployLinkButton"; import { updateApplication } from "@appsmith/actions/applicationActions"; @@ -29,7 +29,6 @@ import { } from "@appsmith/selectors/applicationSelectors"; import EditorAppName from "./EditorAppName"; import { getCurrentUser } from "selectors/usersSelectors"; -import type { User } from "constants/userConstants"; import { EditInteractionKind, SavingState, @@ -59,7 +58,6 @@ import { EditorSaveIndicator } from "./EditorSaveIndicator"; import { retryPromise } from "utils/AppsmithUtils"; import { fetchUsersForWorkspace } from "@appsmith/actions/workspaceActions"; -import type { WorkspaceUser } from "@appsmith/constants/workspaceConstants"; import { getIsGitConnected } from "selectors/gitSyncSelectors"; import { @@ -92,6 +90,7 @@ import EmbedSnippetForm from "@appsmith/pages/Applications/EmbedSnippetTab"; import { getAppsmithConfigs } from "@appsmith/configs"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; import type { NavigationSetting } from "constants/AppConstants"; +import { getIsFirstTimeUserOnboardingEnabled } from "selectors/onboardingSelectors"; const { cloudHosting } = getAppsmithConfigs(); @@ -195,23 +194,6 @@ const SidebarNavButton = styled(Button)` } `; -type EditorHeaderProps = { - pageSaveError?: boolean; - pageName?: string; - pageId: string; - isPublishing: boolean; - publishedTime?: string; - workspaceId: string; - applicationId?: string; - currentApplication?: ApplicationPayload; - isSaving: boolean; - publishApplication: (appId: string) => void; - lastUpdatedTime?: number; - inOnboarding: boolean; - sharedUserList: WorkspaceUser[]; - currentUser?: User; -}; - const GlobalSearch = lazy(() => { return retryPromise( () => @@ -223,7 +205,10 @@ const GlobalSearch = lazy(() => { const theme = getTheme(ThemeMode.LIGHT); -export function EditorHeader(props: EditorHeaderProps) { +// Seperating redux props types from props being passed to the component from a parent +type PropsFromRedux = ConnectedProps; + +export function EditorHeader(props: PropsFromRedux) { const { applicationId, currentApplication, @@ -241,6 +226,7 @@ export function EditorHeader(props: EditorHeaderProps) { const isErroredSavingName = useSelector(getIsErroredSavingAppName); const applicationList = useSelector(getApplicationList); const isPreviewMode = useSelector(previewModeSelector); + const signpostingEnabled = useSelector(getIsFirstTimeUserOnboardingEnabled); const deployLink = useHref(viewerURL, { pageId }); const isAppSettingsPaneWithNavigationTabOpen = useSelector( getIsAppSettingsPaneWithNavigationTabOpen, @@ -341,59 +327,68 @@ export function EditorHeader(props: EditorHeaderProps) { data-testid="t--appsmith-editor-header" > - - - {!pinned - ? createMessage(LOCK_ENTITY_EXPLORER_MESSAGE) - : createMessage(CLOSE_ENTITY_EXPLORER_MESSAGE)} - - {modText()} / - - } - placement="bottomLeft" - > - + + {!pinned + ? createMessage(LOCK_ENTITY_EXPLORER_MESSAGE) + : createMessage(CLOSE_ENTITY_EXPLORER_MESSAGE)} + + {modText()} / + + } + placement="bottomLeft" > -
- - {pinned && ( +
- )} - {!pinned && ( - - )} -
- - + {pinned && ( + + )} + {!pinned && ( + + )} +
+
+
+ )} + - + Appsmith logo { return { @@ -531,3 +532,74 @@ describe("cmd + s hotkey", () => { }); }); }); + +describe("mod + / hotkey", () => { + it("Should dispatch pin/unpin explorer on mod + /", async () => { + const dispatchSpy = jest.spyOn(store, "dispatch"); + const component = render( + { + return { x: 0, y: 0 }; + }} + > +
+ , + ); + dispatchSpy.mockClear(); + + dispatchTestKeyboardEventWithCode( + component.container, + "keydown", + "/", + 191, + false, + true, + ); + + expect(dispatchSpy).toBeCalledTimes(2); + expect(dispatchSpy).toHaveBeenNthCalledWith( + 1, + setExplorerPinnedAction(false), + ); + }); + + it("Shouldn't dispatch pin/unpin explorer on mod + / when signposting is enabled", async () => { + const dispatchSpy = jest.spyOn(store, "dispatch"); + const state: any = { + entities: { + pageList: { + applicationId: "1", + }, + }, + ui: { + onBoarding: { + firstTimeUserOnboardingApplicationIds: ["1"], + }, + }, + }; + const component = render( + { + return { x: 0, y: 0 }; + }} + > +
+ , + { + initialState: state, + }, + ); + dispatchSpy.mockClear(); + + dispatchTestKeyboardEventWithCode( + component.container, + "keydown", + "/", + 191, + false, + true, + ); + + expect(dispatchSpy).toBeCalledTimes(0); + }); +}); diff --git a/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx b/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx index 62f6a1d131..d9f093bc78 100644 --- a/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx +++ b/app/client/src/pages/Editor/GlobalHotKeys/GlobalHotKeys.tsx @@ -47,6 +47,7 @@ import { toggleInstaller } from "actions/JSLibraryActions"; import { SelectionRequestType } from "sagas/WidgetSelectUtils"; import { toast } from "design-system"; import { showDebuggerFlag } from "selectors/debuggerSelectors"; +import { getIsFirstTimeUserOnboardingEnabled } from "selectors/onboardingSelectors"; type Props = { copySelectedWidget: () => void; @@ -72,6 +73,7 @@ type Props = { isPreviewMode: boolean; setPreviewModeAction: (shouldSet: boolean) => void; isExplorerPinned: boolean; + isSignpostingEnabled: boolean; setExplorerPinnedAction: (shouldPinned: boolean) => void; showCommitModal: () => void; getMousePosition: () => { x: number; y: number }; @@ -332,6 +334,7 @@ class GlobalHotKeys extends React.Component { /> { @@ -363,6 +366,7 @@ const mapStateToProps = (state: AppState) => ({ appMode: getAppMode(state), isPreviewMode: previewModeSelector(state), isExplorerPinned: getExplorerPinned(state), + isSignpostingEnabled: getIsFirstTimeUserOnboardingEnabled(state), }); const mapDispatchToProps = (dispatch: any) => { diff --git a/app/client/src/sagas/InitSagas.ts b/app/client/src/sagas/InitSagas.ts index 18a05ff3da..35f97db521 100644 --- a/app/client/src/sagas/InitSagas.ts +++ b/app/client/src/sagas/InitSagas.ts @@ -45,6 +45,10 @@ import { getSearchQuery, updateSlugNamesInURL } from "utils/helpers"; import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions"; import { safeCrashAppRequest } from "../actions/errorActions"; import { resetSnipingMode } from "actions/propertyPaneActions"; +import { + setExplorerActiveAction, + setExplorerPinnedAction, +} from "actions/explorerActions"; import { isEditorPath, isViewerPath, @@ -132,6 +136,8 @@ function* resetEditorSaga() { // previously yield put(setPreviewModeAction(false)); yield put(resetSnipingMode()); + yield put(setExplorerActiveAction(true)); + yield put(setExplorerPinnedAction(true)); yield put(resetEditorSuccess()); }