feat: App navigation - Logo upload (#22297)
## Description Allowing users to upload a logo to show in the navigation along with toggles to hide logo or application title. Fixes #20134 Fixes #21946 Fixes #22260 ## Media <video src="https://user-images.githubusercontent.com/22471214/235613131-129ac2ed-b994-4eab-8eba-7db297c2f7fd.mp4"><video> ## Type of change - New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Manual ### Test Plan > https://github.com/appsmithorg/TestSmith/issues/2376 ### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
94e4b8515e
commit
9d5e2e0246
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ApiResponse> {
|
||||
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<ApiResponse> {
|
||||
return Api.delete(
|
||||
ApplicationApi.baseURL + "/" + request.applicationId + "/logo",
|
||||
);
|
||||
}
|
||||
|
||||
static createApplicationSnapShot(request: snapShotApplicationRequest) {
|
||||
return Api.post(getSnapShotAPIRoute(request.applicationId));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
|
|
|||
|
|
@ -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)<BackToHomeButtonProps>`
|
||||
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 (
|
||||
<TooltipComponent content="Back to apps" position="bottom-left">
|
||||
<StyledLink
|
||||
className={classNames({
|
||||
"flex items-center gap-2 group t--back-to-home hover:no-underline":
|
||||
true,
|
||||
"mr-2": !isLogoVisible,
|
||||
"mb-2 mr-3": isLogoVisible,
|
||||
})}
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
to="/applications"
|
||||
>
|
||||
<StyledAppIcon
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className="p-1 w-7 h-7"
|
||||
forSidebar={forSidebar}
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
</StyledLink>
|
||||
</TooltipComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default BackToHomeButton;
|
||||
75
app/client/src/ce/pages/AppViewer/NavigationLogo.tsx
Normal file
75
app/client/src/ce/pages/AppViewer/NavigationLogo.tsx
Normal file
|
|
@ -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 (
|
||||
<Link
|
||||
className={classNames({
|
||||
"mr-4": true,
|
||||
"pointer-events-none select-none": pages.length <= 1,
|
||||
})}
|
||||
to={pageUrl}
|
||||
>
|
||||
<StyledImage
|
||||
alt="Application's logo"
|
||||
src={getAssetUrl(`/api/v1/assets/${logoAssetId}`)}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavigationLogo;
|
||||
|
|
@ -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 (
|
||||
<div className={`pt-4 t--navigation-settings-logo`}>
|
||||
<div className="flex items-center">
|
||||
<Text type={TextType.P1}>
|
||||
{createMessage(APP_NAVIGATION_SETTING.logoLabel)}
|
||||
</Text>
|
||||
|
||||
{navigationSetting?.logoAssetId?.length ? (
|
||||
<button
|
||||
className="flex items-center justify-center text-center h-7 w-7 ml-auto"
|
||||
disabled={isUploadingNavigationLogo || isDeletingNavigationLogo}
|
||||
onClick={() => handleDelete()}
|
||||
>
|
||||
<Icon fillColor="#575757" name="trash" size="extraLarge" />
|
||||
</button>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<ImageInput
|
||||
className="t--settings-brand-logo-input"
|
||||
onChange={(file) => {
|
||||
handleChange && handleChange(file);
|
||||
}}
|
||||
validate={logoImageValidation}
|
||||
value={
|
||||
navigationSetting?.logoAssetId?.length
|
||||
? `/api/v1/assets/${navigationSetting?.logoAssetId}`
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoInput;
|
||||
|
|
@ -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<NavigationSetting["logoAssetId"]>,
|
||||
) => {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<UploadNavigationLogoRequest>,
|
||||
) {
|
||||
try {
|
||||
const request: UploadNavigationLogoRequest = action.payload;
|
||||
const response: ApiResponse<UpdateApplicationResponse> = 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: <id_string_here> } 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<UpdateApplicationResponse> =
|
||||
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<DeleteNavigationLogoRequest>,
|
||||
) {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export * from "ce/pages/AppViewer/BackToHomeButton";
|
||||
import { default as CE_BackToHomeButton } from "ce/pages/AppViewer/BackToHomeButton";
|
||||
export default CE_BackToHomeButton;
|
||||
3
app/client/src/ee/pages/AppViewer/NavigationLogo.tsx
Normal file
3
app/client/src/ee/pages/AppViewer/NavigationLogo.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "ce/pages/AppViewer/NavigationLogo";
|
||||
import { default as CE_NavigationLogo } from "ce/pages/AppViewer/NavigationLogo";
|
||||
export default CE_NavigationLogo;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
>
|
||||
<StyledHeader>
|
||||
<div
|
||||
className={classNames({
|
||||
flex: true,
|
||||
"flex-col": isLogoVisible,
|
||||
})}
|
||||
>
|
||||
{currentUser?.username !== ANONYMOUS_USERNAME && (
|
||||
<BackToHomeButton
|
||||
forSidebar
|
||||
isLogoVisible={isLogoVisible}
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
setIsLogoVisible={setIsLogoVisible}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-col gap-5">
|
||||
<NavigationLogo logoConfiguration={logoConfiguration} />
|
||||
|
||||
{!isMinimal && (
|
||||
<ApplicationName
|
||||
appName={currentApplicationDetails?.name}
|
||||
forSidebar
|
||||
navColorStyle={navColorStyle}
|
||||
navStyle={navStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
)}
|
||||
{!isMinimal &&
|
||||
(logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION
|
||||
.LOGO_AND_APPLICATION_TITLE ||
|
||||
logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION
|
||||
.APPLICATION_TITLE_ONLY) && (
|
||||
<ApplicationName
|
||||
appName={currentApplicationDetails?.name}
|
||||
forSidebar
|
||||
navColorStyle={navColorStyle}
|
||||
navStyle={navStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isMinimal && (
|
||||
|
|
@ -231,6 +228,12 @@ export function Sidebar(props: SidebarProps) {
|
|||
primaryColor={primaryColor}
|
||||
url={editorURL}
|
||||
/>
|
||||
|
||||
<BackToAppsButton
|
||||
currentApplicationDetails={currentApplicationDetails}
|
||||
insideSidebar
|
||||
isMinimal={isMinimal}
|
||||
/>
|
||||
</StyledCtaContainer>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement>(null);
|
||||
const [ellipsisActive, setEllipsisActive] = useState(false);
|
||||
const isMobile = useIsMobileDevice();
|
||||
|
|
@ -42,6 +50,7 @@ const ApplicationName = (props: ApplicationNameProps) => {
|
|||
>
|
||||
<StyledApplicationName
|
||||
className="overflow-hidden text-base overflow-ellipsis whitespace-nowrap t--app-viewer-application-name"
|
||||
fontWeight={fontWeight || "bold"}
|
||||
forSidebar={forSidebar}
|
||||
isMobile={isMobile}
|
||||
navColorStyle={navColorStyle}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import React from "react";
|
||||
import Button from "../../AppViewerButton";
|
||||
import { useSelector } from "react-redux";
|
||||
import { ALL_APPS, createMessage } from "@appsmith/constants/messages";
|
||||
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
|
||||
import { getMenuItemTextColor } from "pages/AppViewer/utils";
|
||||
import type { NavigationSetting } from "constants/AppConstants";
|
||||
import { NAVIGATION_SETTINGS } from "constants/AppConstants";
|
||||
import { get } from "lodash";
|
||||
import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { useHistory } from "react-router";
|
||||
import styled from "styled-components";
|
||||
import AppsLineIcon from "remixicon-react/AppsLineIcon";
|
||||
import { getCurrentUser } from "selectors/usersSelectors";
|
||||
import type { User } from "constants/userConstants";
|
||||
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
||||
import { TooltipComponent } from "design-system-old";
|
||||
|
||||
type BackToAppsButtonProps = {
|
||||
currentApplicationDetails?: ApplicationPayload;
|
||||
insideSidebar?: boolean;
|
||||
isMinimal?: boolean;
|
||||
};
|
||||
|
||||
const StyledAppIcon = styled(AppsLineIcon)<{
|
||||
primaryColor: string;
|
||||
navColorStyle: NavigationSetting["colorStyle"];
|
||||
}>`
|
||||
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 (
|
||||
<TooltipComponent
|
||||
boundary="viewport"
|
||||
content={createMessage(ALL_APPS)}
|
||||
disabled={insideSidebar}
|
||||
hoverOpenDelay={500}
|
||||
modifiers={{
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
}}
|
||||
position="bottom"
|
||||
>
|
||||
<Button
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className="h-8 t--app-viewer-back-to-apps-button"
|
||||
icon={
|
||||
<StyledAppIcon
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
}
|
||||
insideSidebar={insideSidebar}
|
||||
isMinimal={isMinimal}
|
||||
navColorStyle={navColorStyle}
|
||||
onClick={() => {
|
||||
history.push("/applications");
|
||||
}}
|
||||
primaryColor={primaryColor}
|
||||
text={insideSidebar && !isMinimal && createMessage(ALL_APPS)}
|
||||
/>
|
||||
</TooltipComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackToAppsButton;
|
||||
|
|
@ -49,7 +49,7 @@ export const CollapseIconContainer = styled.div<{
|
|||
${({ isOpen, isPinned }) => {
|
||||
if (!isPinned && !isOpen) {
|
||||
return `
|
||||
transform: translateX(40px);
|
||||
transform: translateX(54px);
|
||||
background: ${Colors.WHITE};
|
||||
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1), 0px 1px 2px rgba(0, 0, 0, 0.06);
|
||||
transition: background 0.3s ease-in-out, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
|
||||
import Button from "../../AppViewerButton";
|
||||
import { Icon } from "design-system-old";
|
||||
import { Icon, TooltipComponent } from "design-system-old";
|
||||
import AppInviteUsersForm from "pages/workspace/AppInviteUsersForm";
|
||||
import { useSelector } from "react-redux";
|
||||
import { showAppInviteUsersDialogSelector } from "@appsmith/selectors/applicationSelectors";
|
||||
|
|
@ -61,26 +61,40 @@ const ShareButton = (props: ShareButtonProps) => {
|
|||
placeholder={createMessage(INVITE_USERS_PLACEHOLDER, cloudHosting)}
|
||||
title={currentApplicationDetails?.name}
|
||||
trigger={
|
||||
<Button
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className="h-8 t--app-viewer-share-button"
|
||||
data-cy="viewmode-share"
|
||||
icon={
|
||||
<Icon
|
||||
fillColor={getApplicationNameTextColor(
|
||||
primaryColor,
|
||||
navColorStyle,
|
||||
)}
|
||||
name="share-line"
|
||||
size="extraLarge"
|
||||
/>
|
||||
}
|
||||
insideSidebar={insideSidebar}
|
||||
isMinimal={isMinimal}
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
text={insideSidebar && !isMinimal && createMessage(SHARE_APP)}
|
||||
/>
|
||||
<TooltipComponent
|
||||
boundary="viewport"
|
||||
content={createMessage(SHARE_APP)}
|
||||
disabled={insideSidebar}
|
||||
hoverOpenDelay={500}
|
||||
modifiers={{
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
}}
|
||||
position="bottom"
|
||||
>
|
||||
<Button
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className="h-8 t--app-viewer-share-button"
|
||||
data-cy="viewmode-share"
|
||||
icon={
|
||||
<Icon
|
||||
fillColor={getApplicationNameTextColor(
|
||||
primaryColor,
|
||||
navColorStyle,
|
||||
)}
|
||||
name="share-line"
|
||||
size="extraLarge"
|
||||
/>
|
||||
}
|
||||
insideSidebar={insideSidebar}
|
||||
isMinimal={isMinimal}
|
||||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
text={insideSidebar && !isMinimal && createMessage(SHARE_APP)}
|
||||
/>
|
||||
</TooltipComponent>
|
||||
}
|
||||
workspaceId={currentWorkspaceId}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ import ProfileDropdown from "pages/common/ProfileDropdown";
|
|||
import TopStacked from "../TopStacked";
|
||||
import { HeaderRow, StyledNav } from "./TopHeader.styled";
|
||||
import TopInline from "../TopInline";
|
||||
import BackToHomeButton from "@appsmith/pages/AppViewer/BackToHomeButton";
|
||||
import NavigationLogo from "@appsmith/pages/AppViewer/NavigationLogo";
|
||||
import BackToAppsButton from "./BackToAppsButton";
|
||||
|
||||
type TopHeaderProps = {
|
||||
currentApplicationDetails?: ApplicationPayload;
|
||||
|
|
@ -52,6 +53,10 @@ const TopHeader = (props: TopHeaderProps) => {
|
|||
const navStyle =
|
||||
currentApplicationDetails?.applicationDetail?.navigationSetting?.navStyle ||
|
||||
NAVIGATION_SETTINGS.NAV_STYLE.STACKED;
|
||||
const logoConfiguration =
|
||||
currentApplicationDetails?.applicationDetail?.navigationSetting
|
||||
?.logoConfiguration ||
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE;
|
||||
const primaryColor = get(
|
||||
selectedTheme,
|
||||
"properties.colors.primaryColor",
|
||||
|
|
@ -86,19 +91,20 @@ const TopHeader = (props: TopHeaderProps) => {
|
|||
setMenuOpen={setMenuOpen}
|
||||
/>
|
||||
|
||||
{currentUser?.username !== ANONYMOUS_USERNAME && (
|
||||
<BackToHomeButton
|
||||
<NavigationLogo logoConfiguration={logoConfiguration} />
|
||||
|
||||
{(logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE ||
|
||||
logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION
|
||||
.APPLICATION_TITLE_ONLY) && (
|
||||
<ApplicationName
|
||||
appName={currentApplicationDetails?.name}
|
||||
navColorStyle={navColorStyle}
|
||||
navStyle={navStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ApplicationName
|
||||
appName={currentApplicationDetails?.name}
|
||||
navColorStyle={navColorStyle}
|
||||
navStyle={navStyle}
|
||||
primaryColor={primaryColor}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{currentApplicationDetails?.applicationDetail?.navigationSetting
|
||||
|
|
@ -126,6 +132,10 @@ const TopHeader = (props: TopHeaderProps) => {
|
|||
primaryColor={primaryColor}
|
||||
url={editorURL}
|
||||
/>
|
||||
|
||||
<BackToAppsButton
|
||||
currentApplicationDetails={currentApplicationDetails}
|
||||
/>
|
||||
</HeaderRightItemContainer>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
/>
|
||||
)}
|
||||
|
||||
<BackToAppsButton
|
||||
currentApplicationDetails={application}
|
||||
insideSidebar
|
||||
/>
|
||||
|
||||
{!hideWatermark && (
|
||||
<a
|
||||
className="flex hover:no-underline mt-2"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ export const initialState: any = {
|
|||
},
|
||||
},
|
||||
ui: {
|
||||
editor: {
|
||||
isPreviewMode: false,
|
||||
},
|
||||
appSettingsPane: {
|
||||
isOpen: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useMemo } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Button from "./AppViewerButton";
|
||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||
import {
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
import {
|
||||
getCurrentApplication,
|
||||
getCurrentPageId,
|
||||
previewModeSelector,
|
||||
} from "selectors/editorSelectors";
|
||||
import { getSelectedAppTheme } from "selectors/appThemingSelectors";
|
||||
import {
|
||||
|
|
@ -24,9 +25,10 @@ import { viewerURL } from "RouteBuilder";
|
|||
import { useHistory } from "react-router";
|
||||
import { useHref } from "pages/Editor/utils";
|
||||
import type { NavigationSetting } from "constants/AppConstants";
|
||||
import { Icon } from "design-system-old";
|
||||
import { Icon, TooltipComponent } from "design-system-old";
|
||||
import { getApplicationNameTextColor } from "./utils";
|
||||
import { ButtonVariantTypes } from "components/constants";
|
||||
import { setPreviewModeInitAction } from "actions/editorActions";
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------------------------------------------
|
||||
|
|
@ -65,6 +67,8 @@ function PrimaryCTA(props: Props) {
|
|||
const permissionRequired = PERMISSION_TYPE.MANAGE_APPLICATION;
|
||||
const userPermissions = currentApplication?.userPermissions ?? [];
|
||||
const canEdit = isPermitted(userPermissions, permissionRequired);
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const appViewerURL = useHref(viewerURL, {
|
||||
pageId: currentPageID,
|
||||
|
|
@ -95,28 +99,46 @@ function PrimaryCTA(props: Props) {
|
|||
const PrimaryCTA = useMemo(() => {
|
||||
if (url && canEdit) {
|
||||
return (
|
||||
<Button
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className={className}
|
||||
icon={
|
||||
<Icon
|
||||
fillColor={getApplicationNameTextColor(
|
||||
primaryColor,
|
||||
navColorStyle,
|
||||
)}
|
||||
name="edit-line"
|
||||
size="extraLarge"
|
||||
/>
|
||||
}
|
||||
insideSidebar={insideSidebar}
|
||||
isMinimal={isMinimal}
|
||||
navColorStyle={navColorStyle}
|
||||
onClick={() => {
|
||||
history.push(url);
|
||||
<TooltipComponent
|
||||
boundary="viewport"
|
||||
content={createMessage(EDIT_APP)}
|
||||
disabled={insideSidebar}
|
||||
hoverOpenDelay={500}
|
||||
modifiers={{
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
}}
|
||||
primaryColor={primaryColor}
|
||||
text={insideSidebar && !isMinimal && createMessage(EDIT_APP)}
|
||||
/>
|
||||
position="bottom"
|
||||
>
|
||||
<Button
|
||||
borderRadius={selectedTheme.properties.borderRadius.appBorderRadius}
|
||||
className={className}
|
||||
icon={
|
||||
<Icon
|
||||
fillColor={getApplicationNameTextColor(
|
||||
primaryColor,
|
||||
navColorStyle,
|
||||
)}
|
||||
name="edit-line"
|
||||
size="extraLarge"
|
||||
/>
|
||||
}
|
||||
insideSidebar={insideSidebar}
|
||||
isMinimal={isMinimal}
|
||||
navColorStyle={navColorStyle}
|
||||
onClick={() => {
|
||||
if (isPreviewMode) {
|
||||
dispatch(setPreviewModeInitAction(!isPreviewMode));
|
||||
} else {
|
||||
history.push(url);
|
||||
}
|
||||
}}
|
||||
primaryColor={primaryColor}
|
||||
text={insideSidebar && !isMinimal && createMessage(EDIT_APP)}
|
||||
/>
|
||||
</TooltipComponent>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +162,7 @@ function PrimaryCTA(props: Props) {
|
|||
}}
|
||||
primaryColor={primaryColor}
|
||||
text={createMessage(FORK_APP)}
|
||||
varient={ButtonVariantTypes.SECONDARY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -163,6 +186,7 @@ function PrimaryCTA(props: Props) {
|
|||
navColorStyle={navColorStyle}
|
||||
primaryColor={primaryColor}
|
||||
text={createMessage(FORK_APP)}
|
||||
varient={ButtonVariantTypes.SECONDARY}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import { Button, Size, Spinner } from "design-system-old";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
getIsDeletingNavigationLogo,
|
||||
getIsUploadingNavigationLogo,
|
||||
} from "@appsmith/selectors/applicationSelectors";
|
||||
import styled from "styled-components";
|
||||
import classNames from "classnames";
|
||||
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
|
||||
|
||||
type ImageInputProps = {
|
||||
value?: any;
|
||||
onChange?(value?: any): void;
|
||||
validate?(
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
callback?: (e: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
): void;
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
const StyledImg = styled.img`
|
||||
object-fit: contain;
|
||||
max-width: 200px;
|
||||
max-height: 100px;
|
||||
`;
|
||||
|
||||
export const ImageInput = (props: ImageInputProps) => {
|
||||
const { className, onChange, validate, value } = props;
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const isUploadingNavigationLogo = useSelector(getIsUploadingNavigationLogo);
|
||||
const isDeletingNavigationLogo = useSelector(getIsDeletingNavigationLogo);
|
||||
const [isLogoLoaded, setIsLogoLoaded] = useState(false);
|
||||
|
||||
// trigger file input on click of upload logo button
|
||||
const onFileInputClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
// on upload, pass the file to api
|
||||
const onFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsLogoLoaded(false);
|
||||
|
||||
const file = e.target.files?.[0];
|
||||
|
||||
if (!file) return;
|
||||
|
||||
// validate the file, if invalid, show error
|
||||
// else call the callback
|
||||
validate &&
|
||||
validate(e, () => {
|
||||
onChange && onChange(file);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isDeletingNavigationLogo) {
|
||||
setIsLogoLoaded(false);
|
||||
|
||||
// reset the input to accept the same file again if delete happens
|
||||
if (fileInputRef?.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
}
|
||||
}, [isDeletingNavigationLogo]);
|
||||
|
||||
const renderLogo = () => {
|
||||
if (isUploadingNavigationLogo || isDeletingNavigationLogo) {
|
||||
return (
|
||||
<div className="px-4 py-10 w-full flex justify-center">
|
||||
<Spinner size="extraExtraExtraExtraLarge" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return (
|
||||
<StyledImg
|
||||
alt="Your application's logo"
|
||||
className={classNames({
|
||||
hidden: !isLogoLoaded,
|
||||
})}
|
||||
onLoad={() => setIsLogoLoaded(true)}
|
||||
src={getAssetUrl(value)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return "No logo set";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center justify-center w-full border h-28 group ${
|
||||
className ? className : ""
|
||||
}`}
|
||||
>
|
||||
{renderLogo()}
|
||||
|
||||
<div className="absolute inset-0 items-center justify-center hidden gap-2 bg-black group-hover:flex bg-opacity-20">
|
||||
<Button
|
||||
icon="upload-line"
|
||||
iconPosition="left"
|
||||
onClick={onFileInputClick}
|
||||
size={Size.medium}
|
||||
text="Upload"
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<input
|
||||
accept="image/jpeg, image/png"
|
||||
className="hidden"
|
||||
onChange={onFileInputChange}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageInput;
|
||||
|
|
@ -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 (
|
||||
<div className="pt-4">
|
||||
<Text type={TextType.P1}>
|
||||
{createMessage(APP_NAVIGATION_SETTING.logoConfigurationLabel) +
|
||||
unavailableLabel}
|
||||
</Text>
|
||||
<Dropdown
|
||||
onSelect={handleOnSelect}
|
||||
options={options}
|
||||
selected={options.find(
|
||||
(item) => item.value === props.navigationSetting?.logoConfiguration,
|
||||
)}
|
||||
showLabelOnly
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoConfiguration;
|
||||
|
|
@ -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<LogoConfigurationSwitches>
|
||||
>;
|
||||
}) => {
|
||||
const {
|
||||
keyName,
|
||||
label,
|
||||
logoConfigurationSwitches,
|
||||
setLogoConfigurationSwitches,
|
||||
tooltip,
|
||||
} = props;
|
||||
|
||||
// updateSetting("logoConfiguration", !isChecked);
|
||||
// logEvent(keyName as keyof StringsFromNavigationSetting, !isChecked);
|
||||
|
||||
return (
|
||||
<div className="pt-4">
|
||||
<div className="flex justify-between content-center">
|
||||
<StyledPropertyHelpLabel
|
||||
label={label}
|
||||
lineHeight="1.17"
|
||||
maxWidth="270px"
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
|
||||
<SwitchWrapper>
|
||||
<Switch
|
||||
checked={logoConfigurationSwitches[keyName]}
|
||||
className="mb-0"
|
||||
id={`t--navigation-settings-${_.kebabCase(keyName)}`}
|
||||
large
|
||||
onChange={() => {
|
||||
setLogoConfigurationSwitches({
|
||||
...logoConfigurationSwitches,
|
||||
[keyName]: !logoConfigurationSwitches[keyName],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</SwitchWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchSettingForLogoConfiguration;
|
||||
|
|
@ -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<LogoConfigurationSwitches>({
|
||||
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
|
||||
*/}
|
||||
{/* <LogoConfiguration
|
||||
navigationSetting={navigationSetting}
|
||||
options={[
|
||||
{
|
||||
label: _.startCase(
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE,
|
||||
),
|
||||
value:
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE,
|
||||
},
|
||||
{
|
||||
label: _.startCase(
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY,
|
||||
),
|
||||
value: NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY,
|
||||
},
|
||||
{
|
||||
label: _.startCase(
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.APPLICATION_TITLE_ONLY,
|
||||
),
|
||||
value:
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.APPLICATION_TITLE_ONLY,
|
||||
},
|
||||
{
|
||||
label: _.startCase(
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION
|
||||
.NO_LOGO_OR_APPLICATION_TITLE,
|
||||
),
|
||||
value:
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION
|
||||
.NO_LOGO_OR_APPLICATION_TITLE,
|
||||
},
|
||||
]}
|
||||
updateSetting={updateSetting}
|
||||
/> */}
|
||||
<SwitchSettingForLogoConfiguration
|
||||
keyName="logo"
|
||||
label={createMessage(APP_NAVIGATION_SETTING.showLogoLabel)}
|
||||
logoConfigurationSwitches={logoConfigurationSwitches}
|
||||
setLogoConfigurationSwitches={setLogoConfigurationSwitches}
|
||||
/>
|
||||
|
||||
{(navigationSetting?.logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_AND_APPLICATION_TITLE ||
|
||||
navigationSetting?.logoConfiguration ===
|
||||
NAVIGATION_SETTINGS.LOGO_CONFIGURATION.LOGO_ONLY) && (
|
||||
<LogoInput
|
||||
navigationSetting={navigationSetting}
|
||||
updateSetting={updateSetting}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SwitchSettingForLogoConfiguration
|
||||
keyName="applicationTitle"
|
||||
label={createMessage(
|
||||
APP_NAVIGATION_SETTING.showApplicationTitleLabel,
|
||||
)}
|
||||
logoConfigurationSwitches={logoConfigurationSwitches}
|
||||
setLogoConfigurationSwitches={setLogoConfigurationSwitches}
|
||||
/>
|
||||
|
||||
<SwitchSetting
|
||||
keyName="showSignIn"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ import type {
|
|||
} from "constants/AppConstants";
|
||||
import { keysOfNavigationSetting } from "constants/AppConstants";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { Toaster, Variant } from "design-system-old";
|
||||
import {
|
||||
APP_NAVIGATION_SETTING,
|
||||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
|
||||
export const logEvent = (
|
||||
keyName: keyof StringsFromNavigationSetting,
|
||||
|
|
@ -39,3 +44,48 @@ export const logEvent = (
|
|||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* validates the uploaded logo file
|
||||
*
|
||||
* checks:
|
||||
* 1. file size max 1MB
|
||||
* 2. file type - jpg, or png
|
||||
*
|
||||
* @param e
|
||||
* @param callback
|
||||
* @returns
|
||||
*/
|
||||
export const logoImageValidation = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
callback?: (e: React.ChangeEvent<HTMLInputElement>) => 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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const NavigationPreview = forwardRef(
|
|||
isPreviewMode || isAppSettingsPaneWithNavigationTabOpen,
|
||||
"-translate-y-full duration-0":
|
||||
!isPreviewMode || !isAppSettingsPaneWithNavigationTabOpen,
|
||||
"select-none pointer-events-none":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
ref={ref}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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 && <Guide />}
|
||||
|
||||
<div className="relative flex flex-row w-full overflow-hidden">
|
||||
<div className="relative flex flex-col w-full overflow-hidden">
|
||||
<CanvasTopSection />
|
||||
<div
|
||||
className={classNames({
|
||||
"relative flex flex-col w-full overflow-hidden": true,
|
||||
"m-8 border border-gray-200":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
>
|
||||
{!isAppSettingsPaneWithNavigationTabOpen && <CanvasTopSection />}
|
||||
|
||||
<div
|
||||
className="relative flex flex-row w-full overflow-hidden"
|
||||
data-testid="widgets-editor"
|
||||
|
|
@ -198,7 +206,12 @@ function WidgetsEditor() {
|
|||
{showNavigation()}
|
||||
|
||||
<PageViewContainer
|
||||
className="relative flex flex-row w-full justify-center overflow-hidden"
|
||||
className={classNames({
|
||||
"relative flex flex-row w-full justify-center overflow-hidden":
|
||||
true,
|
||||
"select-none pointer-events-none":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
hasPinnedSidebar={
|
||||
isPreviewingNavigation && !isMobile
|
||||
? currentApplicationDetails?.applicationDetail
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user