Skip to content

Commit

Permalink
Show fallback logo in preview
Browse files Browse the repository at this point in the history
ref DEV-1690
  • Loading branch information
louischan-oursky committed Aug 27, 2024
2 parents a0f4eab + 87666c1 commit 468455a
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 29 deletions.
109 changes: 109 additions & 0 deletions portal/src/components/design/AppLogoPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from "react";
import { FormattedMessage } from "@oursky/react-messageformat";
import { Image, ImageFit } from "@fluentui/react";

import cn from "classnames";

import BaseImagePicker, { ImageFileExtension } from "../common/BaseImagePicker";
import PrimaryButton from "../../PrimaryButton";
import DefaultButton from "../../DefaultButton";

import { useSystemConfig } from "../../context/SystemConfigContext";
import { base64EncodedDataToDataURI } from "../../util/uri";

import { AppLogoResource } from "../../graphql/portal/DesignScreen/form";

import styles from "./ImagePicker.module.css";

interface AppLogoPickerProps {
logo: AppLogoResource;
onChange: (
image: {
base64EncodedData: string;
extension: ImageFileExtension;
} | null
) => void;
}
const AppLogoPicker: React.VFC<AppLogoPickerProps> = function AppLogoPicker(
props
) {
const { logo, onChange } = props;
const { themes } = useSystemConfig();

const imagePreviewData =
logo.base64EncodedData ?? logo.fallbackBase64EncodedData;

const isShowingFallbackImage =
logo.base64EncodedData == null && logo.fallbackBase64EncodedData != null;

return (
<BaseImagePicker
className={cn("flex", "items-center", "gap-x-6")}
base64EncodedData={logo.base64EncodedData}
onChange={onChange}
>
{({ showFilePicker, clearImage }) => (
<>
<div
className={cn(
"flex",
"items-center",
"justify-center",
"h-30",
"w-30",
"bg-neutral-light",
"rounded",
"border",
"border-solid",
"border-neutral-tertiaryAlt",
"overflow-hidden"
)}
onClick={showFilePicker}
>
{imagePreviewData == null ? (
<span className={styles.icImagePlaceholder}></span>
) : (
<Image
src={base64EncodedDataToDataURI(imagePreviewData)}
className={cn("h-full", "w-full")}
imageFit={ImageFit.centerCover}
maximizeFrame={true}
styles={{
image: isShowingFallbackImage
? {
opacity: 0.3,
filter: "grayscale(1)",
}
: null,
}}
/>
)}
</div>
{logo.base64EncodedData != null ? (
<PrimaryButton
theme={themes.destructive}
text={<FormattedMessage id={"ImageFilePicker.remove"} />}
onClick={clearImage}
/>
) : logo.fallbackBase64EncodedData != null ? (
<DefaultButton
text={
<FormattedMessage id="DesignScreen.configuration.appLogoPicker.override" />
}
onClick={showFilePicker}
/>
) : (
<DefaultButton
text={
<FormattedMessage id="DesignScreen.configuration.imagePicker.upload" />
}
onClick={showFilePicker}
/>
)}
</>
)}
</BaseImagePicker>
);
};

export default AppLogoPicker;
29 changes: 17 additions & 12 deletions portal/src/graphql/portal/DesignScreen/DesignScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Toggle from "../../../Toggle";
import ConfigurationGroup from "../../../components/design/ConfigurationGroup";
import FallbackDescription from "../../../components/design/FallbackDescription";
import ConfigurationDescription from "../../../components/design/ConfigurationDescription";
import AppLogoPicker from "../../../components/design/AppLogoPicker";
import { ImagePicker } from "../../../components/design/ImagePicker";
import ButtonToggleGroup, {
Option,
Expand Down Expand Up @@ -181,11 +182,17 @@ const AppLogoConfiguration: React.VFC<AppLogoConfigurationProps> =
{designForm.state.themeOption !== "darkOnly" ? (
<>
<Configuration labelKey="DesignScreen.configuration.logo.light">
<ImagePicker
base64EncodedData={designForm.state.appLogoBase64EncodedData}
<AppLogoPicker
logo={designForm.state.appLogo}
onChange={designForm.lightThemeSetters.setAppLogo}
/>
</Configuration>
{designForm.state.selectedLanguage !==
designForm.state.fallbackLanguage ? (
<FallbackDescription
fallbackLanguage={designForm.state.fallbackLanguage}
/>
) : null}
<TextField
label={renderToString(
"DesignScreen.configuration.logo.height.label.light"
Expand All @@ -199,13 +206,17 @@ const AppLogoConfiguration: React.VFC<AppLogoConfigurationProps> =
{designForm.state.themeOption !== "lightOnly" ? (
<>
<Configuration labelKey="DesignScreen.configuration.logo.dark">
<ImagePicker
base64EncodedData={
designForm.state.appLogoDarkBase64EncodedData
}
<AppLogoPicker
logo={designForm.state.appLogoDark}
onChange={designForm.darkThemeSetters.setAppLogo}
/>
</Configuration>
{designForm.state.selectedLanguage !==
designForm.state.fallbackLanguage ? (
<FallbackDescription
fallbackLanguage={designForm.state.fallbackLanguage}
/>
) : null}
<TextField
label={renderToString(
"DesignScreen.configuration.logo.height.label.dark"
Expand All @@ -216,12 +227,6 @@ const AppLogoConfiguration: React.VFC<AppLogoConfigurationProps> =
/>
</>
) : null}
{designForm.state.selectedLanguage !==
designForm.state.fallbackLanguage ? (
<FallbackDescription
fallbackLanguage={designForm.state.fallbackLanguage}
/>
) : null}
</ConfigurationGroup>
);
};
Expand Down
47 changes: 37 additions & 10 deletions portal/src/graphql/portal/DesignScreen/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,22 @@ interface ConfigFormState {
themeOption: ThemeOption;
}

export interface AppLogoResource {
base64EncodedData: string | null;
fallbackBase64EncodedData: string | null;
}

interface ResourcesFormState {
appName: string;

// light
customisableLightTheme: PartialCustomisableTheme;
appLogoBase64EncodedData: string | null;
appLogo: AppLogoResource;
backgroundImageBase64EncodedData: string | null;

// dark
customisableDarkTheme: PartialCustomisableTheme;
appLogoDarkBase64EncodedData: string | null;
appLogoDark: AppLogoResource;
backgroundImageDarkBase64EncodedData: string | null;

faviconBase64EncodedData: string | null;
Expand Down Expand Up @@ -345,10 +350,11 @@ export function useBrandDesignForm(appID: string): BranchDesignForm {
};

const getValueFromImageResource = (
def: ResourceDefinition
def: ResourceDefinition,
language: string
): string | null => {
const form = getResourceFormByResourceDefinition(def);
const specifiers = expandDef(def, selectedLanguage);
const specifiers = expandDef(def, language);
const imageResouece = resolveResource(form.state.resources, specifiers);
if (!imageResouece?.nullableValue) {
return null;
Expand All @@ -367,16 +373,37 @@ export function useBrandDesignForm(appID: string): BranchDesignForm {

return {
appName: getValueFromTranslationJSON(TranslationKey.AppName),
appLogoBase64EncodedData: getValueFromImageResource(RESOURCE_APP_LOGO),
appLogoDarkBase64EncodedData: getValueFromImageResource(
RESOURCE_APP_LOGO_DARK
appLogo: {
base64EncodedData: getValueFromImageResource(
RESOURCE_APP_LOGO,
selectedLanguage
),
fallbackBase64EncodedData: getValueFromImageResource(
RESOURCE_APP_LOGO,
configForm.state.fallbackLanguage
),
},
appLogoDark: {
base64EncodedData: getValueFromImageResource(
RESOURCE_APP_LOGO_DARK,
selectedLanguage
),
fallbackBase64EncodedData: getValueFromImageResource(
RESOURCE_APP_LOGO_DARK,
configForm.state.fallbackLanguage
),
},
faviconBase64EncodedData: getValueFromImageResource(
RESOURCE_FAVICON,
selectedLanguage
),
faviconBase64EncodedData: getValueFromImageResource(RESOURCE_FAVICON),
backgroundImageBase64EncodedData: getValueFromImageResource(
RESOURCE_APP_BACKGROUND_IMAGE
RESOURCE_APP_BACKGROUND_IMAGE,
selectedLanguage
),
backgroundImageDarkBase64EncodedData: getValueFromImageResource(
RESOURCE_APP_BACKGROUND_IMAGE_DARK
RESOURCE_APP_BACKGROUND_IMAGE_DARK,
selectedLanguage
),
customisableLightTheme: lightTheme,
customisableDarkTheme: darkTheme,
Expand Down
19 changes: 12 additions & 7 deletions portal/src/graphql/portal/DesignScreen/viewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
getThemeTargetSelector,
} from "../../../model/themeAuthFlowV2";
import { PortalAPIAppConfig } from "../../../types";
import { BranchDesignFormState, TranslationKey } from "./form";
import { AppLogoResource, BranchDesignFormState, TranslationKey } from "./form";

export enum PreviewPage {
Login = "preview/login",
Expand Down Expand Up @@ -136,12 +136,8 @@ export function mapDesignFormStateToPreviewCustomisationMessage(

const images: Record<string, string | null> = {};

images["brand-logo-light"] = state.appLogoBase64EncodedData
? `data:;base64,${state.appLogoBase64EncodedData}`
: null;
images["brand-logo-dark"] = state.appLogoDarkBase64EncodedData
? `data:;base64,${state.appLogoDarkBase64EncodedData}`
: null;
images["brand-logo-light"] = getLogoDataSrc(state.appLogo);
images["brand-logo-dark"] = getLogoDataSrc(state.appLogoDark);

const translations = {
[TranslationKey.AppName]: state.appName,
Expand All @@ -155,3 +151,12 @@ export function mapDesignFormStateToPreviewCustomisationMessage(
translations,
};
}

function getLogoDataSrc(logoResouce: AppLogoResource): string | null {
const data =
logoResouce.base64EncodedData ?? logoResouce.fallbackBase64EncodedData;
if (!data) {
return null;
}
return `data:;base64,${data}`;
}
1 change: 1 addition & 0 deletions portal/src/locale-data/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,7 @@
"DesignScreen.configuration.favicon.label": "Favicon",
"DesignScreen.configuration.favicon.description": "Add a favicon to browser tabs to identify your brand. The size must be a multiple of 32px square PNG, JPG or GIF",
"DesignScreen.configuration.imagePicker.upload": "Upload",
"DesignScreen.configuration.appLogoPicker.override": "Upload override",
"DesignScreen.configuration.card.label": "Card",
"DesignScreen.configuration.card.alignment.label": "Alignment",
"DesignScreen.configuration.background.label": "Background",
Expand Down

0 comments on commit 468455a

Please sign in to comment.