diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/EmbedSettings/EmbedSettings_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/EmbedSettings/EmbedSettings_spec.js index 7daca35551..0e63f46797 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/EmbedSettings/EmbedSettings_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/EmbedSettings/EmbedSettings_spec.js @@ -48,7 +48,9 @@ describe("Embed settings options", function () { .invoke("readText") .as("embeddedAppUrl"); cy.enablePublicAccess(); - cy.get(".t--back-to-home").click(); + cy.get( + `${appNavigationLocators.header} ${appNavigationLocators.backToAppsButton}`, + ).click(); homePage.CreateNewApplication(); ee.DragDropWidgetNVerify("iframewidget", 100, 100); cy.get("@embeddedAppUrl").then((url) => { diff --git a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/ViewMode_spec.js b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/ViewMode_spec.js index 7ad24232b9..aa7b3a1537 100644 --- a/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/ViewMode_spec.js +++ b/app/client/cypress/integration/Regression_TestSuite/ClientSideTests/OtherUIFeatures/ViewMode_spec.js @@ -1,4 +1,5 @@ const dsl = require("../../../../fixtures/previewMode.json"); +const appNavigationLocators = require("../../../../locators/AppNavigation.json"); const BASE_URL = Cypress.config().baseUrl; @@ -9,7 +10,9 @@ describe("Preview mode functionality", function () { it("1. on click of apps on header, it should take to application home page", function () { cy.PublishtheApp(); - cy.get(".t--back-to-home").click(); + cy.get( + `${appNavigationLocators.header} ${appNavigationLocators.backToAppsButton}`, + ).click(); cy.url().should("eq", BASE_URL + "applications"); }); }); diff --git a/app/client/cypress/locators/AppNavigation.json b/app/client/cypress/locators/AppNavigation.json index 5ee2ed4c8d..8166de610d 100644 --- a/app/client/cypress/locators/AppNavigation.json +++ b/app/client/cypress/locators/AppNavigation.json @@ -8,6 +8,7 @@ "editButton": ".t--back-to-editor", "forkButton": ".t--fork-app", "signButton": ".t--sign-in", + "backToAppsButton": ".t--app-viewer-back-to-apps-button", "userProfileDropdownButton": ".t--profile-menu-icon", "userProfileDropdownMenu": ".t--profile-menu", "modal": ".bp3-dialog", diff --git a/app/client/cypress/locators/HomePage.js b/app/client/cypress/locators/HomePage.js index 86be05ce30..c31eae7875 100644 --- a/app/client/cypress/locators/HomePage.js +++ b/app/client/cypress/locators/HomePage.js @@ -96,5 +96,5 @@ export default { optionsIcon: ".t--options-icon", reconnectDatasourceModal: ".reconnect-datasource-modal", importAppProgressWrapper: ".t-import-app-progress-wrapper", - backtoHome: ".t--back-to-home", + backtoHome: ".t--app-viewer-back-to-apps-button", }; diff --git a/app/client/cypress/support/Pages/DeployModeHelper.ts b/app/client/cypress/support/Pages/DeployModeHelper.ts index 2d1c27593f..954d0f941f 100644 --- a/app/client/cypress/support/Pages/DeployModeHelper.ts +++ b/app/client/cypress/support/Pages/DeployModeHelper.ts @@ -20,7 +20,8 @@ export class DeployMode { _clearDropdown = "button.select-button span.cancel-icon"; private _jsonFormMultiSelectOptions = (option: string) => `//div[@title='${option}']//input[@type='checkbox']/ancestor::div[@title='${option}']`; - private _backtoHome = ".t--back-to-home"; + private _backtoHome = + ".t--app-viewer-navigation-header .t--app-viewer-back-to-apps-button"; private _homeAppsmithImage = "a.t--appsmith-logo"; //refering PublishtheApp from command.js diff --git a/app/client/src/ce/actions/applicationActions.ts b/app/client/src/ce/actions/applicationActions.ts index d68641db13..db27259f85 100644 --- a/app/client/src/ce/actions/applicationActions.ts +++ b/app/client/src/ce/actions/applicationActions.ts @@ -101,6 +101,34 @@ export const updateApplicationNavigationSettingAction = ( }; }; +export const updateApplicationNavigationLogoAction = (logo: string) => { + return { + type: ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_INIT, + payload: logo, + }; +}; + +export const updateApplicationNavigationLogoSuccessAction = ( + logoAssetId: string, +) => { + return { + type: ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_SUCCESS, + payload: logoAssetId, + }; +}; + +export const deleteApplicationNavigationLogoAction = () => { + return { + type: ReduxActionTypes.DELETE_NAVIGATION_LOGO_INIT, + }; +}; + +export const deleteApplicationNavigationLogoSuccessAction = () => { + return { + type: ReduxActionTypes.DELETE_NAVIGATION_LOGO_SUCCESS, + }; +}; + export const publishApplication = (applicationId: string) => { return { type: ReduxActionTypes.PUBLISH_APPLICATION_INIT, diff --git a/app/client/src/ce/api/ApplicationApi.tsx b/app/client/src/ce/api/ApplicationApi.tsx index bd3d302cfd..641e0e0fec 100644 --- a/app/client/src/ce/api/ApplicationApi.tsx +++ b/app/client/src/ce/api/ApplicationApi.tsx @@ -221,6 +221,16 @@ export interface PageDefaultMeta { default: boolean; } +export interface UploadNavigationLogoRequest { + applicationId: string; + logo: File; + onSuccessCallback?: () => void; +} + +export interface DeleteNavigationLogoRequest { + applicationId: string; +} + export interface snapShotApplicationRequest { applicationId: string; } @@ -356,6 +366,35 @@ export class ApplicationApi extends Api { ); } + static uploadNavigationLogo( + request: UploadNavigationLogoRequest, + ): AxiosPromise { + const formData = new FormData(); + + if (request.logo) { + formData.append("file", request.logo); + } + + return Api.post( + ApplicationApi.baseURL + "/" + request.applicationId + "/logo", + formData, + null, + { + headers: { + "Content-Type": "multipart/form-data", + }, + }, + ); + } + + static deleteNavigationLogo( + request: DeleteNavigationLogoRequest, + ): AxiosPromise { + return Api.delete( + ApplicationApi.baseURL + "/" + request.applicationId + "/logo", + ); + } + static createApplicationSnapShot(request: snapShotApplicationRequest) { return Api.post(getSnapShotAPIRoute(request.applicationId)); } diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index ed34c58443..556f823476 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -796,6 +796,10 @@ export const ReduxActionTypes = { "PROCESS_AUTO_LAYOUT_DIMENSION_UPDATES", SET_GSHEET_TOKEN: "SET_GSHEET_TOKEN", FILE_PICKER_CALLBACK_ACTION: "FILE_PICKER_CALLBACK_ACTION", + UPLOAD_NAVIGATION_LOGO_INIT: "UPLOAD_NAVIGATION_LOGO_INIT", + UPLOAD_NAVIGATION_LOGO_SUCCESS: "UPLOAD_NAVIGATION_LOGO_SUCCESS", + DELETE_NAVIGATION_LOGO_INIT: "DELETE_NAVIGATION_LOGO_INIT", + DELETE_NAVIGATION_LOGO_SUCCESS: "DELETE_NAVIGATION_LOGO_SUCCESS", FETCH_GSHEET_SPREADSHEETS: "FETCH_GSHEET_SPREADSHEETS", FETCH_GSHEET_SPREADSHEETS_SUCCESS: "FETCH_GSHEET_SPREADSHEETS_SUCCESS", FETCH_GSHEET_SPREADSHEETS_FAILURE: "FETCH_GSHEET_SPREADSHEETS_FAILURE", @@ -984,6 +988,8 @@ export const ReduxActionErrorTypes = { INSTALL_LIBRARY_FAILED: "INSTALL_LIBRARY_FAILED", UNINSTALL_LIBRARY_FAILED: "UNINSTALL_LIBRARY_FAILED", FETCH_JS_LIBRARIES_FAILED: "FETCH_JS_LIBRARIES_FAILED", + UPLOAD_NAVIGATION_LOGO_ERROR: "UPLOAD_NAVIGATION_LOGO_ERROR", + DELETE_NAVIGATION_LOGO_ERROR: "DELETE_NAVIGATION_LOGO_ERROR", USER_PROFILE_PICTURE_UPLOAD_FAILED: "USER_PROFILE_PICTURE_UPLOAD_FAILED", USER_IMAGE_INVALID_FILE_CONTENT: "USER_IMAGE_INVALID_FILE_CONTENT", }; diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index 6cbebdf981..585c46e96b 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -208,6 +208,7 @@ export const EDIT_APP = () => `Edit App`; export const FORK_APP = () => `Fork App`; export const SIGN_IN = () => `Sign in`; export const SHARE_APP = () => `Share app`; +export const ALL_APPS = () => `All apps`; export const EDITOR_HEADER = { saving: () => "Saving", @@ -1574,17 +1575,21 @@ export const IN_APP_EMBED_SETTING = { export const APP_NAVIGATION_SETTING = { sectionHeader: () => "Navigation", sectionHeaderDesc: () => "Customize the navigation bar", - showNavbarLabel: () => "Show Navbar", + showNavbarLabel: () => "Show navbar", orientationLabel: () => "Orientation", navStyleLabel: () => "Variant", positionLabel: () => "Position", itemStyleLabel: () => "Item Style", colorStyleLabel: () => "Background color", logoLabel: () => "Logo", - logoConfigurationLabel: () => "Logo Configuration", - showSignInLabel: () => "Show Sign In", + logoConfigurationLabel: () => "Logo configuration", + showSignInLabel: () => "Show sign in", showSignInTooltip: () => "Toggle to show the sign-in button for users who are not logged in.", + logoUploadFormatError: () => `Uploaded file must be in .PNG or .JPG formats.`, + logoUploadSizeError: () => `Uploaded file must be less than 1MB.`, + showLogoLabel: () => "Show logo", + showApplicationTitleLabel: () => "Show application title", }; export const LOCK_SIDEBAR_MESSAGE = () => `Lock sidebar open`; diff --git a/app/client/src/ce/pages/AppViewer/BackToHomeButton.tsx b/app/client/src/ce/pages/AppViewer/BackToHomeButton.tsx deleted file mode 100644 index 15720f8afe..0000000000 --- a/app/client/src/ce/pages/AppViewer/BackToHomeButton.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useEffect } from "react"; -import { Link } from "react-router-dom"; -import { useSelector } from "react-redux"; -import AppsIcon from "remixicon-react/AppsLineIcon"; -import { getSelectedAppTheme } from "selectors/appThemingSelectors"; -import type { NavigationSetting } from "constants/AppConstants"; -import { NAVIGATION_SETTINGS } from "constants/AppConstants"; -import { - getMenuItemBackgroundColorOnHover, - getMenuItemTextColor, -} from "pages/AppViewer/utils"; -import styled from "styled-components"; -import { TooltipComponent } from "design-system-old"; -import classNames from "classnames"; - -type BackToHomeButtonProps = { - primaryColor: string; - navColorStyle: NavigationSetting["colorStyle"]; - forSidebar?: boolean; - isLogoVisible?: boolean; - setIsLogoVisible?: (isLogoVisible: boolean) => void; -}; - -const StyledAppIcon = styled(AppsIcon)< - BackToHomeButtonProps & { - borderRadius: string; - } ->` - color: ${({ navColorStyle, primaryColor }) => - getMenuItemTextColor(primaryColor, navColorStyle, true)}; - border-radius: ${({ borderRadius }) => borderRadius}; - transition: all 0.3s ease-in-out; - margin-top: ${({ forSidebar }) => (forSidebar ? " -3px" : "-2px")}; - width: 100%; -`; - -export const StyledLink = styled(Link)` - min-width: max-content; - - img { - width: 100%; - max-width: 4rem; - max-height: 1.5rem; - } - - &:hover { - svg { - background-color: ${({ navColorStyle, primaryColor }) => - getMenuItemBackgroundColorOnHover(primaryColor, navColorStyle)}; - - ${({ navColorStyle, primaryColor }) => { - if (navColorStyle !== NAVIGATION_SETTINGS.COLOR_STYLE.LIGHT) { - return `color: ${getMenuItemTextColor(primaryColor, navColorStyle)};`; - } - }}; - } - } -`; - -function BackToHomeButton(props: BackToHomeButtonProps) { - const { - forSidebar, - isLogoVisible, - navColorStyle, - primaryColor, - setIsLogoVisible, - } = props; - const selectedTheme = useSelector(getSelectedAppTheme); - - useEffect(() => { - if (setIsLogoVisible) { - setIsLogoVisible(false); - } - }, []); - - return ( - - - - - - ); -} - -export default BackToHomeButton; diff --git a/app/client/src/ce/pages/AppViewer/NavigationLogo.tsx b/app/client/src/ce/pages/AppViewer/NavigationLogo.tsx new file mode 100644 index 0000000000..228ed8b506 --- /dev/null +++ b/app/client/src/ce/pages/AppViewer/NavigationLogo.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { useSelector } from "react-redux"; +import type { NavigationSetting } from "constants/AppConstants"; +import { NAVIGATION_SETTINGS } from "constants/AppConstants"; +import styled from "styled-components"; +import classNames from "classnames"; +import { + getAppMode, + getCurrentApplication, +} from "@appsmith/selectors/applicationSelectors"; +import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstants"; +import { getViewModePageList } from "selectors/editorSelectors"; +import { useHref } from "pages/Editor/utils"; +import { APP_MODE } from "entities/App"; +import { builderURL, viewerURL } from "RouteBuilder"; +import { get } from "lodash"; +import { getAssetUrl } from "@appsmith/utils/airgapHelpers"; + +type NavigationLogoProps = { + logoConfiguration: NavigationSetting["logoConfiguration"]; +}; + +const StyledImage = styled.img` + max-width: 10rem; + max-height: 1.5rem; +`; + +function NavigationLogo(props: NavigationLogoProps) { + const { logoConfiguration } = props; + const currentApplicationDetails: ApplicationPayload | undefined = useSelector( + getCurrentApplication, + ); + const pages = useSelector(getViewModePageList); + const appMode = useSelector(getAppMode); + const defaultPage = pages.find((page) => page.isDefault) || pages[0]; + const pageUrl = useHref( + appMode === APP_MODE.PUBLISHED ? viewerURL : builderURL, + { + pageId: defaultPage?.pageId, + }, + ); + const logoAssetId = get( + currentApplicationDetails, + "applicationDetail.navigationSetting.logoAssetId", + "", + ); + + if ( + !logoAssetId?.length || + logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.APPLICATION_TITLE_ONLY || + logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.NO_LOGO_OR_APPLICATION_TITLE + ) { + return null; + } + + return ( + + + + ); +} + +export default NavigationLogo; diff --git a/app/client/src/ce/pages/Editor/NavigationSettings/LogoInput.tsx b/app/client/src/ce/pages/Editor/NavigationSettings/LogoInput.tsx new file mode 100644 index 0000000000..9aa55781f1 --- /dev/null +++ b/app/client/src/ce/pages/Editor/NavigationSettings/LogoInput.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { ImageInput } from "../../../../pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/ImageInput"; +import { TextType, Text, Icon } from "design-system-old"; +import { + createMessage, + APP_NAVIGATION_SETTING, +} from "@appsmith/constants/messages"; +import type { UpdateSetting } from "../../../../pages/Editor/AppSettingsPane/AppSettings/NavigationSettings"; +import { useDispatch, useSelector } from "react-redux"; +import { getCurrentApplicationId } from "selectors/editorSelectors"; +import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; +import type { NavigationSetting } from "constants/AppConstants"; +import { logoImageValidation } from "../../../../pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/utils"; +import { + getIsDeletingNavigationLogo, + getIsUploadingNavigationLogo, +} from "@appsmith/selectors/applicationSelectors"; + +export type ButtonGroupSettingProps = { + updateSetting: UpdateSetting; + navigationSetting: NavigationSetting; +}; + +const LogoInput = ({ navigationSetting }: ButtonGroupSettingProps) => { + const dispatch = useDispatch(); + const applicationId = useSelector(getCurrentApplicationId); + const isUploadingNavigationLogo = useSelector(getIsUploadingNavigationLogo); + const isDeletingNavigationLogo = useSelector(getIsDeletingNavigationLogo); + + const handleChange = (file: File) => { + dispatch({ + type: ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_INIT, + payload: { + applicationId: applicationId, + logo: file, + }, + }); + }; + + const handleDelete = () => { + dispatch({ + type: ReduxActionTypes.DELETE_NAVIGATION_LOGO_INIT, + payload: { + applicationId: applicationId, + }, + }); + }; + + return ( +
+
+ + {createMessage(APP_NAVIGATION_SETTING.logoLabel)} + + + {navigationSetting?.logoAssetId?.length ? ( + + ) : ( + "" + )} +
+
+ { + handleChange && handleChange(file); + }} + validate={logoImageValidation} + value={ + navigationSetting?.logoAssetId?.length + ? `/api/v1/assets/${navigationSetting?.logoAssetId}` + : "" + } + /> +
+
+ ); +}; + +export default LogoInput; diff --git a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx index b3c792eb7b..36bcc5db14 100644 --- a/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/ce/reducers/uiReducers/applicationsReducer.tsx @@ -26,6 +26,7 @@ import type { ConnectToGitResponse } from "actions/gitSyncActions"; import type { AppIconName } from "design-system-old"; import type { NavigationSetting } from "constants/AppConstants"; import { defaultNavigationSetting } from "constants/AppConstants"; +import produce from "immer"; export const initialState: ApplicationsReduxState = { isFetchingApplications: false, @@ -49,6 +50,8 @@ export const initialState: ApplicationsReduxState = { isAppSidebarPinned: true, isSavingNavigationSetting: false, isErrorSavingNavigationSetting: false, + isUploadingNavigationLogo: false, + isDeletingNavigationLogo: false, }; export const handlers = { @@ -402,12 +405,6 @@ export const handlers = { if (action.payload.name) { isSavingAppName = true; } - if (state.currentApplication && action.payload.applicationDetail) { - state.currentApplication.applicationDetail = { - ...state.currentApplication.applicationDetail, - ...action.payload.applicationDetail, - }; - } if (action.payload.applicationDetail?.navigationSetting) { isSavingNavigationSetting = true; @@ -417,11 +414,16 @@ export const handlers = { ...state, isSavingAppName, isErrorSavingAppName: false, - ...(action.payload.applicationDetail - ? { applicationDetail: action.payload.applicationDetail } - : {}), isSavingNavigationSetting, isErrorSavingNavigationSetting: false, + ...(action.payload.applicationDetail + ? { + applicationDetail: { + ...state.currentApplication?.applicationDetail, + ...action.payload.applicationDetail, + }, + } + : {}), }; }, [ReduxActionTypes.UPDATE_APPLICATION_SUCCESS]: ( @@ -459,6 +461,7 @@ export const handlers = { ...state, isSavingAppName: false, isErrorSavingAppName: true, + isSavingNavigationSetting: false, isErrorSavingNavigationSetting: true, }; }, @@ -592,6 +595,81 @@ export const handlers = { ...state, isAppSidebarPinned: action.payload, }), + [ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_INIT]: ( + state: ApplicationsReduxState, + ) => ({ + ...state, + isUploadingNavigationLogo: true, + }), + [ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_SUCCESS]: ( + state: ApplicationsReduxState, + action: ReduxAction, + ) => { + return produce(state, (draftState: ApplicationsReduxState) => { + draftState.isUploadingNavigationLogo = false; + + if ( + draftState?.currentApplication?.applicationDetail?.navigationSetting + ) { + draftState.currentApplication.applicationDetail.navigationSetting.logoAssetId = + action.payload; + } + }); + }, + [ReduxActionErrorTypes.UPLOAD_NAVIGATION_LOGO_ERROR]: ( + state: ApplicationsReduxState, + ) => { + return { + ...state, + isUploadingNavigationLogo: false, + }; + }, + [ReduxActionTypes.DELETE_NAVIGATION_LOGO_INIT]: ( + state: ApplicationsReduxState, + ) => ({ + ...state, + isDeletingNavigationLogo: true, + }), + [ReduxActionTypes.DELETE_NAVIGATION_LOGO_SUCCESS]: ( + state: ApplicationsReduxState, + ) => { + const updatedNavigationSetting = Object.assign( + {}, + state.currentApplication?.applicationDetail?.navigationSetting, + { + logoAssetId: "", + }, + ); + + const updatedApplicationDetail = Object.assign( + {}, + state.currentApplication?.applicationDetail, + { + navigationSetting: updatedNavigationSetting, + }, + ); + + const updatedCurrentApplication = Object.assign( + {}, + state.currentApplication, + { + applicationDetail: updatedApplicationDetail, + }, + ); + + return Object.assign({}, state, { + isDeletingNavigationLogo: false, + currentApplication: updatedCurrentApplication, + }); + }, + [ReduxActionErrorTypes.DELETE_NAVIGATION_LOGO_ERROR]: ( + state: ApplicationsReduxState, + ) => { + return { + ...state, + isDeletingNavigationLogo: false, + }; + }, }; const applicationsReducer = createReducer(initialState, handlers); @@ -624,6 +702,8 @@ export interface ApplicationsReduxState { isAppSidebarPinned: boolean; isSavingNavigationSetting: boolean; isErrorSavingNavigationSetting: boolean; + isUploadingNavigationLogo: boolean; + isDeletingNavigationLogo: boolean; } export interface Application { diff --git a/app/client/src/ce/sagas/ApplicationSagas.tsx b/app/client/src/ce/sagas/ApplicationSagas.tsx index 04630f4422..be1ea043c2 100644 --- a/app/client/src/ce/sagas/ApplicationSagas.tsx +++ b/app/client/src/ce/sagas/ApplicationSagas.tsx @@ -15,6 +15,7 @@ import type { CreateApplicationRequest, CreateApplicationResponse, DeleteApplicationRequest, + DeleteNavigationLogoRequest, DuplicateApplicationRequest, FetchApplicationPayload, FetchApplicationResponse, @@ -27,6 +28,7 @@ import type { SetDefaultPageRequest, UpdateApplicationRequest, UpdateApplicationResponse, + UploadNavigationLogoRequest, WorkspaceApplicationObject, } from "@appsmith/api/ApplicationApi"; import ApplicationApi from "@appsmith/api/ApplicationApi"; @@ -39,6 +41,7 @@ import history from "utils/history"; import type { AppState } from "@appsmith/reducers"; import { ApplicationVersion, + deleteApplicationNavigationLogoSuccessAction, fetchApplication, getAllApplications, importApplicationSuccess, @@ -49,6 +52,7 @@ import { setPageIdForImport, setWorkspaceIdForImport, showReconnectDatasourceModal, + updateApplicationNavigationLogoSuccessAction, updateApplicationNavigationSettingAction, updateCurrentApplicationEmbedSetting, updateCurrentApplicationIcon, @@ -113,6 +117,10 @@ import { getCurrentUser } from "selectors/usersSelectors"; import { ERROR_CODES } from "@appsmith/constants/ApiConstants"; import { safeCrashAppRequest } from "actions/errorActions"; import { isAirgapped } from "@appsmith/utils/airgapHelpers"; +import { + defaultNavigationSetting, + keysOfNavigationSetting, +} from "constants/AppConstants"; import { setAllEntityCollapsibleStates } from "../../actions/editorContextActions"; export const getDefaultPageId = ( @@ -913,3 +921,111 @@ export function* initDatasourceConnectionDuringImport( yield put(initDatasourceConnectionDuringImportSuccess()); } + +export function* uploadNavigationLogoSaga( + action: ReduxAction, +) { + try { + const request: UploadNavigationLogoRequest = action.payload; + const response: ApiResponse = yield call( + ApplicationApi.uploadNavigationLogo, + request, + ); + const isValidResponse: boolean = yield validateResponse(response); + + if (isValidResponse) { + if (request.logo) { + if ( + request.logo && + response.data.applicationDetail?.navigationSetting?.logoAssetId + ) { + yield put( + updateApplicationNavigationLogoSuccessAction( + response.data.applicationDetail.navigationSetting.logoAssetId, + ), + ); + + /** + * When the user creates a new application and they upload logo without + * interacting with any other navigation settings first, we get only + * navigationSetting = { logoAssetId: } in the API response. + * + * Therefore, we need to handle this case by hitting the update application + * API and store the default navigation settings as well alongside + * the logoAssetId. + */ + const navigationSettingKeys = Object.keys( + response.data.applicationDetail?.navigationSetting, + ); + if ( + navigationSettingKeys?.length === 1 && + navigationSettingKeys?.[0] === keysOfNavigationSetting.logoAssetId + ) { + const newUpdateApplicationRequestWithDefaultNavigationSettings = { + ...response.data, + applicationDetail: { + ...response.data.applicationDetail, + navigationSetting: { + ...defaultNavigationSetting, + ...response.data.applicationDetail.navigationSetting, + }, + }, + }; + + const updateApplicationResponse: ApiResponse = + yield call( + ApplicationApi.updateApplication, + newUpdateApplicationRequestWithDefaultNavigationSettings, + ); + + if (updateApplicationResponse?.data) { + yield put({ + type: ReduxActionTypes.UPDATE_APPLICATION_SUCCESS, + payload: updateApplicationResponse.data, + }); + + if ( + newUpdateApplicationRequestWithDefaultNavigationSettings + .applicationDetail?.navigationSetting && + updateApplicationResponse.data.applicationDetail + ?.navigationSetting + ) { + yield put( + updateApplicationNavigationSettingAction( + updateApplicationResponse.data.applicationDetail + .navigationSetting, + ), + ); + } + } + } + } + } + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.UPLOAD_NAVIGATION_LOGO_ERROR, + payload: { + error, + }, + }); + } +} + +export function* deleteNavigationLogoSaga( + action: ReduxAction, +) { + try { + const request: DeleteNavigationLogoRequest = action.payload; + + yield call(ApplicationApi.deleteNavigationLogo, request); + yield put(deleteApplicationNavigationLogoSuccessAction()); + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.DELETE_NAVIGATION_LOGO_ERROR, + payload: { + error, + }, + }); + } +} diff --git a/app/client/src/ce/selectors/applicationSelectors.tsx b/app/client/src/ce/selectors/applicationSelectors.tsx index 516cf19077..311009a2c1 100644 --- a/app/client/src/ce/selectors/applicationSelectors.tsx +++ b/app/client/src/ce/selectors/applicationSelectors.tsx @@ -216,6 +216,14 @@ export const getSidebarWidth = (state: AppState) => { return 0; }; +export const getIsUploadingNavigationLogo = (state: AppState) => { + return state.ui.applications.isUploadingNavigationLogo; +}; + +export const getIsDeletingNavigationLogo = (state: AppState) => { + return state.ui.applications.isDeletingNavigationLogo; +}; + const DEFAULT_EVALUATION_VERSION = 2; export const selectEvaluationVersion = (state: AppState) => diff --git a/app/client/src/constants/AppConstants.ts b/app/client/src/constants/AppConstants.ts index 22af3e06ad..7c671d558e 100644 --- a/app/client/src/constants/AppConstants.ts +++ b/app/client/src/constants/AppConstants.ts @@ -68,6 +68,7 @@ export const NAVIGATION_SETTINGS = { LIGHT: "light", THEME: "theme", }, + LOGO_ASSET_ID: "", LOGO_CONFIGURATION: { LOGO_AND_APPLICATION_TITLE: "logoAndApplicationTitle", LOGO_ONLY: "logoOnly", @@ -84,6 +85,7 @@ export type NavigationSetting = { position: (typeof NAVIGATION_SETTINGS.POSITION)[keyof typeof NAVIGATION_SETTINGS.POSITION]; itemStyle: (typeof NAVIGATION_SETTINGS.ITEM_STYLE)[keyof typeof NAVIGATION_SETTINGS.ITEM_STYLE]; colorStyle: (typeof NAVIGATION_SETTINGS.COLOR_STYLE)[keyof typeof NAVIGATION_SETTINGS.COLOR_STYLE]; + logoAssetId: string; logoConfiguration: (typeof NAVIGATION_SETTINGS.LOGO_CONFIGURATION)[keyof typeof NAVIGATION_SETTINGS.LOGO_CONFIGURATION]; }; @@ -100,6 +102,7 @@ export const keysOfNavigationSetting = { position: "position", itemStyle: "itemStyle", colorStyle: "colorStyle", + logoAssetId: "logoAssetId", logoConfiguration: "logoConfiguration", }; @@ -111,6 +114,7 @@ export const defaultNavigationSetting = { position: NAVIGATION_SETTINGS.POSITION.STATIC, itemStyle: NAVIGATION_SETTINGS.ITEM_STYLE.TEXT, colorStyle: NAVIGATION_SETTINGS.COLOR_STYLE.LIGHT, + logoAssetId: NAVIGATION_SETTINGS.LOGO_ASSET_ID, logoConfiguration: NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE, }; @@ -120,7 +124,7 @@ export const SIDEBAR_WIDTH = { MINIMAL: 66, }; -export const APPLICATION_TITLE_MAX_WIDTH = 224; +export const APPLICATION_TITLE_MAX_WIDTH = 192; export const APPLICATION_TITLE_MAX_WIDTH_MOBILE = 150; //all values are in milliseconds export const REQUEST_IDLE_CALLBACK_TIMEOUT = { diff --git a/app/client/src/ee/pages/AppViewer/BackToHomeButton.tsx b/app/client/src/ee/pages/AppViewer/BackToHomeButton.tsx deleted file mode 100644 index 647c96dda4..0000000000 --- a/app/client/src/ee/pages/AppViewer/BackToHomeButton.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export * from "ce/pages/AppViewer/BackToHomeButton"; -import { default as CE_BackToHomeButton } from "ce/pages/AppViewer/BackToHomeButton"; -export default CE_BackToHomeButton; diff --git a/app/client/src/ee/pages/AppViewer/NavigationLogo.tsx b/app/client/src/ee/pages/AppViewer/NavigationLogo.tsx new file mode 100644 index 0000000000..7bad20dd53 --- /dev/null +++ b/app/client/src/ee/pages/AppViewer/NavigationLogo.tsx @@ -0,0 +1,3 @@ +export * from "ce/pages/AppViewer/NavigationLogo"; +import { default as CE_NavigationLogo } from "ce/pages/AppViewer/NavigationLogo"; +export default CE_NavigationLogo; diff --git a/app/client/src/ee/pages/Editor/NavigationSettings/LogoInput.tsx b/app/client/src/ee/pages/Editor/NavigationSettings/LogoInput.tsx new file mode 100644 index 0000000000..867845dadb --- /dev/null +++ b/app/client/src/ee/pages/Editor/NavigationSettings/LogoInput.tsx @@ -0,0 +1,3 @@ +export * from "ce/pages/Editor/NavigationSettings/LogoInput"; +import { default as CE_LogoInput } from "ce/pages/Editor/NavigationSettings/LogoInput"; +export default CE_LogoInput; diff --git a/app/client/src/ee/sagas/ApplicationSagas.tsx b/app/client/src/ee/sagas/ApplicationSagas.tsx index 1cd2c7bf79..01fb3df1ed 100644 --- a/app/client/src/ee/sagas/ApplicationSagas.tsx +++ b/app/client/src/ee/sagas/ApplicationSagas.tsx @@ -16,6 +16,8 @@ import { initDatasourceConnectionDuringImport, showReconnectDatasourcesModalSaga, fetchUnconfiguredDatasourceList, + uploadNavigationLogoSaga, + deleteNavigationLogoSaga, } from "ce/sagas/ApplicationSagas"; import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants"; import { all, takeLatest } from "redux-saga/effects"; @@ -49,6 +51,14 @@ export default function* applicationSagas() { duplicateApplicationSaga, ), takeLatest(ReduxActionTypes.IMPORT_APPLICATION_INIT, importApplicationSaga), + takeLatest( + ReduxActionTypes.UPLOAD_NAVIGATION_LOGO_INIT, + uploadNavigationLogoSaga, + ), + takeLatest( + ReduxActionTypes.DELETE_NAVIGATION_LOGO_INIT, + deleteNavigationLogoSaga, + ), takeLatest(ReduxActionTypes.FETCH_RELEASES, fetchReleases), takeLatest( ReduxActionTypes.INIT_DATASOURCE_CONNECTION_DURING_IMPORT_REQUEST, diff --git a/app/client/src/pages/AppViewer/AppViewerButton.tsx b/app/client/src/pages/AppViewer/AppViewerButton.tsx index 6a1db6c823..7a6fb7cf82 100644 --- a/app/client/src/pages/AppViewer/AppViewerButton.tsx +++ b/app/client/src/pages/AppViewer/AppViewerButton.tsx @@ -97,6 +97,10 @@ const StyledButton = styled(Button)<{ color: ${styles.color} !important; } + svg path { + fill: ${styles.color} !important; + } + &:hover, &:active, &:focus { @@ -105,6 +109,10 @@ const StyledButton = styled(Button)<{ span { color: ${styles.color} !important; } + + svg path { + fill: ${styles.color} !important; + } } `; diff --git a/app/client/src/pages/AppViewer/Navigation/Sidebar.styled.tsx b/app/client/src/pages/AppViewer/Navigation/Sidebar.styled.tsx index be2bf5fc93..ab8c2bcb30 100644 --- a/app/client/src/pages/AppViewer/Navigation/Sidebar.styled.tsx +++ b/app/client/src/pages/AppViewer/Navigation/Sidebar.styled.tsx @@ -43,12 +43,12 @@ export const StyledMenuContainer = styled.div<{ primaryColor: string; navColorStyle: NavigationSetting["colorStyle"]; }>` - margin: 16px 0 0 0; + margin: 20px 0 0 0; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; - padding: 0 8px; + padding: 0 16px 12px; flex-grow: 1; padding-bottom: 12px; @@ -107,7 +107,7 @@ export const StyledCtaContainer = styled.div` `; export const StyledHeader = styled.div` - padding: 16px 8px 0px; + padding: 20px 20px 0px; display: flex; align-items: flex-start; justify-content: space-between; diff --git a/app/client/src/pages/AppViewer/Navigation/Sidebar.tsx b/app/client/src/pages/AppViewer/Navigation/Sidebar.tsx index d5bd0c6094..536d007dc1 100644 --- a/app/client/src/pages/AppViewer/Navigation/Sidebar.tsx +++ b/app/client/src/pages/AppViewer/Navigation/Sidebar.tsx @@ -19,7 +19,6 @@ import { previewModeSelector, } from "selectors/editorSelectors"; import type { User } from "constants/userConstants"; -import { ANONYMOUS_USERNAME } from "constants/userConstants"; import SidebarProfileComponent from "./components/SidebarProfileComponent"; import CollapseButton from "./components/CollapseButton"; import classNames from "classnames"; @@ -35,8 +34,9 @@ import { } from "./Sidebar.styled"; import { getCurrentThemeDetails } from "selectors/themeSelectors"; import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettingsPaneSelectors"; -import BackToHomeButton from "@appsmith/pages/AppViewer/BackToHomeButton"; +import NavigationLogo from "@appsmith/pages/AppViewer/NavigationLogo"; import MenuItemContainer from "./components/MenuItemContainer"; +import BackToAppsButton from "./components/BackToAppsButton"; type SidebarProps = { currentApplicationDetails?: ApplicationPayload; @@ -58,6 +58,10 @@ export function Sidebar(props: SidebarProps) { const isMinimal = currentApplicationDetails?.applicationDetail?.navigationSetting ?.navStyle === NAVIGATION_SETTINGS.NAV_STYLE.MINIMAL; + const logoConfiguration = + currentApplicationDetails?.applicationDetail?.navigationSetting + ?.logoConfiguration || + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE; const primaryColor = get( selectedTheme, "properties.colors.primaryColor", @@ -82,7 +86,6 @@ export function Sidebar(props: SidebarProps) { const isAppSettingsPaneWithNavigationTabOpen = useSelector( getIsAppSettingsPaneWithNavigationTabOpen, ); - const [isLogoVisible, setIsLogoVisible] = useState(false); useEffect(() => { setQuery(window.location.search); @@ -127,7 +130,8 @@ export function Sidebar(props: SidebarProps) { if (isPreviewMode) { prefix += theme.smallHeaderHeight; } else if (isAppSettingsPaneWithNavigationTabOpen) { - prefix += `${theme.smallHeaderHeight} - ${theme.bottomBarHeight}`; + // We deduct 64px as well since it is the margin coming from "m-8" class from tailwind + prefix += `${theme.smallHeaderHeight} - ${theme.bottomBarHeight} - 64px`; } else { prefix += "0px"; } @@ -148,31 +152,24 @@ export function Sidebar(props: SidebarProps) { sidebarHeight={calculateSidebarHeight()} > -
- {currentUser?.username !== ANONYMOUS_USERNAME && ( - - )} +
+ - {!isMinimal && ( - - )} + {!isMinimal && + (logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION + .LOGO_AND_APPLICATION_TITLE || + logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION + .APPLICATION_TITLE_ONLY) && ( + + )}
{!isMinimal && ( @@ -231,6 +228,12 @@ export function Sidebar(props: SidebarProps) { primaryColor={primaryColor} url={editorURL} /> + + )} diff --git a/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.styled.tsx b/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.styled.tsx index 10ae863b5a..5c9e4f68e7 100644 --- a/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.styled.tsx +++ b/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.styled.tsx @@ -12,14 +12,18 @@ export const StyledApplicationName = styled.div<{ primaryColor: string; navColorStyle: NavigationSetting["colorStyle"]; navStyle: NavigationSetting["navStyle"]; - forSidebar?: boolean; isMobile: boolean; + forSidebar?: boolean; + fontWeight: "regular" | "bold"; }>` color: ${({ navColorStyle, primaryColor }) => getApplicationNameTextColor(primaryColor, navColorStyle)}; font-size: ${THEMEING_TEXT_SIZES.base}; + font-weight: ${({ fontWeight }) => + fontWeight === "regular" ? "400" : "600"}; + ${({ forSidebar }) => (forSidebar ? "margin-left: 6px;" : "")}; - ${({ forSidebar, isMobile, navStyle }) => { + ${({ isMobile, navStyle }) => { if (isMobile) { return `max-width: ${APPLICATION_TITLE_MAX_WIDTH_MOBILE}px;`; } else if ( @@ -27,8 +31,6 @@ export const StyledApplicationName = styled.div<{ !isMobile ) { return `max-width: 500px;`; - } else if (forSidebar) { - return `max-width: ${APPLICATION_TITLE_MAX_WIDTH - 40}px;`; } else { return `max-width: ${APPLICATION_TITLE_MAX_WIDTH}px;`; } diff --git a/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.tsx b/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.tsx index 5fb70c9560..d190fb63c8 100644 --- a/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.tsx +++ b/app/client/src/pages/AppViewer/Navigation/components/ApplicationName.tsx @@ -12,10 +12,18 @@ type ApplicationNameProps = { navStyle: NavigationSetting["navStyle"]; primaryColor: string; forSidebar?: boolean; + fontWeight?: "regular" | "bold"; }; const ApplicationName = (props: ApplicationNameProps) => { - const { appName, forSidebar, navColorStyle, navStyle, primaryColor } = props; + const { + appName, + fontWeight, + forSidebar, + navColorStyle, + navStyle, + primaryColor, + } = props; const applicationNameRef = useRef(null); const [ellipsisActive, setEllipsisActive] = useState(false); const isMobile = useIsMobileDevice(); @@ -42,6 +50,7 @@ const ApplicationName = (props: ApplicationNameProps) => { > ` + color: ${({ navColorStyle, primaryColor }) => + getMenuItemTextColor(primaryColor, navColorStyle, true)}; + width: 16px; + height: 16px; +`; + +const BackToAppsButton = (props: BackToAppsButtonProps) => { + const { currentApplicationDetails, insideSidebar, isMinimal } = props; + const selectedTheme = useSelector(getSelectedAppTheme); + const navColorStyle = + currentApplicationDetails?.applicationDetail?.navigationSetting + ?.colorStyle || NAVIGATION_SETTINGS.COLOR_STYLE.LIGHT; + const primaryColor = get( + selectedTheme, + "properties.colors.primaryColor", + "inherit", + ); + const history = useHistory(); + const currentUser: User | undefined = useSelector(getCurrentUser); + + if (currentUser?.username === ANONYMOUS_USERNAME) { + return null; + } + + return ( + +
)} diff --git a/app/client/src/pages/AppViewer/PageMenu.tsx b/app/client/src/pages/AppViewer/PageMenu.tsx index c88b9cfba6..5436428ad3 100644 --- a/app/client/src/pages/AppViewer/PageMenu.tsx +++ b/app/client/src/pages/AppViewer/PageMenu.tsx @@ -23,6 +23,7 @@ import { get } from "lodash"; import { PageMenuContainer, StyledNavLink } from "./PageMenu.styled"; import { StyledCtaContainer } from "./Navigation/Sidebar.styled"; import ShareButton from "./Navigation/components/ShareButton"; +import BackToAppsButton from "./Navigation/components/BackToAppsButton"; type NavigationProps = { isOpen?: boolean; @@ -144,6 +145,11 @@ export function PageMenu(props: NavigationProps) { /> )} + + {!hideWatermark && ( { if (url && canEdit) { return ( - + + + + + ); +}; + +export default ImageInput; diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/LogoConfiguration.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/LogoConfiguration.tsx deleted file mode 100644 index 1071e84cbe..0000000000 --- a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/LogoConfiguration.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { - APP_NAVIGATION_SETTING, - createMessage, -} from "@appsmith/constants/messages"; -import type { NavigationSetting } from "constants/AppConstants"; -import type { DropdownOption } from "design-system-old"; -import { Dropdown, Text, TextType } from "design-system-old"; -import React from "react"; -import type { UpdateSetting } from "."; - -const LogoConfiguration = (props: { - options: DropdownOption[]; - navigationSetting: NavigationSetting; - updateSetting: UpdateSetting; -}) => { - const { options } = props; - - const unavailableLabel = " - [Unavailable atm]"; - - const handleOnSelect = (value?: { label: string; value: string }) => { - props.updateSetting("logoConfiguration", value?.value || ""); - }; - - return ( -
- - {createMessage(APP_NAVIGATION_SETTING.logoConfigurationLabel) + - unavailableLabel} - - item.value === props.navigationSetting?.logoConfiguration, - )} - showLabelOnly - width="100%" - /> -
- ); -}; - -export default LogoConfiguration; diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/SwitchSettingForLogoConfiguration.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/SwitchSettingForLogoConfiguration.tsx new file mode 100644 index 0000000000..b5245b599f --- /dev/null +++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/SwitchSettingForLogoConfiguration.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import type { Dispatch, SetStateAction } from "react"; +import StyledPropertyHelpLabel from "./StyledPropertyHelpLabel"; +import SwitchWrapper from "../../Components/SwitchWrapper"; +import { Switch } from "design-system-old"; +import type { LogoConfigurationSwitches } from "."; +import _ from "lodash"; + +const SwitchSettingForLogoConfiguration = (props: { + label: string; + keyName: keyof LogoConfigurationSwitches; + tooltip?: string; + logoConfigurationSwitches: LogoConfigurationSwitches; + setLogoConfigurationSwitches: Dispatch< + SetStateAction + >; +}) => { + const { + keyName, + label, + logoConfigurationSwitches, + setLogoConfigurationSwitches, + tooltip, + } = props; + + // updateSetting("logoConfiguration", !isChecked); + // logEvent(keyName as keyof StringsFromNavigationSetting, !isChecked); + + return ( +
+
+ + + + { + setLogoConfigurationSwitches({ + ...logoConfigurationSwitches, + [keyName]: !logoConfigurationSwitches[keyName], + }); + }} + /> + +
+
+ ); +}; + +export default SwitchSettingForLogoConfiguration; diff --git a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/index.tsx b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/index.tsx index 2e526616f1..7db87896ef 100644 --- a/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/index.tsx +++ b/app/client/src/pages/Editor/AppSettingsPane/AppSettings/NavigationSettings/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { getCurrentApplication } from "@appsmith/selectors/applicationSelectors"; import { @@ -21,13 +21,14 @@ import equal from "fast-deep-equal"; import { getCurrentApplicationId } from "selectors/editorSelectors"; import { updateApplication } from "@appsmith/actions/applicationActions"; import { Spinner } from "design-system-old"; +import LogoInput from "@appsmith/pages/Editor/NavigationSettings/LogoInput"; +import SwitchSettingForLogoConfiguration from "./SwitchSettingForLogoConfiguration"; /** * TODO - @Dhruvik - ImprovedAppNav * Revisit these imports in v1.1 * https://www.notion.so/appsmith/Ship-Faster-33b32ed5b6334810a0b4f42e03db4a5b?pvs=4 */ -// import LogoConfiguration from "./LogoConfiguration"; // import { ReactComponent as NavPositionStickyIcon } from "assets/icons/settings/nav-position-sticky.svg"; // import { ReactComponent as NavPositionStaticIcon } from "assets/icons/settings/nav-position-static.svg"; // import { ReactComponent as NavStyleMinimalIcon } from "assets/icons/settings/nav-style-minimal.svg"; @@ -37,6 +38,11 @@ export type UpdateSetting = ( value: NavigationSetting[keyof NavigationSetting], ) => void; +export type LogoConfigurationSwitches = { + logo: boolean; + applicationTitle: boolean; +}; + function NavigationSettings() { const application = useSelector(getCurrentApplication); const applicationId = useSelector(getCurrentApplicationId); @@ -44,6 +50,81 @@ function NavigationSettings() { const [navigationSetting, setNavigationSetting] = useState( application?.applicationDetail?.navigationSetting, ); + const [logoConfigurationSwitches, setLogoConfigurationSwitches] = + useState({ + logo: false, + applicationTitle: false, + }); + + useEffect(() => { + setNavigationSetting(application?.applicationDetail?.navigationSetting); + + // Logo configuration + switch (navigationSetting?.logoConfiguration) { + case NAVIGATION_SETTINGS.LOGO_CONFIGURATION.APPLICATION_TITLE_ONLY: + setLogoConfigurationSwitches({ + logo: false, + applicationTitle: true, + }); + break; + case NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE: + setLogoConfigurationSwitches({ + logo: true, + applicationTitle: true, + }); + break; + case NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY: + setLogoConfigurationSwitches({ + logo: true, + applicationTitle: false, + }); + break; + case NAVIGATION_SETTINGS.LOGO_CONFIGURATION.NO_LOGO_OR_APPLICATION_TITLE: + setLogoConfigurationSwitches({ + logo: false, + applicationTitle: false, + }); + break; + default: + break; + } + }, [application?.applicationDetail?.navigationSetting]); + + useEffect(() => { + if ( + logoConfigurationSwitches.logo && + logoConfigurationSwitches.applicationTitle + ) { + updateSetting( + "logoConfiguration", + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE, + ); + } else if ( + logoConfigurationSwitches.logo && + !logoConfigurationSwitches.applicationTitle + ) { + updateSetting( + "logoConfiguration", + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY, + ); + } else if ( + !logoConfigurationSwitches.logo && + logoConfigurationSwitches.applicationTitle + ) { + updateSetting( + "logoConfiguration", + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.APPLICATION_TITLE_ONLY, + ); + } else if ( + !logoConfigurationSwitches.logo && + !logoConfigurationSwitches.applicationTitle + ) { + updateSetting( + "logoConfiguration", + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.NO_LOGO_OR_APPLICATION_TITLE, + ); + } + }, [logoConfigurationSwitches]); const updateSetting = useCallback( debounce( @@ -56,7 +137,7 @@ function NavigationSettings() { isPlainObject(navigationSetting) && !isEmpty(navigationSetting) ) { - const newSettings = { + const newSettings: NavigationSetting = { ...navigationSetting, [key]: value, }; @@ -104,14 +185,9 @@ function NavigationSettings() { // } // } - if (payload.applicationDetail) { - payload.applicationDetail.navigationSetting = - newSettings as NavigationSetting; - } else { - payload.applicationDetail = { - navigationSetting: newSettings as NavigationSetting, - }; - } + payload.applicationDetail = { + navigationSetting: newSettings, + }; dispatch(updateApplication(applicationId, payload)); setNavigationSetting(newSettings); @@ -163,7 +239,8 @@ function NavigationSettings() { {/** * TODO - @Dhruvik - ImprovedAppNav - * Remove check for orientation = top in v1.1 + * Remove check for orientation = top when adding sidebar minimal to show sidebar + * variants as well. * https://www.notion.so/appsmith/Ship-Faster-33b32ed5b6334810a0b4f42e03db4a5b */} {navigationSetting?.orientation === @@ -303,46 +380,31 @@ function NavigationSettings() { updateSetting={updateSetting} /> - {/** - * TODO - @Dhruvik - ImprovedAppNav - * Hiding logo config for v1 - * https://www.notion.so/appsmith/Logo-configuration-option-can-be-multiselect-2a436598539c4db99d1f030850fd8918?pvs=4 - */} - {/* */} + + + {(navigationSetting?.logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE || + navigationSetting?.logoConfiguration === + NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY) && ( + + )} + + , + callback?: (e: React.ChangeEvent) => void, +) => { + const file = e.target.files?.[0]; + + // case 1: no file selected + if (!file) return false; + + // case 2: file size > 2mb + if (file.size > 2 * 1024 * 1024) { + Toaster.show({ + text: createMessage(APP_NAVIGATION_SETTING.logoUploadSizeError), + variant: Variant.danger, + }); + + return false; + } + + // case 3: image selected + const validTypes = ["image/jpeg", "image/png"]; + + if (!validTypes.includes(file.type)) { + Toaster.show({ + text: createMessage(APP_NAVIGATION_SETTING.logoUploadFormatError), + variant: Variant.danger, + }); + + return false; + } + + callback && callback(e); +}; diff --git a/app/client/src/pages/Editor/AppSettingsPane/index.tsx b/app/client/src/pages/Editor/AppSettingsPane/index.tsx index 44c541e38e..a98405147e 100644 --- a/app/client/src/pages/Editor/AppSettingsPane/index.tsx +++ b/app/client/src/pages/Editor/AppSettingsPane/index.tsx @@ -9,10 +9,23 @@ function AppSettingsPane() { const dispatch = useDispatch(); const paneRef = useRef(null); const portalRef = useRef(null); + + // Close app settings pane when clicked outside useOnClickOutside([paneRef, portalRef], () => { if (document.getElementById("save-theme-modal")) return; if (document.getElementById("delete-theme-modal")) return; if (document.getElementById("manual-upgrades-modal")) return; + + // If logo configuration navigation setting dropdown is open + if ( + document.getElementsByClassName( + "t--navigation-settings-logo-configuration", + )?.[0] && + document.getElementsByClassName("bp3-overlay-open")?.[0] + ) { + return; + } + // No id property for `Dialog` component, so using class name if (document.querySelector(".t--import-application-modal")) { return; diff --git a/app/client/src/pages/Editor/WidgetsEditor/CanvasContainer.tsx b/app/client/src/pages/Editor/WidgetsEditor/CanvasContainer.tsx index 5764c26c4f..15c92ce1a5 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/CanvasContainer.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/CanvasContainer.tsx @@ -45,6 +45,7 @@ const Container = styled.section<{ $isAutoLayout: boolean; background: string; isPreviewingNavigation?: boolean; + isAppSettingsPaneWithNavigationTabOpen?: boolean; navigationHeight?: number; }>` width: ${({ $isAutoLayout }) => @@ -56,12 +57,33 @@ const Container = styled.section<{ overflow-y: auto; background: ${({ background }) => background}; - ${({ isPreviewingNavigation, navigationHeight }) => { + ${({ + isAppSettingsPaneWithNavigationTabOpen, + isPreviewingNavigation, + navigationHeight, + }) => { + let css = ``; + if (isPreviewingNavigation) { - return ` + css += ` margin-top: ${navigationHeight}px !important; `; } + + if (isAppSettingsPaneWithNavigationTabOpen) { + /** + * We need to remove the scrollbar width to avoid small white space on the + * right of the canvas since we disable all interactions, including scroll, + * while the app settings pane with navigation tab is open + */ + css += ` + ::-webkit-scrollbar { + width: 0px; + } + `; + } + + return css; }} &:before { @@ -171,6 +193,9 @@ function CanvasContainer(props: CanvasContainerProps) { "mt-24": shouldShowSnapShotBanner, })} id={"canvas-viewport"} + isAppSettingsPaneWithNavigationTabOpen={ + isAppSettingsPaneWithNavigationTabOpen + } isPreviewingNavigation={isPreviewingNavigation} key={currentPageId} navigationHeight={navigationHeight} diff --git a/app/client/src/pages/Editor/WidgetsEditor/NavigationPreview.tsx b/app/client/src/pages/Editor/WidgetsEditor/NavigationPreview.tsx index e5033fc096..f007d63fbb 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/NavigationPreview.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/NavigationPreview.tsx @@ -22,6 +22,8 @@ const NavigationPreview = forwardRef( isPreviewMode || isAppSettingsPaneWithNavigationTabOpen, "-translate-y-full duration-0": !isPreviewMode || !isAppSettingsPaneWithNavigationTabOpen, + "select-none pointer-events-none": + isAppSettingsPaneWithNavigationTabOpen, })} ref={ref} > diff --git a/app/client/src/pages/Editor/WidgetsEditor/index.tsx b/app/client/src/pages/Editor/WidgetsEditor/index.tsx index 10426e49a8..c9bd50bf9e 100644 --- a/app/client/src/pages/Editor/WidgetsEditor/index.tsx +++ b/app/client/src/pages/Editor/WidgetsEditor/index.tsx @@ -54,6 +54,7 @@ import SnapShotBannerCTA from "../CanvasLayoutConversion/SnapShotBannerCTA"; import { APP_MODE } from "entities/App"; import { getSelectedAppTheme } from "selectors/appThemingSelectors"; import { useIsMobileDevice } from "utils/hooks/useDeviceDetect"; +import classNames from "classnames"; import { getSnapshotUpdatedTime } from "selectors/autoLayoutSelectors"; import { getReadableSnapShotDetails } from "utils/autoLayout/AutoLayoutUtils"; @@ -182,8 +183,15 @@ function WidgetsEditor() { {guidedTourEnabled && }
-
- +
+ {!isAppSettingsPaneWithNavigationTabOpen && } +