Merge pull request #1040 from GNS3/fix_for_theming

GNS3 Web-UI - Dark Theme Not Working
This commit is contained in:
piotrpekala7 2021-02-22 11:00:02 -08:00 committed by GitHub
commit a0d88a937e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 163 additions and 171 deletions

View File

@ -47,16 +47,7 @@
"node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/notosans-fontface/css/notosans-fontface.min.css", "node_modules/notosans-fontface/css/notosans-fontface.min.css",
"src/styles.scss", "src/styles.scss",
{ "src/theme.scss"
"inject": true,
"input": "src/theme.scss",
"bundleName": "theme-default-dark"
},
{
"inject": true,
"input": "src/theme-light.scss",
"bundleName": "theme-default"
}
], ],
"scripts": [] "scripts": []
}, },

View File

@ -1,2 +1,4 @@
<router-outlet></router-outlet> <div [ngClass]="{dark: darkThemeEnabled, light: !darkThemeEnabled}">
<app-notification-box></app-notification-box> <router-outlet></router-outlet>
<app-notification-box></app-notification-box>
</div>

View File

@ -1,3 +1,11 @@
mat-menu-panel { mat-menu-panel {
min-height: 0px; min-height: 0px;
} }
.dark {
background: #263238!important;
}
.light {
background: white!important;
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, HostBinding, OnInit } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { ElectronService } from 'ngx-electron'; import { ElectronService } from 'ngx-electron';
@ -6,6 +6,7 @@ import { SettingsService } from './services/settings.service';
import { ThemeService } from './services/theme.service'; import { ThemeService } from './services/theme.service';
import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router'; import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
import { ProgressService } from './common/progress/progress.service'; import { ProgressService } from './common/progress/progress.service';
import { OverlayContainer} from '@angular/cdk/overlay';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -13,7 +14,10 @@ import { ProgressService } from './common/progress/progress.service';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
public darkThemeEnabled: boolean = false;
constructor( constructor(
private overlayContainer: OverlayContainer,
iconReg: MatIconRegistry, iconReg: MatIconRegistry,
sanitizer: DomSanitizer, sanitizer: DomSanitizer,
private settingsService: SettingsService, private settingsService: SettingsService,
@ -30,18 +34,29 @@ export class AppComponent implements OnInit {
}); });
} }
@HostBinding('class') componentCssClass;
ngOnInit(): void { ngOnInit(): void {
if (this.electronService.isElectronApp) { if (this.electronService.isElectronApp) {
this.settingsService.subscribe(settings => { this.settingsService.subscribe(settings => {
this.electronService.ipcRenderer.send('settings.changed', settings); this.electronService.ipcRenderer.send('settings.changed', settings);
}); });
} }
let theme = localStorage.getItem('theme');
if (theme === 'light') { this.applyTheme(this.themeService.savedTheme + '-theme');
this.themeService.setDarkMode(false); this.themeService.themeChanged.subscribe((event: string) => {
} else { this.applyTheme(event);
this.themeService.setDarkMode(true); });
} }
applyTheme(theme: string) {
if (theme === 'dark-theme') {
this.darkThemeEnabled = true;
} else {
this.darkThemeEnabled = false;
}
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
} }
checkEvent(routerEvent) : void { checkEvent(routerEvent) : void {

View File

@ -284,6 +284,7 @@ import { UpdatesService } from './services/updates.service';
import { ReportIssueComponent } from './components/help/report-issue/report-issue.component'; import { ReportIssueComponent } from './components/help/report-issue/report-issue.component';
import { AngularReactBrowserModule } from '@angular-react/core'; import { AngularReactBrowserModule } from '@angular-react/core';
import { FabDialogModule, FabButtonModule } from '@angular-react/fabric'; import { FabDialogModule, FabButtonModule } from '@angular-react/fabric';
import { OverlayContainer, OverlayModule } from '@angular/cdk/overlay';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -492,7 +493,8 @@ import { FabDialogModule, FabButtonModule } from '@angular-react/fabric';
DragDropModule, DragDropModule,
NgxChildProcessModule, NgxChildProcessModule,
MATERIAL_IMPORTS, MATERIAL_IMPORTS,
NgCircleProgressModule.forRoot() NgCircleProgressModule.forRoot(),
OverlayModule
], ],
providers: [ providers: [
SettingsService, SettingsService,

View File

@ -47,6 +47,10 @@ export class NotificationBoxComponent implements OnInit, OnDestroy {
if (!this.location.path().includes('nodes')) this.startTimer(); if (!this.location.path().includes('nodes')) this.startTimer();
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false; this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
this.themeService.themeChanged.subscribe((value: string) => {
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
});
} }
ngAfterViewInit() { ngAfterViewInit() {

View File

@ -172,7 +172,7 @@
</div> </div>
<div *ngIf="toolbarVisibility" id="show-menu-wrapper" [ngClass]="{lightTheme: isLightThemeEnabled, shadowed: !isProjectMapMenuVisible }"> <div *ngIf="toolbarVisibility" id="show-menu-wrapper" [ngClass]="{lightTheme: isLightThemeEnabled, shadowed: !isProjectMapMenuVisible }">
<button [ngClass]="{lightTheme: isLightThemeEnabled}" class="arrow-button" mat-icon-button (click)="showMenu()"><mat-icon class="unmarked">keyboard_arrow_right</mat-icon></button> <button [ngClass]="{lightTheme: isLightThemeEnabled, darkTheme: !isLightThemeEnabled}" class="arrow-button" mat-icon-button (click)="showMenu()"><mat-icon class="unmarked">keyboard_arrow_right</mat-icon></button>
</div> </div>
<div *ngIf="toolbarVisibility" id="menu-wrapper" [ngClass]="{lightTheme: isLightThemeEnabled, extended: isProjectMapMenuVisible }"> <div *ngIf="toolbarVisibility" id="menu-wrapper" [ngClass]="{lightTheme: isLightThemeEnabled, extended: isProjectMapMenuVisible }">

View File

@ -35,6 +35,10 @@ img {
color: black!important; color: black!important;
} }
.darkTheme {
color: white!important;
}
#show-menu-wrapper { #show-menu-wrapper {
position: fixed; position: fixed;
background: transparent; background: transparent;

View File

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation, ElementRef } from '@angular/core'; import { Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation, ElementRef, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Observable, Subject, Subscription, from } from 'rxjs'; import { Observable, Subject, Subscription, from } from 'rxjs';
@ -164,7 +164,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private notificationService: NotificationService, private notificationService: NotificationService,
private themeService: ThemeService, private themeService: ThemeService,
private title: Title, private title: Title,
private nodeConsoleService: NodeConsoleService private nodeConsoleService: NodeConsoleService,
private cd: ChangeDetectorRef
) {} ) {}
ngOnInit() { ngOnInit() {
@ -183,10 +184,16 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.addSubscriptions(); this.addSubscriptions();
this.addKeyboardListeners(); this.addKeyboardListeners();
this.themeService.themeChanged.subscribe((value: string) => {
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
});
} }
getSettings() { getSettings() {
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false; this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
this.cd.detectChanges();
this.settings = this.settingsService.getAll(); this.settings = this.settingsService.getAll();
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible; this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible; this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible;

View File

@ -1,67 +1,33 @@
import { Injectable, RendererFactory2, Renderer2, Inject, EventEmitter } from '@angular/core'; import { Injectable, EventEmitter } from '@angular/core';
import { Observable, BehaviorSubject, combineLatest } from 'rxjs'; import { Observable, BehaviorSubject} from 'rxjs';
import { DOCUMENT } from '@angular/common';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ThemeService { export class ThemeService {
private _mainTheme$: BehaviorSubject<string> = new BehaviorSubject('theme-default');
private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false); private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false);
private _renderer: Renderer2;
private head: HTMLElement;
private themeLinks: HTMLElement[] = [];
darkMode$: Observable<boolean> = this._darkMode$.asObservable(); darkMode$: Observable<boolean> = this._darkMode$.asObservable();
theme$: Observable<[string, boolean]>; theme$: Observable<[string, boolean]>;
public themeChanged = new EventEmitter<string>(); public themeChanged = new EventEmitter<string>();
public savedTheme: string = 'dark'; public savedTheme: string = 'dark';
constructor( constructor() {
rendererFactory: RendererFactory2, if (!localStorage.getItem('theme')) localStorage.setItem('theme', 'dark');
@Inject(DOCUMENT) document: Document this.savedTheme = localStorage.getItem('theme');
) {
this.head = document.head;
this._renderer = rendererFactory.createRenderer(null, null);
this.theme$ = combineLatest(this._mainTheme$, this._darkMode$);
this.theme$.subscribe(async ([mainTheme, darkMode]) => {
const cssExt = '.css';
const cssFilename = darkMode ? mainTheme + '-dark' + cssExt : mainTheme + cssExt;
await this.loadCss(cssFilename);
if (this.themeLinks.length == 2)
this._renderer.removeChild(this.head, this.themeLinks.shift());
})
} }
getActualTheme() { getActualTheme() {
return this.savedTheme; return this.savedTheme;
} }
setMainTheme(name: string) {
this._mainTheme$.next(name);
}
setDarkMode(value: boolean) { setDarkMode(value: boolean) {
this._darkMode$.next(value);
localStorage.removeItem('theme');
if (value) { if (value) {
this.savedTheme = 'dark'; this.savedTheme = 'dark';
this.themeChanged.emit(this.savedTheme); this.themeChanged.emit('dark-theme');
localStorage.setItem('theme', 'dark'); localStorage.setItem('theme', 'dark');
} else { } else {
this.savedTheme = 'light'; this.savedTheme = 'light';
this.themeChanged.emit(this.savedTheme); this.themeChanged.emit('light-theme');
localStorage.setItem('theme', 'light'); localStorage.setItem('theme', 'light');
} }
} }
private async loadCss(filename: string) {
return new Promise(resolve => {
const linkEl: HTMLElement = this._renderer.createElement('link');
this._renderer.setAttribute(linkEl, 'rel', 'stylesheet');
this._renderer.setAttribute(linkEl, 'type', 'text/css');
this._renderer.setAttribute(linkEl, 'href', filename);
this._renderer.setProperty(linkEl, 'onload', resolve);
this._renderer.appendChild(this.head, linkEl);
this.themeLinks = [...this.themeLinks, linkEl];
})
}
} }

View File

@ -1,70 +0,0 @@
@import '~@angular/material/theming';
@import '~material-design-icons/iconfont/material-icons.css';
@import '~typeface-roboto/index.css';
// Include non-theme styles for core.
@include mat-core();
// Define a theme.
$primary: mat-palette($mat-cyan, 700, 500, 900);
$accent: mat-palette($mat-blue, A200, A100, A700);
$theme: mat-light-theme($primary, $accent);
$light-palette: mat-palette($mat-blue, 900, A100, A400);
$light-background: mat-light-theme($light-palette, $primary);
$mat-light-theme-background: (
status-bar: black,
app-bar: map_get($mat-blue, 900),
background: white,
hover: rgba(white, 0.04),
card: white,
dialog: white,
disabled-button: $white-12-opacity,
raised-button: white,
focused-button: $white-6-opacity,
selected-button: map_get($mat-blue, 900),
selected-disabled-button: map_get($mat-blue, 800),
disabled-button-toggle: black,
unselected-chip: map_get($mat-blue, 700),
disabled-list-option: black
);
$theme: map-merge(
$theme,
(
background: $mat-light-theme-background
)
);
@include mat-core-theme($theme);
@include mat-autocomplete-theme($theme);
@include mat-button-theme($theme);
@include mat-button-toggle-theme($theme);
@include mat-card-theme($theme);
@include mat-checkbox-theme($theme);
@include mat-chips-theme($theme);
@include mat-table-theme($theme);
@include mat-datepicker-theme($theme);
@include mat-dialog-theme($theme);
@include mat-expansion-panel-theme($theme);
@include mat-form-field-theme($theme);
@include mat-grid-list-theme($theme);
@include mat-icon-theme($theme);
@include mat-input-theme($theme);
@include mat-list-theme($theme);
@include mat-menu-theme($theme);
@include mat-paginator-theme($theme);
@include mat-progress-bar-theme($theme);
@include mat-progress-spinner-theme($theme);
@include mat-radio-theme($theme);
@include mat-select-theme($theme);
@include mat-sidenav-theme($theme);
@include mat-slide-toggle-theme($theme);
@include mat-slider-theme($theme);
@include mat-stepper-theme($theme);
@include mat-tabs-theme($theme);
@include mat-toolbar-theme($light-background);
@include mat-tooltip-theme($theme);
@include mat-snack-bar-theme($theme);

View File

@ -1,16 +1,13 @@
@import '~@angular/material/theming'; @import '~@angular/material/theming';
@import '~material-design-icons/iconfont/material-icons.css'; @import '~material-design-icons/iconfont/material-icons.css';
@import '~typeface-roboto/index.css'; @import '~typeface-roboto/index.css';
// Include non-theme styles for core.
@include mat-core(); @include mat-core();
// Define a theme.
$primary: mat-palette($mat-cyan, 700, 500, 900); $primary: mat-palette($mat-cyan, 700, 500, 900);
$accent: mat-palette($mat-blue-grey, A200, A100, A700); $accent: mat-palette($mat-blue-grey, A200, A100, A700);
$theme: mat-dark-theme($primary, $accent); // Dark theme
$dark-theme: mat-dark-theme($primary, $accent);
$dark-palette: mat-palette($mat-blue-grey, 900, A100, A400); $dark-palette: mat-palette($mat-blue-grey, 900, A100, A400);
$dark-background: mat-dark-theme($dark-palette, $primary); $dark-background: mat-dark-theme($dark-palette, $primary);
@ -31,40 +28,106 @@ $mat-dark-theme-background: (
disabled-list-option: black disabled-list-option: black
); );
$theme: map-merge( $dark-theme: map-merge(
$theme, $dark-theme,
( (
background: $mat-dark-theme-background background: $mat-dark-theme-background
) )
); );
@include mat-core-theme($theme); .dark-theme {
@include mat-autocomplete-theme($theme); @include angular-material-theme($dark-theme);
@include mat-button-theme($theme); @include mat-core-theme($dark-theme);
@include mat-button-toggle-theme($theme); @include mat-autocomplete-theme($dark-theme);
@include mat-card-theme($theme); @include mat-button-theme($dark-theme);
@include mat-checkbox-theme($theme); @include mat-button-toggle-theme($dark-theme);
@include mat-chips-theme($theme); @include mat-card-theme($dark-theme);
@include mat-table-theme($theme); @include mat-checkbox-theme($dark-theme);
@include mat-datepicker-theme($theme); @include mat-chips-theme($dark-theme);
@include mat-dialog-theme($theme); @include mat-table-theme($dark-theme);
@include mat-expansion-panel-theme($theme); @include mat-datepicker-theme($dark-theme);
@include mat-form-field-theme($theme); @include mat-dialog-theme($dark-theme);
@include mat-grid-list-theme($theme); @include mat-expansion-panel-theme($dark-theme);
@include mat-icon-theme($theme); @include mat-form-field-theme($dark-theme);
@include mat-input-theme($theme); @include mat-grid-list-theme($dark-theme);
@include mat-list-theme($theme); @include mat-icon-theme($dark-theme);
@include mat-menu-theme($theme); @include mat-input-theme($dark-theme);
@include mat-paginator-theme($theme); @include mat-list-theme($dark-theme);
@include mat-progress-bar-theme($theme); @include mat-menu-theme($dark-theme);
@include mat-progress-spinner-theme($theme); @include mat-paginator-theme($dark-theme);
@include mat-radio-theme($theme); @include mat-progress-bar-theme($dark-theme);
@include mat-select-theme($theme); @include mat-progress-spinner-theme($dark-theme);
@include mat-sidenav-theme($theme); @include mat-radio-theme($dark-theme);
@include mat-slide-toggle-theme($theme); @include mat-select-theme($dark-theme);
@include mat-slider-theme($theme); @include mat-sidenav-theme($dark-theme);
@include mat-stepper-theme($theme); @include mat-slide-toggle-theme($dark-theme);
@include mat-tabs-theme($theme); @include mat-slider-theme($dark-theme);
@include mat-toolbar-theme($dark-background); @include mat-stepper-theme($dark-theme);
@include mat-tooltip-theme($theme); @include mat-tabs-theme($dark-theme);
@include mat-snack-bar-theme($theme); @include mat-toolbar-theme($dark-background);
@include mat-tooltip-theme($dark-theme);
@include mat-snack-bar-theme($dark-theme);
color: white!important;
}
// Light theme
$light-theme: mat-light-theme($primary, $accent);
$light-palette: mat-palette($mat-blue, 900, A100, A400);
$light-background: mat-light-theme($light-palette, $primary);
$mat-light-theme-background: (
status-bar: black,
app-bar: map_get($mat-blue, 900),
background: white,
hover: rgba(white, 0.04),
card: white,
dialog: white,
disabled-button: $white-12-opacity,
raised-button: white,
focused-button: $white-6-opacity,
selected-button: map_get($mat-blue, 900),
selected-disabled-button: map_get($mat-blue, 800),
disabled-button-toggle: black,
unselected-chip: map_get($mat-blue, 700),
disabled-list-option: black
);
$light-theme: map-merge(
$light-theme,
(
background: $mat-light-theme-background
)
);
.light-theme {
@include mat-core-theme($light-theme);
@include mat-autocomplete-theme($light-theme);
@include mat-button-theme($light-theme);
@include mat-button-toggle-theme($light-theme);
@include mat-card-theme($light-theme);
@include mat-checkbox-theme($light-theme);
@include mat-chips-theme($light-theme);
@include mat-table-theme($light-theme);
@include mat-datepicker-theme($light-theme);
@include mat-dialog-theme($light-theme);
@include mat-expansion-panel-theme($light-theme);
@include mat-form-field-theme($light-theme);
@include mat-grid-list-theme($light-theme);
@include mat-icon-theme($light-theme);
@include mat-input-theme($light-theme);
@include mat-list-theme($light-theme);
@include mat-menu-theme($light-theme);
@include mat-paginator-theme($light-theme);
@include mat-progress-bar-theme($light-theme);
@include mat-progress-spinner-theme($light-theme);
@include mat-radio-theme($light-theme);
@include mat-select-theme($light-theme);
@include mat-sidenav-theme($light-theme);
@include mat-slide-toggle-theme($light-theme);
@include mat-slider-theme($light-theme);
@include mat-stepper-theme($light-theme);
@include mat-tabs-theme($light-theme);
@include mat-toolbar-theme($light-background);
@include mat-tooltip-theme($light-theme);
@include mat-snack-bar-theme($light-theme);
}