diff --git a/src/portal/angular.json b/src/portal/angular.json index b847491ec..18f5ba6ea 100644 --- a/src/portal/angular.json +++ b/src/portal/angular.json @@ -15,6 +15,7 @@ "index": "src/index.html", "main": "src/main.ts", "tsConfig": "src/tsconfig.app.json", + "extractCss": true, "assets": [ "src/images", "src/favicon.ico", @@ -27,7 +28,17 @@ "node_modules/swagger-ui/dist/swagger-ui.css", "node_modules/prismjs/themes/prism-solarizedlight.css", "src/global.scss", - "src/styles.css" + "src/styles.css", + { + "input": "src/css/dark-theme.scss", + "bundleName": "dark-theme", + "lazy": true + }, + { + "input": "src/css/light-theme.scss", + "bundleName": "light-theme", + "lazy": true + } ], "scripts": [ "node_modules/core-js/client/shim.min.js", diff --git a/src/portal/package-lock.json b/src/portal/package-lock.json index 86e8ab209..a31303cd8 100644 --- a/src/portal/package-lock.json +++ b/src/portal/package-lock.json @@ -8333,7 +8333,7 @@ "dependencies": { "form-data": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "requires": { "async": "^2.0.1", @@ -10251,7 +10251,7 @@ }, "next-tick": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, "ng-packagr": { @@ -12874,7 +12874,7 @@ }, "reselect": { "version": "2.5.4", - "resolved": "http://registry.npmjs.org/reselect/-/reselect-2.5.4.tgz", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-2.5.4.tgz", "integrity": "sha1-t9I/3wC4P6etAnlUb427vXZccEc=" }, "resolve": { @@ -13496,7 +13496,7 @@ }, "serialize-error": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" }, "serialize-javascript": { diff --git a/src/portal/src/app/app.component.ts b/src/portal/src/app/app.component.ts index 22ef3cdef..748fc4f41 100644 --- a/src/portal/src/app/app.component.ts +++ b/src/portal/src/app/app.component.ts @@ -19,18 +19,29 @@ import { CookieService } from 'ngx-cookie'; import { SessionService } from './shared/session.service'; import { AppConfigService } from './app-config.service'; +import { ThemeService } from './theme.service'; +import { themeArray, ThemeInterface } from './theme'; +import { clone } from '../lib/utils/utils'; + +const HAS_STYLE_MODE: string = 'styleModeLocal'; @Component({ selector: 'harbor-app', templateUrl: 'app.component.html' }) export class AppComponent { + themeArray: ThemeInterface[] = clone(themeArray); + + styleMode: string = this.themeArray[0].showStyle; constructor( private translate: TranslateService, private cookie: CookieService, private session: SessionService, private appConfigService: AppConfigService, - private titleService: Title) { + private titleService: Title, + public theme: ThemeService + + ) { // Override page title let key: string = "APP_TITLE.HARBOR"; if (this.appConfigService.isIntegrationMode()) { @@ -40,5 +51,20 @@ export class AppComponent { translate.get(key).subscribe((res: string) => { this.titleService.setTitle(res); }); + this.setTheme(); + } + setTheme () { + let styleMode = this.themeArray[0].showStyle; + const localHasStyle = localStorage && localStorage.getItem(HAS_STYLE_MODE); + if (localHasStyle) { + styleMode = localStorage.getItem(HAS_STYLE_MODE); + } else { + localStorage.setItem(HAS_STYLE_MODE, styleMode); + } + this.themeArray.forEach((themeItem) => { + if (themeItem.showStyle === styleMode) { + this.theme.loadStyle(themeItem.currentFileName); + } + }); } } diff --git a/src/portal/src/app/app.module.ts b/src/portal/src/app/app.module.ts index 59c82b82e..9c530b644 100644 --- a/src/portal/src/app/app.module.ts +++ b/src/portal/src/app/app.module.ts @@ -46,7 +46,6 @@ import { ProjectQuotasComponent } from './project-quotas/project-quotas.componen import { HarborLibraryModule } from "../lib/harbor-library.module"; import { HTTP_INTERCEPTORS } from '@angular/common/http'; - registerLocaleData(zh, 'zh-cn'); registerLocaleData(es, 'es-es'); registerLocaleData(localeFr, 'fr-fr'); @@ -90,16 +89,16 @@ export function getCurrentLanguage(translateService: TranslateService) { exports: [ ], providers: [ - AppConfigService, - SkinableConfig, - { - provide: APP_INITIALIZER, - useFactory: initConfig, - deps: [ AppConfigService, SkinableConfig], - multi: true - }, - {provide: LOCALE_ID, useValue: "en-US"}, - { provide: HTTP_INTERCEPTORS, useClass: InterceptHttpService, multi: true } + AppConfigService, + SkinableConfig, + { + provide: APP_INITIALIZER, + useFactory: initConfig, + deps: [AppConfigService, SkinableConfig], + multi: true + }, + { provide: LOCALE_ID, useValue: "en-US" }, + { provide: HTTP_INTERCEPTORS, useClass: InterceptHttpService, multi: true } ], schemas: [ @@ -107,4 +106,4 @@ export function getCurrentLanguage(translateService: TranslateService) { ], bootstrap: [AppComponent] }) -export class AppModule {} +export class AppModule { } diff --git a/src/portal/src/app/base/global-search/search.component.scss b/src/portal/src/app/base/global-search/search.component.scss index baa6bbc76..a3e8f88eb 100644 --- a/src/portal/src/app/base/global-search/search.component.scss +++ b/src/portal/src/app/base/global-search/search.component.scss @@ -19,12 +19,6 @@ position: relative; } -.search-title { - font-size: 28px; - letter-spacing: normal; - color: #000; -} - .search-close { position: absolute; right: 24px; diff --git a/src/portal/src/app/base/harbor-shell/harbor-shell.component.html b/src/portal/src/app/base/harbor-shell/harbor-shell.component.html index 5d249e895..bde51dffb 100644 --- a/src/portal/src/app/base/harbor-shell/harbor-shell.component.html +++ b/src/portal/src/app/base/harbor-shell/harbor-shell.component.html @@ -8,16 +8,17 @@
- {{'SCANNER.ALL_SCANNERS' | translate }} + {{'SCANNER.ALL_SCANNERS' | translate }}
- +
+ [class.content-area-override]="!shouldOverrideContent" + [class.start-content-padding]="shouldOverrideContent"> @@ -42,7 +43,8 @@ {{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}} - + {{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}} @@ -54,7 +56,8 @@ {{'SIDE_NAV.SYSTEM_MGMT.REPLICATION' | translate}} - + {{'CONFIG.LABEL' | translate }} @@ -84,6 +87,7 @@
+
@@ -91,4 +95,4 @@ - + \ No newline at end of file diff --git a/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts b/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts index 24a50908e..098f462d6 100644 --- a/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts +++ b/src/portal/src/app/base/harbor-shell/harbor-shell.component.ts @@ -39,16 +39,16 @@ const YES: string = 'yes'; export class HarborShellComponent implements OnInit, OnDestroy { - @ViewChild(AccountSettingsModalComponent, {static: false}) + @ViewChild(AccountSettingsModalComponent, { static: false }) accountSettingsModal: AccountSettingsModalComponent; - @ViewChild(PasswordSettingComponent, {static: false}) + @ViewChild(PasswordSettingComponent, { static: false }) pwdSetting: PasswordSettingComponent; - @ViewChild(NavigatorComponent, {static: false}) + @ViewChild(NavigatorComponent, { static: false }) navigator: NavigatorComponent; - @ViewChild(AboutDialogComponent, {static: false}) + @ViewChild(AboutDialogComponent, { static: false }) aboutDialog: AboutDialogComponent; // To indicator whwther or not the search results page is displayed @@ -62,14 +62,14 @@ export class HarborShellComponent implements OnInit, OnDestroy { isHttpAuthMode: boolean; showScannerInfo: boolean = false; scannerDocUrl: string = SCANNERS_DOC; - constructor( private route: ActivatedRoute, private router: Router, private session: SessionService, private searchTrigger: SearchTriggerService, private appConfigService: AppConfigService, - private scannerService: ConfigScannerService) { } + private scannerService: ConfigScannerService + ) { } ngOnInit() { if (this.appConfigService.isLdapMode()) { @@ -89,7 +89,7 @@ export class HarborShellComponent implements OnInit, OnDestroy { this.isSearchResultsOpened = false; }); if (!(localStorage && localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES)) { - this.getDefaultScanner(); + this.getDefaultScanner(); } } closeInfo() { diff --git a/src/portal/src/app/base/navigator/navigator.component.html b/src/portal/src/app/base/navigator/navigator.component.html index 414b0b24b..a7e27251b 100644 --- a/src/portal/src/app/base/navigator/navigator.component.html +++ b/src/portal/src/app/base/navigator/navigator.component.html @@ -44,5 +44,16 @@ {{'ACCOUNT_SETTINGS.ABOUT' | translate}} + + + + + + + {{ theme.text | translate }} + + + diff --git a/src/portal/src/app/base/navigator/navigator.component.scss b/src/portal/src/app/base/navigator/navigator.component.scss index 7a2624c3b..6af4a8ada 100644 --- a/src/portal/src/app/base/navigator/navigator.component.scss +++ b/src/portal/src/app/base/navigator/navigator.component.scss @@ -67,4 +67,28 @@ } .dropdown-lang { padding-right: 1.3rem; -} \ No newline at end of file +} +.header { + .header-actions { + .theme-select { + width: 5.4rem; + display: flex; + align-items: center; + justify-content: flex-start; + height: 100%; + font-size: 14px; + letter-spacing: 1px; + cursor: pointer; + &:hover { + opacity: 1; + } + clr-icon { + position: static; + transform: none; + margin-right: .2rem; + min-width: 20px; + } + } + } +} + diff --git a/src/portal/src/app/base/navigator/navigator.component.ts b/src/portal/src/app/base/navigator/navigator.component.ts index e470799c8..35294c6e6 100644 --- a/src/portal/src/app/base/navigator/navigator.component.ts +++ b/src/portal/src/app/base/navigator/navigator.component.ts @@ -26,6 +26,10 @@ import { SearchTriggerService } from '../global-search/search-trigger.service'; import { MessageHandlerService } from '../../shared/message-handler/message-handler.service'; import { SkinableConfig } from "../../skinable-config.service"; import { CommonRoutes } from "../../../lib/entities/shared.const"; +import { ThemeInterface, themeArray } from '../../theme'; +import { clone } from '../../../lib/utils/utils'; +import { ThemeService } from '../../theme.service'; +const HAS_STYLE_MODE: string = 'styleModeLocal'; @Component({ selector: 'navigator', @@ -42,7 +46,9 @@ export class NavigatorComponent implements OnInit { appTitle: string = 'APP_TITLE.HARBOR'; customStyle: { [key: string]: any }; customProjectName: { [key: string]: any }; + themeArray: ThemeInterface[] = clone(themeArray); + styleMode = this.themeArray[0].showStyle; constructor( private session: SessionService, private router: Router, @@ -52,6 +58,7 @@ export class NavigatorComponent implements OnInit { private appConfigService: AppConfigService, private msgHandler: MessageHandlerService, private searchTrigger: SearchTriggerService, + public theme: ThemeService, private skinableConfig: SkinableConfig) { } @@ -79,6 +86,8 @@ export class NavigatorComponent implements OnInit { if (this.appConfigService.getConfig().read_only) { this.msgHandler.handleReadOnly(); } + // set local in app + this.styleMode = localStorage.getItem(HAS_STYLE_MODE); } public get isSessionValid(): boolean { @@ -187,4 +196,10 @@ export class NavigatorComponent implements OnInit { registryAction(): void { this.searchTrigger.closeSearch(true); } + + themeChanged(theme) { + this.styleMode = theme.mode; + this.theme.loadStyle(theme.toggleFileName); + localStorage.setItem(HAS_STYLE_MODE, this.styleMode); + } } diff --git a/src/portal/src/app/global-message/message.component.html b/src/portal/src/app/global-message/message.component.html index 5b6addbd3..9f70e1d71 100644 --- a/src/portal/src/app/global-message/message.component.html +++ b/src/portal/src/app/global-message/message.component.html @@ -1,5 +1,5 @@ -
- +
+
{{message}}
diff --git a/src/portal/src/app/interrogation-services/interrogation-services.component.html b/src/portal/src/app/interrogation-services/interrogation-services.component.html index c674f81da..fcce1c4f2 100644 --- a/src/portal/src/app/interrogation-services/interrogation-services.component.html +++ b/src/portal/src/app/interrogation-services/interrogation-services.component.html @@ -1,5 +1,5 @@

{{'SIDE_NAV.SYSTEM_MGMT.INTERROGATION_SERVICES' | translate}}

-