From 7fa5e3b1af4b9be4264277c84c8a2b372d9d752b Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Wed, 24 Feb 2021 19:17:37 +0530 Subject: [PATCH] Hide pages in publish mode (#3106) --- .../ClientSideTests/Pages/Hide_Page_spec.js | 21 ++++++++++++++ .../ClientSideTests/Pages/Page_Load_Spec.js | 1 + app/client/cypress/locators/Pages.json | 1 + app/client/src/actions/pageActions.tsx | 3 +- app/client/src/api/PageApi.tsx | 2 ++ .../src/constants/ReduxActionConstants.tsx | 1 + app/client/src/pages/AppViewer/index.tsx | 4 +-- .../AppViewer/viewer/AppViewerHeader.tsx | 6 ++-- .../Datasources/DatasourceStructure.tsx | 2 +- .../pages/Editor/Explorer/Entity/index.tsx | 15 +++++++++- .../pages/Editor/Explorer/ExplorerIcons.tsx | 4 +++ .../Editor/Explorer/Pages/PageContextMenu.tsx | 29 +++++++++++++++++-- .../Editor/Explorer/Pages/PageEntity.tsx | 16 ++++++---- .../entityReducers/pageListReducer.tsx | 3 +- app/client/src/sagas/ApplicationSagas.tsx | 4 ++- app/client/src/sagas/PageSagas.tsx | 1 + app/client/src/selectors/editorSelectors.tsx | 20 +++++++++++++ 17 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js new file mode 100644 index 0000000000..9843072c95 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js @@ -0,0 +1,21 @@ +const pages = require("../../../../locators/Pages.json"); + +const pageOne = "MyPage1"; +const pageTwo = "MyPage2"; + +describe("Hide page", function() { + it("Hide page", function() { + cy.Createpage(pageOne); + cy.Createpage(pageTwo); + + cy.GlobalSearchEntity(pageOne); + cy.xpath(pages.popover) + .last() + .click({ force: true }); + cy.get(pages.hidePage).click({ force: true }); + cy.ClearSearch(); + + cy.PublishtheApp(); + cy.get(".t--page-switch-tab").should("have.length", 2); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js index aac5b704bd..a5990dec47 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js @@ -7,6 +7,7 @@ describe("Page Load tests", () => { cy.get("div") .contains("Pages") .next() + .next() .click(); cy.get("h2").contains("Drag and drop a widget here"); cy.addDsl(dsl); diff --git a/app/client/cypress/locators/Pages.json b/app/client/cypress/locators/Pages.json index 206e7b874c..19214a6437 100644 --- a/app/client/cypress/locators/Pages.json +++ b/app/client/cypress/locators/Pages.json @@ -18,5 +18,6 @@ "editName": ".single-select >div:contains('Edit Name')", "clonePage": ".single-select >div:contains('Clone')", "deletePage": ".single-select >div:contains('Delete')", + "hidePage": ".single-select >div:contains('Hide')", "entityQuery": ".t--entity-name:contains('Queries')" } \ No newline at end of file diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index d7a65e8871..157cde260f 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -155,12 +155,13 @@ export const clonePageSuccess = ( }; }; -export const updatePage = (id: string, name: string) => { +export const updatePage = (id: string, name: string, isHidden: boolean) => { return { type: ReduxActionTypes.UPDATE_PAGE_INIT, payload: { id, name, + isHidden, }, }; }; diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index 30853bb479..c39051b9a3 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -66,6 +66,7 @@ export interface CreatePageRequest { export interface UpdatePageRequest { id: string; name: string; + isHidden?: boolean; } export interface CreatePageResponse extends ApiResponse { @@ -78,6 +79,7 @@ export interface FetchPageListResponse extends ApiResponse { id: string; name: string; isDefault: boolean; + isHidden?: boolean; layouts: Array; }>; organizationId: string; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index b2ebcb5599..2d23b79e19 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -503,6 +503,7 @@ export interface Page { pageId: string; isDefault: boolean; latest?: boolean; + isHidden?: boolean; } export interface ClonePageSuccessPayload { diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 76cd70b0be..cb07ed84a4 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -27,7 +27,7 @@ import { import { editorInitializer } from "utils/EditorUtils"; import * as Sentry from "@sentry/react"; import log from "loglevel"; -import { getPageList } from "selectors/editorSelectors"; +import { getViewModePageList } from "selectors/editorSelectors"; const SentryRoute = Sentry.withSentryRouting(Route); @@ -111,7 +111,7 @@ class AppViewer extends Component< const mapStateToProps = (state: AppState) => ({ isInitialized: getIsInitialized(state), - pages: getPageList(state), + pages: getViewModePageList(state), }); const mapDispatchToProps = (dispatch: any) => ({ diff --git a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx index f5f4c77857..dab501c1cd 100644 --- a/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx +++ b/app/client/src/pages/AppViewer/viewer/AppViewerHeader.tsx @@ -21,7 +21,7 @@ import { import { connect } from "react-redux"; import { AppState } from "reducers"; import { getEditorURL } from "selectors/appViewSelectors"; -import { getPageList } from "selectors/editorSelectors"; +import { getViewModePageList } from "selectors/editorSelectors"; import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent"; import AppInviteUsersForm from "pages/organization/AppInviteUsersForm"; import { getCurrentOrgId } from "selectors/organizationSelectors"; @@ -135,7 +135,7 @@ type AppViewerHeaderProps = { }; export const AppViewerHeader = (props: AppViewerHeaderProps) => { - const { currentApplicationDetails, pages, currentOrgId, currentUser } = props; + const { currentApplicationDetails, currentOrgId, currentUser, pages } = props; const isExampleApp = currentApplicationDetails?.appIsExample; const userPermissions = currentApplicationDetails?.userPermissions ?? []; const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION; @@ -249,7 +249,7 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => { }; const mapStateToProps = (state: AppState): AppViewerHeaderProps => ({ - pages: getPageList(state), + pages: getViewModePageList(state), url: getEditorURL(state), currentApplicationDetails: state.ui.applications.currentApplication, currentOrgId: getCurrentOrgId(state), diff --git a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx index 896b884d36..d85429c3b4 100644 --- a/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx +++ b/app/client/src/pages/Editor/Explorer/Datasources/DatasourceStructure.tsx @@ -29,7 +29,7 @@ const Wrapper = styled(EntityTogglesWrapper)` const StyledEntity = styled(Entity)` & > div { - grid-template-columns: 20px auto 1fr auto; + grid-template-columns: 20px auto 1fr auto auto; } `; diff --git a/app/client/src/pages/Editor/Explorer/Entity/index.tsx b/app/client/src/pages/Editor/Explorer/Entity/index.tsx index 5f48fe0640..fb00a146bb 100644 --- a/app/client/src/pages/Editor/Explorer/Entity/index.tsx +++ b/app/client/src/pages/Editor/Explorer/Entity/index.tsx @@ -19,6 +19,7 @@ import useClick from "utils/hooks/useClick"; export enum EntityClassNames { CONTEXT_MENU = "entity-context-menu", + RIGHT_ICON = "entity-right-icon", ADD_BUTTON = "t--entity-add-btn", NAME = "t--entity-name", COLLAPSE_TOGGLE = "t--entity-collapse-toggle", @@ -44,7 +45,7 @@ export const EntityItem = styled.div<{ width: 100%; display: inline-grid; grid-template-columns: ${(props) => - props.spaced ? "20px auto 1fr 30px" : "8px auto 1fr 30px"}; + props.spaced ? "20px auto 1fr auto 30px" : "8px auto 1fr auto 30px"}; border-radius: 0; color: ${(props) => (props.active ? Colors.WHITE : Colors.ALTO)}; cursor: pointer; @@ -71,6 +72,14 @@ export const EntityItem = styled.div<{ &&&&:hover .${EntityClassNames.CONTEXT_MENU} { visibility: visible; } + + & .${EntityClassNames.RIGHT_ICON} { + visibility: hidden; + padding-right: ${(props) => props.theme.spaces[2]}px; + } + &:hover .${EntityClassNames.RIGHT_ICON} { + visibility: visible; + } `; const IconWrapper = styled.span` @@ -83,6 +92,7 @@ export type EntityProps = { name: string; children?: ReactNode; icon: ReactNode; + rightIcon?: ReactNode; disabled?: boolean; action?: () => void; active?: boolean; @@ -172,6 +182,9 @@ export const Entity = forwardRef( updateEntityName={updateNameCallback} searchKeyword={props.searchKeyword} /> + + {props.rightIcon} + ); +export const hiddenPageIcon = ( + +); + const WidgetIcon = MenuIcons.WIDGETS_COLORED_ICON; export const widgetIcon = ( diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageContextMenu.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageContextMenu.tsx index 8b7408dfc5..a96950e779 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageContextMenu.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageContextMenu.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { ReactNode, useCallback } from "react"; import { useDispatch } from "react-redux"; import TreeDropdown, { TreeDropdownOption, @@ -9,7 +9,15 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { ContextMenuPopoverModifiers } from "../helpers"; import { initExplorerEntityNameEdit } from "actions/explorerActions"; -import { clonePageInit } from "actions/pageActions"; +import { clonePageInit, updatePage } from "actions/pageActions"; +import styled from "styled-components"; +import { Icon } from "@blueprintjs/core"; + +const CustomLabel = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; export const PageContextMenu = (props: { pageId: string; @@ -17,6 +25,7 @@ export const PageContextMenu = (props: { applicationId: string; className?: string; isDefaultPage: boolean; + isHidden: boolean; }) => { const dispatch = useDispatch(); @@ -58,6 +67,11 @@ export const PageContextMenu = (props: { props.pageId, ]); + const setHiddenField = useCallback( + () => dispatch(updatePage(props.pageId, props.name, !props.isHidden)), + [dispatch, props.pageId, props.name, props.isHidden], + ); + const optionTree: TreeDropdownOption[] = [ { value: "rename", @@ -69,6 +83,17 @@ export const PageContextMenu = (props: { onSelect: clonePage, label: "Clone", }, + { + value: "visibility", + onSelect: setHiddenField, + // Possibly support ReactNode in TreeOption + label: (( + + {props.isHidden ? "Show" : "Hide"} + + + ) as ReactNode) as string, + }, ]; if (!props.isDefaultPage) { optionTree.push({ diff --git a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx index 1ab3fcdf8e..5fecc1c0d1 100644 --- a/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx +++ b/app/client/src/pages/Editor/Explorer/Pages/PageEntity.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback } from "react"; +import React, { useCallback } from "react"; import { Page } from "constants/ReduxActionConstants"; import Entity, { EntityClassNames } from "../Entity"; import { useParams } from "react-router"; @@ -10,7 +10,7 @@ import PageContextMenu from "./PageContextMenu"; import { useSelector } from "react-redux"; import { AppState } from "reducers"; import { DataTreeAction } from "entities/DataTree/dataTreeFactory"; -import { homePageIcon, pageIcon } from "../ExplorerIcons"; +import { hiddenPageIcon, homePageIcon, pageIcon } from "../ExplorerIcons"; import { getPluginGroups } from "../Actions/helpers"; import ExplorerWidgetGroup from "../Widgets/WidgetGroup"; import { resolveAsSpaceChar } from "utils/helpers"; @@ -28,7 +28,8 @@ type ExplorerPageEntityProps = { searchKeyword?: string; showWidgetsSidebar: (pageId: string) => void; }; -export const ExplorerPageEntity = memo((props: ExplorerPageEntityProps) => { + +export const ExplorerPageEntity = (props: ExplorerPageEntityProps) => { const params = useParams(); const currentPageId = useSelector((state: AppState) => { @@ -50,10 +51,12 @@ export const ExplorerPageEntity = memo((props: ExplorerPageEntityProps) => { name={props.page.pageName} className={EntityClassNames.CONTEXT_MENU} isDefaultPage={props.page.isDefault} + isHidden={!!props.page.isHidden} /> ); const icon = props.page.isDefault ? homePageIcon : pageIcon; + const rightIcon = !!props.page.isHidden ? hiddenPageIcon : null; const addWidgetsFn = useCallback( () => props.showWidgetsSidebar(props.page.pageId), @@ -70,9 +73,12 @@ export const ExplorerPageEntity = memo((props: ExplorerPageEntityProps) => { entityId={props.page.pageId} active={isCurrentPage} isDefaultExpanded={isCurrentPage || !!props.searchKeyword} - updateEntityName={updatePage} + updateEntityName={(id, name) => + updatePage(id, name, !!props.page.isHidden) + } contextMenu={contextMenu} onNameEdit={resolveAsSpaceChar} + rightIcon={rightIcon} searchKeyword={props.searchKeyword} > { )} ); -}); +}; ExplorerPageEntity.displayName = "ExplorerPageEntity"; (ExplorerPageEntity as any).whyDidYouRender = { diff --git a/app/client/src/reducers/entityReducers/pageListReducer.tsx b/app/client/src/reducers/entityReducers/pageListReducer.tsx index d73fe65d82..75c20dfb07 100644 --- a/app/client/src/reducers/entityReducers/pageListReducer.tsx +++ b/app/client/src/reducers/entityReducers/pageListReducer.tsx @@ -93,12 +93,13 @@ export const pageListReducer = createReducer(initialState, { }), [ReduxActionTypes.UPDATE_PAGE_SUCCESS]: ( state: PageListReduxState, - action: ReduxAction<{ id: string; name: string }>, + action: ReduxAction<{ id: string; name: string; isHidden?: boolean }>, ) => { const pages = [...state.pages]; const updatedPage = pages.find((page) => page.pageId === action.payload.id); if (updatedPage) { updatedPage.pageName = action.payload.name; + updatedPage.isHidden = !!action.payload.isHidden; } return { ...state, pages }; }, diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index c0db880342..d86e996671 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -85,6 +85,7 @@ export function* publishApplicationSaga( const applicationId = yield select(getCurrentApplicationId); const currentPageId = yield select(getCurrentPageId); + let appicationViewPageUrl = getApplicationViewerPageURL( applicationId, currentPageId, @@ -99,8 +100,9 @@ export function* publishApplicationSaga( if (!windowReference || windowReference.closed) { windowReference = window.open(appicationViewPageUrl, "_blank"); } else { - windowReference.location.reload(); windowReference.focus(); + windowReference.location.href = + windowReference.location.origin + appicationViewPageUrl; } } } catch (error) { diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx index df62bd012e..4510d7bbb8 100644 --- a/app/client/src/sagas/PageSagas.tsx +++ b/app/client/src/sagas/PageSagas.tsx @@ -103,6 +103,7 @@ export function* fetchPageListSaga( pageName: page.name, pageId: page.id, isDefault: page.isDefault, + isHidden: !!page.isHidden, })); yield put({ type: ReduxActionTypes.SET_CURRENT_ORG_ID, diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index 086d818f60..ab0eaaf0a7 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -85,6 +85,26 @@ export const getCurrentPageId = (state: AppState) => export const getCurrentApplicationId = (state: AppState) => state.entities.pageList.applicationId; +export const getViewModePageList = createSelector( + getPageList, + getCurrentPageId, + (pageList: PageListReduxState["pages"], currentPageId?: string) => { + if (currentPageId) { + const currentPage = pageList.find( + (page) => page.pageId === currentPageId, + ); + if (!!currentPage?.isHidden) { + return [currentPage]; + } + + const visiblePages = pageList.filter((page) => !page.isHidden); + return visiblePages; + } + + return []; + }, +); + export const getCurrentPageName = createSelector( getPageListState, (pageList: PageListReduxState) =>