Update customizing UI style function (#14550)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-04-01 17:12:17 +08:00 committed by GitHub
parent 681b69a863
commit 3604ebc536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 176 additions and 93 deletions

View File

@ -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;
}
}

View File

@ -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<any>;
@ -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]
});

View File

@ -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();
}

View File

@ -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": ""
}
};
}
};

View File

@ -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) {

View File

@ -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);
});
});

View File

@ -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<any> {
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}`;
}
}
}

View File

@ -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
}
];

View File

@ -1,10 +1,10 @@
<clr-modal [(clrModalOpen)]="opened" [clrModalClosable]="false" [clrModalStaticBackdrop]="false">
<div class="modal-body dialog-body">
<div class="harbor-logo-black">
<img [src]="'images/harbor-logo.svg'" class="harbor-icon">
<img [src]="customLogo ? ('images/' + customLogo) : 'images/harbor-logo.svg'" class="harbor-icon">
</div>
<div class="content" tabindex="1">
<div>{{customName?.name? customName?.name : ('APP_TITLE.HARBOR' | translate)}}</div>
<div>{{customName? customName : ('APP_TITLE.HARBOR' | translate)}}</div>
<div>
<span class="p5 about-version">{{'ABOUT.VERSION' | translate}} {{version}}</span>
</div>
@ -22,4 +22,4 @@
<div class="modal-footer margin-left-override">
<button type="button" class="btn btn-primary" (click)="close()">{{'BUTTON.CLOSE' | translate}}</button>
</div>
</clr-modal>
</clr-modal>

View File

@ -16,9 +16,19 @@ describe('AboutDialogComponent', () => {
}
};
let fakeSkinableConfig = {
getProject: function () {
getSkinConfig: function () {
return {
introduction: {}
"headerBgColor": {
"darkMode": "",
"lightMode": ""
},
"loginBgImg": "",
"loginTitle": "",
"product": {
"name": "",
"logo": "",
"introduction": ""
}
};
}
};

View File

@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit } from '@angular/core';
import { TranslateService } from "@ngx-translate/core";
import { AppConfigService } from '../../../services/app-config.service';
import { SkinableConfig } from "../../../services/skinable-config.service";
@ -26,21 +24,21 @@ export class AboutDialogComponent implements OnInit {
opened: boolean = false;
build: string = "4276418";
customIntroduction: string;
customName: { [key: string]: any };
customName: string;
customLogo: string;
constructor(private appConfigService: AppConfigService,
private translate: TranslateService,
private skinableConfig: SkinableConfig) {
}
ngOnInit(): void {
// custom skin
let customSkinObj = this.skinableConfig.getProject();
let customSkinObj = this.skinableConfig.getSkinConfig();
if (customSkinObj) {
let selectedLang = this.translate.currentLang;
this.customName = customSkinObj;
if (customSkinObj.introduction && customSkinObj.introduction[selectedLang]) {
this.customIntroduction = customSkinObj.introduction[selectedLang];
if (customSkinObj.product) {
this.customLogo = customSkinObj.product.logo;
this.customName = customSkinObj.product.name;
this.customIntroduction = customSkinObj.product.introduction;
}
}
}

View File

@ -25,9 +25,19 @@ describe('GlobalSearchComponent', () => {
}
};
let fakeSkinableConfig = {
getProject: function () {
getSkinConfig: function () {
return {
introduction: {}
"headerBgColor": {
"darkMode": "",
"lightMode": ""
},
"loginBgImg": "",
"loginTitle": "",
"product": {
"name": "",
"logo": "",
"introduction": ""
}
};
}
};

View File

@ -57,9 +57,9 @@ export class GlobalSearchComponent implements OnInit, OnDestroy {
ngOnInit(): void {
// custom skin
let customSkinObj = this.skinableConfig.getProject();
if (customSkinObj && customSkinObj.name) {
this.translate.get('GLOBAL_SEARCH.PLACEHOLDER', {'param': customSkinObj.name}).subscribe(res => {
let customSkinObj = this.skinableConfig.getSkinConfig();
if (customSkinObj && customSkinObj.product && customSkinObj.product.name) {
this.translate.get('GLOBAL_SEARCH.PLACEHOLDER', {'param': customSkinObj.product.name}).subscribe(res => {
// Placeholder text
this.placeholderText = res;
});

View File

@ -1,10 +1,10 @@
<clr-header class="header-5 header" [ngStyle]='{"background-color": customStyle?.headerBgColor?customStyle?.headerBgColor:"#004a70" }'>
<clr-header class="header-5 header" [attr.style]="getBgColor()">
<div class="branding">
<a href="javascript:void(0)" class="nav-link" (click)="homeAction()">
<!-- <clr-icon shape="vm-bug" *ngIf="!customStyle?.headerLogo"></clr-icon> -->
<img [attr.src]="'images/'+customStyle?.headerLogo" *ngIf="customStyle?.headerLogo;else elseBlock" class="headerLogo">
<img [attr.src]="'images/'+customStyle?.product?.logo" *ngIf="customStyle?.product?.logo;else elseBlock" class="headerLogo">
<ng-template #elseBlock><img [src]="'images/harbor-logo.svg'" class="harbor-logo" /></ng-template>
<span class="title">{{customProjectName?.name? customProjectName?.name:(appTitle | translate)}}</span>
<span class="title">{{customStyle?.product?.name ? customStyle?.product?.name:(appTitle | translate)}}</span>
</a>
</div>
<div class="header-nav">

View File

@ -28,6 +28,7 @@ import {
DeFaultLang,
languageNames,
} from "../../entities/shared.const";
import { CustomStyle, HAS_STYLE_MODE, StyleMode } from "../../../services/theme";
@Component({
@ -42,8 +43,7 @@ export class NavigatorComponent implements OnInit {
selectedLang: string = DeFaultLang;
appTitle: string = 'APP_TITLE.HARBOR';
customStyle: { [key: string]: any };
customProjectName: { [key: string]: any };
customStyle: CustomStyle;
constructor(
private session: SessionService,
private router: Router,
@ -57,13 +57,7 @@ export class NavigatorComponent implements OnInit {
ngOnInit(): void {
// custom skin
let customSkinObj = this.skinableConfig.getSkinConfig();
if (customSkinObj) {
if (customSkinObj.product) {
this.customProjectName = customSkinObj.product;
}
this.customStyle = customSkinObj;
}
this.customStyle = this.skinableConfig.getSkinConfig();
this.selectedLang = this.translate.currentLang;
if (this.appConfigService.isIntegrationMode()) {
this.appTitle = 'APP_TITLE.VIC';
@ -175,4 +169,16 @@ export class NavigatorComponent implements OnInit {
registryAction(): void {
this.searchTrigger.closeSearch(true);
}
getBgColor(): string {
if (this.customStyle && this.customStyle.headerBgColor && localStorage) {
if (localStorage.getItem(HAS_STYLE_MODE) === StyleMode.LIGHT) {
return `background-color:${this.customStyle.headerBgColor.lightMode} !important`;
}
if (localStorage.getItem(HAS_STYLE_MODE) === StyleMode.DARK) {
return `background-color:${this.customStyle.headerBgColor.darkMode} !important`;
}
}
return null;
}
}

View File

@ -1,14 +1,13 @@
{
"headerBgColor": "",
"headerLogo": "",
"headerBgColor": {
"darkMode": "",
"lightMode": ""
},
"loginBgImg": "",
"appTitle": "",
"loginTitle": "",
"product": {
"name": "",
"introduction": {
"zh-cn": "",
"es-es": "",
"en-us": ""
}
"logo": "",
"introduction": ""
}
}
}