From 3604ebc536792e2b5eba116a27c204752d318797 Mon Sep 17 00:00:00 2001 From: Will Sun <30999793+AllForNothing@users.noreply.github.com> Date: Thu, 1 Apr 2021 17:12:17 +0800 Subject: [PATCH] Update customizing UI style function (#14550) Signed-off-by: AllForNothing --- .../app/account/sign-in/sign-in.component.ts | 4 +- src/portal/src/app/app.component.spec.ts | 26 +++++++- src/portal/src/app/app.component.ts | 16 +++-- .../harbor-shell.component.spec.ts | 14 ++++- .../result-tip-histogram.component.ts | 3 +- .../services/skinable-config.service.spec.ts | 20 +++--- .../app/services/skinable-config.service.ts | 22 ++++--- src/portal/src/app/services/theme.ts | 61 +++++++++++++------ .../about-dialog/about-dialog.component.html | 6 +- .../about-dialog.component.spec.ts | 14 ++++- .../about-dialog/about-dialog.component.ts | 16 +++-- .../global-search.component.spec.ts | 14 ++++- .../global-search/global-search.component.ts | 6 +- .../navigator/navigator.component.html | 6 +- .../navigator/navigator.component.ts | 24 +++++--- src/portal/src/setting.json | 17 +++--- 16 files changed, 176 insertions(+), 93 deletions(-) diff --git a/src/portal/src/app/account/sign-in/sign-in.component.ts b/src/portal/src/app/account/sign-in/sign-in.component.ts index 592037c8f..3e18f0488 100644 --- a/src/portal/src/app/account/sign-in/sign-in.component.ts +++ b/src/portal/src/app/account/sign-in/sign-in.component.ts @@ -83,8 +83,8 @@ export class SignInComponent implements AfterViewChecked, OnInit { if (customSkinObj.loginBgImg) { this.customLoginBgImg = customSkinObj.loginBgImg; } - if (customSkinObj.appTitle) { - this.customAppTitle = customSkinObj.appTitle; + if (customSkinObj.loginTitle) { + this.customAppTitle = customSkinObj.loginTitle; } } diff --git a/src/portal/src/app/app.component.spec.ts b/src/portal/src/app/app.component.spec.ts index 37873fffe..ca055f6f4 100644 --- a/src/portal/src/app/app.component.spec.ts +++ b/src/portal/src/app/app.component.spec.ts @@ -23,6 +23,8 @@ import { AppConfigService } from './services/app-config.service'; import { AppComponent } from './app.component'; import { ClarityModule } from "@clr/angular"; import { APP_BASE_HREF } from "@angular/common"; +import { SharedTestingModule } from "./shared/shared.module"; +import { SkinableConfig } from "./services/skinable-config.service"; describe('AppComponent', () => { let fixture: ComponentFixture; @@ -42,6 +44,25 @@ describe('AppComponent', () => { setTitle: function () { } }; + const fakeSkinableConfig = { + getSkinConfig() { + return { + "headerBgColor": { + "darkMode": "", + "lightMode": "" + }, + "loginBgImg": "", + "loginTitle": "", + "product": { + "name": "test", + "logo": "", + "introduction": "" + } + }; + }, + setTitleIcon() { + } + }; beforeEach(() => { TestBed.configureTestingModule({ @@ -49,16 +70,15 @@ describe('AppComponent', () => { AppComponent ], imports: [ - ClarityModule, - TranslateModule.forRoot() + SharedTestingModule, ], providers: [ - TranslateService, { provide: APP_BASE_HREF, useValue: '/' }, { provide: CookieService, useValue: fakeCookieService }, { provide: SessionService, useValue: fakeSessionService }, { provide: AppConfigService, useValue: fakeAppConfigService }, { provide: Title, useValue: fakeTitle }, + { provide: SkinableConfig, useValue: fakeSkinableConfig }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); diff --git a/src/portal/src/app/app.component.ts b/src/portal/src/app/app.component.ts index 5cd7507a4..d1cee0d8f 100644 --- a/src/portal/src/app/app.component.ts +++ b/src/portal/src/app/app.component.ts @@ -16,12 +16,13 @@ import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; import { AppConfigService } from './services/app-config.service'; import { ThemeService } from './services/theme.service'; -import { THEME_ARRAY, ThemeInterface } from './services/theme'; +import { CustomStyle, HAS_STYLE_MODE, THEME_ARRAY, ThemeInterface } from './services/theme'; import { clone } from './shared/units/utils'; import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang, supportedLangs } from "./shared/entities/shared.const"; import { forkJoin, Observable } from "rxjs"; +import { SkinableConfig } from "./services/skinable-config.service"; + -const HAS_STYLE_MODE: string = 'styleModeLocal'; @Component({ selector: 'harbor-app', @@ -34,7 +35,8 @@ export class AppComponent { private translate: TranslateService, private appConfigService: AppConfigService, private titleService: Title, - public theme: ThemeService + public theme: ThemeService, + private skinableConfig: SkinableConfig ) { // init language @@ -46,7 +48,13 @@ export class AppComponent { } translate.get(key).subscribe((res: string) => { - this.titleService.setTitle(res); + const customSkinData: CustomStyle = this.skinableConfig.getSkinConfig(); + if (customSkinData && customSkinData.product && customSkinData.product.name) { + this.titleService.setTitle(customSkinData.product.name); + this.skinableConfig.setTitleIcon(); + } else { + this.titleService.setTitle(res); + } }); this.setTheme(); } diff --git a/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts b/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts index 0ffc74411..d47e2881e 100644 --- a/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts +++ b/src/portal/src/app/base/harbor-shell/harbor-shell.component.spec.ts @@ -39,9 +39,19 @@ describe('HarborShellComponent', () => { let mockAccountSettingsModalService = null; let mockPasswordSettingService = null; let mockSkinableConfig = { - getProject: function () { + getSkinConfig: function () { return { - introduction: {} + "headerBgColor": { + "darkMode": "", + "lightMode": "" + }, + "loginBgImg": "", + "loginTitle": "", + "product": { + "name": "", + "logo": "", + "introduction": "" + } }; } }; diff --git a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts index 08f7e57f7..3ed523e68 100644 --- a/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts +++ b/src/portal/src/app/base/project/repository/artifact/vulnerability-scanning/result-tip-histogram/result-tip-histogram.component.ts @@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { TranslateService } from "@ngx-translate/core"; import { ScannerVo, VulnerabilitySummary } from "../../../../../../shared/services"; import { VULNERABILITY_SCAN_STATUS, VULNERABILITY_SEVERITY } from "../../../../../../shared/units/utils"; +import { HAS_STYLE_MODE, StyleMode } from "../../../../../../services/theme"; const MIN = 60; const MIN_STR = "min "; @@ -240,7 +241,7 @@ export class ResultTipHistogramComponent implements OnInit { ]; } isThemeLight() { - return localStorage.getItem('styleModeLocal') === 'LIGHT'; + return localStorage.getItem(HAS_STYLE_MODE) === StyleMode.LIGHT; } getScannerInfo(): string { if (this.scanner) { diff --git a/src/portal/src/app/services/skinable-config.service.spec.ts b/src/portal/src/app/services/skinable-config.service.spec.ts index 87541bbe1..c3d790a46 100644 --- a/src/portal/src/app/services/skinable-config.service.spec.ts +++ b/src/portal/src/app/services/skinable-config.service.spec.ts @@ -8,17 +8,16 @@ describe('SkinableConfig', () => { let httpMock: HttpTestingController; let product = { "name": "", - "introduction": { - "zh-cn": "", - "es-es": "", - "en-us": "" - } + "logo": "", + "introduction": "" }; let mockCustomSkinData = { - "headerBgColor": "", - "headerLogo": "", + "headerBgColor": { + "darkMode": "", + "lightMode": "" + }, "loginBgImg": "", - "appTitle": "", + "loginTitle": "", "product": product }; @@ -46,9 +45,6 @@ describe('SkinableConfig', () => { expect(req.request.method).toBe('GET'); req.flush(mockCustomSkinData); expect(service.getSkinConfig()).toEqual(mockCustomSkinData); - expect(service.getProject()).toEqual(product); - service.customSkinData = null; - expect(service.getProject()).toBeNull(); - + expect(service.getSkinConfig().product).toEqual(product); }); }); diff --git a/src/portal/src/app/services/skinable-config.service.ts b/src/portal/src/app/services/skinable-config.service.ts index db2e3ae89..c860f9b79 100644 --- a/src/portal/src/app/services/skinable-config.service.ts +++ b/src/portal/src/app/services/skinable-config.service.ts @@ -1,16 +1,19 @@ -import {Injectable} from "@angular/core"; +import { Inject, Injectable } from "@angular/core"; import {HttpClient} from "@angular/common/http"; import { map, catchError } from "rxjs/operators"; import { Observable, throwError as observableThrowError } from "rxjs"; +import { CustomStyle } from "./theme"; +import { DOCUMENT } from "@angular/common"; @Injectable() export class SkinableConfig { - customSkinData: {[key: string]: any}; - constructor(private http: HttpClient) {} + private customSkinData: CustomStyle; + constructor(private http: HttpClient, + @Inject(DOCUMENT) private document: Document) {} public getCustomFile(): Observable { return this.http.get('setting.json') - .pipe(map(response => this.customSkinData = response) + .pipe(map(response => this.customSkinData = response as CustomStyle) , catchError((error: any) => { console.error('custom skin json file load failed'); return observableThrowError(error); @@ -21,11 +24,10 @@ export class SkinableConfig { return this.customSkinData; } - public getProject() { - if (this.customSkinData) { - return this.customSkinData.product; - } else { - return null; - } + public setTitleIcon() { + if (this.customSkinData && this.customSkinData.product && this.customSkinData.product.logo) { + const titleIcon: HTMLLinkElement = this.document.querySelector('link'); + titleIcon.href = `images/${this.customSkinData.product.logo}`; + } } } diff --git a/src/portal/src/app/services/theme.ts b/src/portal/src/app/services/theme.ts index 8fa426264..88e93c187 100644 --- a/src/portal/src/app/services/theme.ts +++ b/src/portal/src/app/services/theme.ts @@ -1,24 +1,47 @@ +export enum StyleMode { + DARK = 'DARK', + LIGHT = 'LIGHT' +} + +export const HAS_STYLE_MODE: string = 'styleModeLocal'; + export interface ThemeInterface { - showStyle: string; - mode: string; - text: string; - currentFileName: string; - toggleFileName: string; + showStyle: string; + mode: string; + text: string; + currentFileName: string; + toggleFileName: string; +} + +export interface CustomStyle { + headerBgColor: { + darkMode: string; + lightMode: string; + }; + loginBgImg: string; + loginTitle: string; + product: { + name: string; + logo: string; + introduction: string; + }; } export const THEME_ARRAY: ThemeInterface[] = [ - { - showStyle: "DARK", - mode: "LIGHT", - text: "APP_TITLE.THEME_LIGHT_TEXT", - currentFileName: "dark-theme.css", - toggleFileName: "light-theme.css", - }, - { - showStyle: "LIGHT", - mode: "DARK", // show button icon - text: "APP_TITLE.THEME_DARK_TEXT", // show button text - currentFileName: "light-theme.css", // loaded current theme file name - toggleFileName: "dark-theme.css", // to toggle theme file name - } + { + showStyle: StyleMode.DARK, + mode: StyleMode.LIGHT, + text: "APP_TITLE.THEME_LIGHT_TEXT", + currentFileName: "dark-theme.css", + toggleFileName: "light-theme.css", + }, + { + showStyle: StyleMode.LIGHT, + mode: StyleMode.DARK, // show button icon + text: "APP_TITLE.THEME_DARK_TEXT", // show button text + currentFileName: "light-theme.css", // loaded current theme file name + toggleFileName: "dark-theme.css", // to toggle theme file name + } ]; + + diff --git a/src/portal/src/app/shared/components/about-dialog/about-dialog.component.html b/src/portal/src/app/shared/components/about-dialog/about-dialog.component.html index 05ab91362..f783e192e 100644 --- a/src/portal/src/app/shared/components/about-dialog/about-dialog.component.html +++ b/src/portal/src/app/shared/components/about-dialog/about-dialog.component.html @@ -1,10 +1,10 @@