Skip to content

Commit

Permalink
Refactor logo usage
Browse files Browse the repository at this point in the history
Also:
* Move logos to a central location
* Make the loading spinner color-scheme-aware
* Recreate `OverviewPageHeader`, `HomeIcon`, `HeaderLogo` tests
* Enhance `ExitFullScreenButton`, `Header` tests

Signed-off-by: Miki <[email protected]>
  • Loading branch information
AMoo-Miki committed Aug 8, 2023
1 parent 4bc1f55 commit 7a9a6c6
Show file tree
Hide file tree
Showing 63 changed files with 2,100 additions and 5,660 deletions.
7 changes: 3 additions & 4 deletions src/core/public/chrome/chrome_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

import { BehaviorSubject } from 'rxjs';
import type { PublicMethodsOf } from '@osd/utility-types';
import { ChromeBadge, ChromeBrand, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './';
import { ChromeBadge, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './';
import { getLogosMock } from './logos/get_logos.mock';

const createStartContractMock = () => {
const startContract: DeeplyMockedKeys<InternalChromeStart> = {
Expand All @@ -54,6 +55,7 @@ const createStartContractMock = () => {
change: jest.fn(),
reset: jest.fn(),
},
logos: getLogosMock.default,
navControls: {
registerLeft: jest.fn(),
registerCenter: jest.fn(),
Expand All @@ -63,8 +65,6 @@ const createStartContractMock = () => {
getRight$: jest.fn(),
},
setAppTitle: jest.fn(),
setBrand: jest.fn(),
getBrand$: jest.fn(),
setIsVisible: jest.fn(),
getIsVisible$: jest.fn(),
addApplicationClass: jest.fn(),
Expand All @@ -82,7 +82,6 @@ const createStartContractMock = () => {
setCustomNavLink: jest.fn(),
};
startContract.navLinks.getAll.mockReturnValue([]);
startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge));
Expand Down
32 changes: 0 additions & 32 deletions src/core/public/chrome/chrome_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,36 +145,6 @@ describe('start', () => {
});
});

describe('brand', () => {
it('updates/emits the brand as it changes', async () => {
const { chrome, service } = await start();
const promise = chrome.getBrand$().pipe(toArray()).toPromise();

chrome.setBrand({
logo: 'big logo',
smallLogo: 'not so big logo',
});
chrome.setBrand({
logo: 'big logo without small logo',
});
service.stop();

await expect(promise).resolves.toMatchInlineSnapshot(`
Array [
Object {},
Object {
"logo": "big logo",
"smallLogo": "not so big logo",
},
Object {
"logo": "big logo without small logo",
"smallLogo": undefined,
},
]
`);
});
});

describe('visibility', () => {
it('emits false when no application is mounted', async () => {
const { chrome, service } = await start();
Expand Down Expand Up @@ -478,7 +448,6 @@ describe('stop', () => {
it('completes applicationClass$, getIsNavDrawerLocked, breadcrumbs$, isVisible$, and brand$ observables', async () => {
const { chrome, service } = await start();
const promise = Rx.combineLatest(
chrome.getBrand$(),
chrome.getApplicationClasses$(),
chrome.getIsNavDrawerLocked$(),
chrome.getBreadcrumbs$(),
Expand All @@ -496,7 +465,6 @@ describe('stop', () => {

await expect(
Rx.combineLatest(
chrome.getBrand$(),
chrome.getApplicationClasses$(),
chrome.getIsNavDrawerLocked$(),
chrome.getBreadcrumbs$(),
Expand Down
51 changes: 8 additions & 43 deletions src/core/public/chrome/chrome_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_acce
import { Header } from './ui';
import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu';
import { Branding } from '../';
import { getLogos, Logos } from './logos';

export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle };

const IS_LOCKED_KEY = 'core.chrome.isLocked';
Expand All @@ -60,12 +62,6 @@ export interface ChromeBadge {
iconType?: IconType;
}

/** @public */
export interface ChromeBrand {
logo?: string;
smallLogo?: string;
}

/** @public */
export type ChromeBreadcrumb = EuiBreadcrumb;

Expand Down Expand Up @@ -156,7 +152,6 @@ export class ChromeService {
this.initVisibility(application);

const appTitle$ = new BehaviorSubject<string>('Overview');
const brand$ = new BehaviorSubject<ChromeBrand>({});
const applicationClasses$ = new BehaviorSubject<Set<string>>(new Set());
const helpExtension$ = new BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
const breadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
Expand Down Expand Up @@ -185,6 +180,8 @@ export class ChromeService {

const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$));

const logos = getLogos(injectedMetadata.getBranding(), http.basePath.serverBasePath);

const isIE = () => {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE '); // IE 10 or older
Expand Down Expand Up @@ -234,6 +231,7 @@ export class ChromeService {
navLinks,
recentlyAccessed,
docTitle,
logos,

getHeaderComponent: () => (
<Header
Expand Down Expand Up @@ -261,23 +259,13 @@ export class ChromeService {
onIsLockedUpdate={setIsNavDrawerLocked}
isLocked$={getIsNavDrawerLocked$}
branding={injectedMetadata.getBranding()}
logos={logos}
survey={injectedMetadata.getSurvey()}
/>
),

setAppTitle: (appTitle: string) => appTitle$.next(appTitle),

getBrand$: () => brand$.pipe(takeUntil(this.stop$)),

setBrand: (brand: ChromeBrand) => {
brand$.next(
Object.freeze({
logo: brand.logo,
smallLogo: brand.smallLogo,
})
);
},

getIsVisible$: () => this.isVisible$,

setIsVisible: (isVisible: boolean) => this.isForceHidden$.next(!isVisible),
Expand Down Expand Up @@ -371,6 +359,8 @@ export interface ChromeStart {
recentlyAccessed: ChromeRecentlyAccessed;
/** {@inheritdoc ChromeDocTitle} */
docTitle: ChromeDocTitle;
/** {@inheritdoc Logos} */
readonly logos: Logos;

/**
* Sets the current app's title
Expand All @@ -381,31 +371,6 @@ export interface ChromeStart {
*/
setAppTitle(appTitle: string): void;

/**
* Get an observable of the current brand information.
*/
getBrand$(): Observable<ChromeBrand>;

/**
* Set the brand configuration.
*
* @remarks
* Normally the `logo` property will be rendered as the
* CSS background for the home link in the chrome navigation, but when the page is
* rendered in a small window the `smallLogo` will be used and rendered at about
* 45px wide.
*
* @example
* ```js
* chrome.setBrand({
* logo: 'url(/plugins/app/logo.png) center no-repeat'
* smallLogo: 'url(/plugins/app/logo-small.png) center no-repeat'
* })
* ```
*
*/
setBrand(brand: ChromeBrand): void;

/**
* Get an observable of the current visibility state of the chrome.
*/
Expand Down
1 change: 0 additions & 1 deletion src/core/public/chrome/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export {
ChromeService,
ChromeStart,
InternalChromeStart,
ChromeBrand,
ChromeHelpExtension,
} from './chrome_service';
export {
Expand Down
27 changes: 27 additions & 0 deletions src/core/public/chrome/logos/get_logos.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { getLogos } from './get_logos';
import { Logos } from './logos';

export const getLogosMock: {
default: DeeplyMockedKeys<Logos>;
branded: DeeplyMockedKeys<Logos>;
} = {
default: getLogos({}, ''),
branded: getLogos(
{
logo: {
defaultUrl: '/custom/branded/logo.svg',
darkModeUrl: '/custom/branded/logo-darkmode.svg',
},
mark: {
defaultUrl: '/custom/branded/mark.svg',
darkModeUrl: '/custom/branded/mark-darkmode.svg',
},
},
''
),
};
120 changes: 120 additions & 0 deletions src/core/public/chrome/logos/get_logos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { deepFreeze } from '@osd/std';
import { Logos } from './logos';
import { ImageType } from './image_item';
import { Branding } from '../../../types';

// The logos are stored at `src/core/server/core_app/assets/logos` to have a pretty URL
export const OPENSEARCH_DASHBOARDS_THEMED = 'ui/logos/opensearch_dashboards.svg';
export const OPENSEARCH_DASHBOARDS_ON_LIGHT = 'ui/logos/opensearch_dashboards_on_light.svg';
export const OPENSEARCH_DASHBOARDS_ON_DARK = 'ui/logos/opensearch_dashboards_on_dark.svg';
export const OPENSEARCH_THEMED = 'ui/logos/opensearch.svg';
export const OPENSEARCH_ON_LIGHT = 'ui/logos/opensearch_on_light.svg';
export const OPENSEARCH_ON_DARK = 'ui/logos/opensearch_on_dark.svg';
export const MARK_THEMED = 'ui/logos/opensearch_mark.svg';
export const MARK_ON_LIGHT = 'ui/logos/opensearch_mark_on_light.svg';
export const MARK_ON_DARK = 'ui/logos/opensearch_mark_on_dark.svg';
export const CENTER_MARK_THEMED = 'ui/logos/opensearch_center_mark.svg';
export const CENTER_MARK_ON_LIGHT = 'ui/logos/opensearch_center_mark_on_light.svg';
export const CENTER_MARK_ON_DARK = 'ui/logos/opensearch_center_mark_on_dark.svg';

export const getLogos = (branding: Branding = {}, serverBasePath: string): Logos => {
const {
logo: { defaultUrl: customLogoUrl, darkModeUrl: customDarkLogoUrl } = {},
mark: { defaultUrl: customMarkUrl, darkModeUrl: customDarkMarkUrl } = {},
darkMode = false,
} = branding;

// OSD logos
const defaultThemedOpenSearchDashboards = `${serverBasePath}/${OPENSEARCH_DASHBOARDS_THEMED}`;
const defaultOnLightOpenSearchDashboards = `${serverBasePath}/${OPENSEARCH_DASHBOARDS_ON_LIGHT}`;
const defaultOnDarkOpenSearchDashboards = `${serverBasePath}/${OPENSEARCH_DASHBOARDS_ON_DARK}`;
// OS logos
const defaultThemedOpenSearch = `${serverBasePath}/${OPENSEARCH_THEMED}`;
const defaultOnLightOpenSearch = `${serverBasePath}/${OPENSEARCH_ON_LIGHT}`;
const defaultOnDarkOpenSearch = `${serverBasePath}/${OPENSEARCH_ON_DARK}`;
// OS marks
const defaultThemedMark = `${serverBasePath}/${MARK_THEMED}`;
const defaultOnLightMark = `${serverBasePath}/${MARK_ON_LIGHT}`;
const defaultOnDarkMark = `${serverBasePath}/${MARK_ON_DARK}`;
// OS marks variant centered within the container
const defaultThemedCenterMark = `${serverBasePath}/${CENTER_MARK_THEMED}`;
const defaultOnLightCenterMark = `${serverBasePath}/${CENTER_MARK_ON_LIGHT}`;
const defaultOnDarkCenterMark = `${serverBasePath}/${CENTER_MARK_ON_DARK}`;

// in dark mode use the custom dark, and if it is not set, use the custom default
let urlOpenSearchDashboards = (darkMode && customDarkLogoUrl) || customLogoUrl;
let typeOpenSearchDashboards: ImageType = 'custom';

// If not custom branded, use OSD's themed one
if (!urlOpenSearchDashboards) {
urlOpenSearchDashboards = defaultThemedOpenSearchDashboards;
typeOpenSearchDashboards = 'default';
}

// in dark mode use the custom dark, and if it is not set, use the custom default
let urlOpenSearch = (darkMode && customDarkLogoUrl) || customLogoUrl;
let typeOpenSearch: ImageType = 'custom';

// If not custom branded, use OSD's themed one
if (!urlOpenSearch) {
urlOpenSearch = defaultThemedOpenSearch;
typeOpenSearch = 'default';
}

// in dark mode use the custom dark, and if it is not set, use the custom default
let urlMark = (darkMode && customDarkMarkUrl) || customMarkUrl;
let typeMark: ImageType = 'custom';

// If not custom branded, use OSD's themed one
if (!urlMark) {
urlMark = defaultThemedMark;
typeMark = 'default';
}

// in dark mode use the custom dark, and if it is not set, use the custom default
let urlCenterMark = (darkMode && customDarkMarkUrl) || customMarkUrl;
let typeCenterMark: ImageType = 'custom';

// If not custom branded, use OSD's themed one
if (!urlCenterMark) {
urlCenterMark = defaultThemedCenterMark;
typeCenterMark = 'default';
}

return deepFreeze({
OpenSearch: {
url: urlOpenSearch,
type: typeOpenSearch,

light: { url: customLogoUrl || defaultOnLightOpenSearch },
dark: { url: customDarkLogoUrl || customLogoUrl || defaultOnDarkOpenSearch },
},
OpenSearchDashboards: {
url: urlOpenSearchDashboards,
type: typeOpenSearchDashboards,

light: { url: customLogoUrl || defaultOnLightOpenSearchDashboards },
dark: { url: customDarkLogoUrl || customLogoUrl || defaultOnDarkOpenSearchDashboards },
},
Mark: {
url: urlMark,
type: typeMark,

light: { url: customMarkUrl || defaultOnLightMark },
dark: { url: customDarkMarkUrl || customMarkUrl || defaultOnDarkMark },
},
CenterMark: {
url: urlCenterMark,
type: typeCenterMark,

light: { url: customMarkUrl || defaultOnLightCenterMark },
dark: { url: customDarkMarkUrl || customMarkUrl || defaultOnDarkCenterMark },
},
colorScheme: darkMode ? 'dark' : 'light',
});
};
13 changes: 13 additions & 0 deletions src/core/public/chrome/logos/image_item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export type ImageType = 'default' | 'custom';
export interface ImageItem {
/**
* The URL of the image
*/
readonly url: string;
readonly type?: ImageType;
}
9 changes: 9 additions & 0 deletions src/core/public/chrome/logos/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

// ToDo: Extend this to handle everything related to white-labelling

export type { Logos } from './logos';
export * from './get_logos';
Loading

0 comments on commit 7a9a6c6

Please sign in to comment.