From ec68abb78f94711a99b1384464c3ac2bce0244db Mon Sep 17 00:00:00 2001 From: Anris Ceta <anris.ceta@mni.thm.de> Date: Sat, 13 Apr 2019 20:11:06 +0200 Subject: [PATCH] Add theme switch functionality --- src/app/app.component.html | 12 ++--- src/app/app.component.scss | 2 +- src/app/app.component.ts | 4 +- src/app/app.module.ts | 4 +- .../shared/header/header.component.html | 10 ++++ .../shared/header/header.component.ts | 16 ++++++- src/styles.scss | 7 +++ src/theme/_dark-theme.scss | 39 ++++++++++++++++ src/theme/_theme.scss | 7 --- src/theme/_variables.scss | 1 + src/theme/arsnova-theme.const.ts | 12 +++++ src/theme/theme.directive.ts | 46 +++++++++++++++++++ src/theme/theme.module.ts | 14 ++++++ src/theme/theme.service.ts | 20 ++++++++ 14 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 src/theme/_dark-theme.scss create mode 100644 src/theme/arsnova-theme.const.ts create mode 100644 src/theme/theme.directive.ts create mode 100644 src/theme/theme.module.ts create mode 100644 src/theme/theme.service.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index b86ef90e2..86bb7600d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,7 +1,7 @@ -<div fxLayout="column" fxFill> - <app-header></app-header> - <div fxFlex class="app-component"> - <router-outlet></router-outlet> + <div [ngClass]="header.themeClass" fxLayout="column" fxFill appTheme> + <app-header #header></app-header> + <div fxFlex class="app-component"> + <router-outlet></router-outlet> + </div> + <app-footer></app-footer> </div> - <app-footer></app-footer> -</div> diff --git a/src/app/app.component.scss b/src/app/app.component.scss index 23f30f882..526e030fe 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -2,7 +2,7 @@ .app-component { padding: 4%; - background-color: #b2dfdb; + background-color:var(--background-color); } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index aae1fa0a6..39c8d57d7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { ThemeService } from '../theme/theme.service'; @Component({ selector: 'app-root', @@ -8,7 +9,8 @@ import { TranslateService } from '@ngx-translate/core'; }) export class AppComponent { - constructor(private translationService: TranslateService) { + constructor(private translationService: TranslateService, + private themeService: ThemeService) { translationService.setDefaultLang(this.translationService.getBrowserLang()); sessionStorage.setItem('currentLang', this.translationService.getBrowserLang()); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 83b65385d..30c5ab13f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -26,6 +26,7 @@ import { MarkdownService, MarkedOptions } from 'ngx-markdown'; import { NewLandingComponent } from './components/home/new-landing/new-landing.component'; import { HomePageComponent } from './components/home/home-page/home-page.component'; import { AppConfig } from './app.config'; +import { ThemeModule } from '../theme/theme.module'; export function dialogClose(dialogResult: any) { } @@ -53,7 +54,8 @@ export function initializeApp(appConfig: AppConfig) { BrowserModule, BrowserAnimationsModule, EssentialsModule, - SharedModule + SharedModule, + ThemeModule ], providers: [ AppConfig, diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html index ad321a5fe..bcaf1c673 100644 --- a/src/app/components/shared/header/header.component.html +++ b/src/app/components/shared/header/header.component.html @@ -7,6 +7,16 @@ <span *ngIf="router.url !== '/home'" class="app-title" (click)="goToHomepage()">ARSnova</span> <span class="fill-remaining-space"></span> + + <mat-menu #themeMenu="matMenu" [overlapTrigger]="false"> + <button mat-menu-item (click)="changeTheme('')">{{ 'default' | translate }}</button> + <button mat-menu-item (click)="changeTheme('dark')">{{ 'dark' | translate }}</button> + </mat-menu> + + <button mat-icon-button [matMenuTriggerFor]="themeMenu"> + <mat-icon>palette</mat-icon> + </button> + <mat-menu #langMenu="matMenu" [overlapTrigger]="false"> <button mat-menu-item (click)="useLanguage('de')">{{ 'header.german' | translate }}</button> <button mat-menu-item (click)="useLanguage('en')">{{ 'header.english' | translate }}</button> diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts index b3bb28fb8..1ba5921eb 100644 --- a/src/app/components/shared/header/header.component.ts +++ b/src/app/components/shared/header/header.component.ts @@ -9,6 +9,7 @@ import { TranslateService } from '@ngx-translate/core'; import { LanguageService } from '../../../services/util/language.service'; import { MatDialog } from '@angular/material'; import { LoginComponent } from '../login/login.component'; +import { ThemeService } from '../../../../theme/theme.service'; @Component({ selector: 'app-header', @@ -17,6 +18,7 @@ import { LoginComponent } from '../login/login.component'; }) export class HeaderComponent implements OnInit { user: User; + themeClass = localStorage.getItem('classNameOfTheme'); constructor(public location: Location, private authenticationService: AuthenticationService, @@ -24,7 +26,9 @@ export class HeaderComponent implements OnInit { public router: Router, private translationService: TranslateService, private langService: LanguageService, - public dialog: MatDialog) { + public dialog: MatDialog, + private themeService: ThemeService + ) { } ngOnInit() { @@ -61,6 +65,16 @@ export class HeaderComponent implements OnInit { this.langService.langEmitter.emit(language); } + changeTheme(theme) { + this.themeClass = theme; + localStorage.setItem('classNameOfTheme', theme); + if (theme === '') { + this.themeService.setActiveThem('arsnovaTheme'); + } else { + this.themeService.setActiveThem(theme); + } + } + login(isDozent: boolean) { const dialogRef = this.dialog.open(LoginComponent, { width: '350px' diff --git a/src/styles.scss b/src/styles.scss index 7186b2044..fdfdd9810 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,4 +1,5 @@ @import 'theme/_variables.scss'; +@import "theme/_dark-theme.scss"; // Plus imports for other components in your app. // Include the common styles for Angular Material. We include this here so that you only @@ -11,4 +12,10 @@ // that you are using. @include angular-material-theme($arsnova-theme); + +.dark { + @include angular-material-theme($dark-theme); +} + + @import 'theme/_theme.scss'; diff --git a/src/theme/_dark-theme.scss b/src/theme/_dark-theme.scss new file mode 100644 index 000000000..b633de8a8 --- /dev/null +++ b/src/theme/_dark-theme.scss @@ -0,0 +1,39 @@ +@import '~@angular/material/theming'; + +html, body { + font-family: 'Roboto', 'Helvetica Neue', sans-serif; + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +.fill-remaining-space { + /* This fills the remaining space, by using flexbox. + Every toolbar row uses a flexbox row layout. */ + flex: 1 1 auto; +} + +mat-form-field.input-block { + display: block; +} + +.mat-fab .mat-button-wrapper { + padding: 0!important; +} + +.mat-dialog-container { + background-color: #e0f2f1; +} + +.mat-tab-header { + border-bottom-style: none!important; +} + + +$dark-primary: mat-palette($mat-gray); +$dark-accent: mat-palette($mat-blue-gray); +$dark-warn: mat-palette($mat-red); + +$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); + diff --git a/src/theme/_theme.scss b/src/theme/_theme.scss index 24c076011..33dd1ada5 100644 --- a/src/theme/_theme.scss +++ b/src/theme/_theme.scss @@ -11,10 +11,3 @@ html, body { @import '_util.scss'; @import '_form.scss'; -.progress-theme { - $progress-primary: mat-palette($mat-light-green, 300); - $progress-accent: mat-palette($mat-amber, 300); - $progress-warn: mat-palette($mat-deep-orange, 300); - $progress-theme: mat-light-theme($progress-primary, $progress-accent, $progress-warn); - @include angular-material-theme($progress-theme); -} diff --git a/src/theme/_variables.scss b/src/theme/_variables.scss index a4a274efc..18e71b8b2 100644 --- a/src/theme/_variables.scss +++ b/src/theme/_variables.scss @@ -10,3 +10,4 @@ $arsnova-warn: mat-palette($mat-red, A100); // Create the theme object (a Sass map containing all of the palettes). $arsnova-theme: mat-light-theme($arsnova-primary, $arsnova-accent, $arsnova-warn); + diff --git a/src/theme/arsnova-theme.const.ts b/src/theme/arsnova-theme.const.ts new file mode 100644 index 000000000..311968dcc --- /dev/null +++ b/src/theme/arsnova-theme.const.ts @@ -0,0 +1,12 @@ +export const themes = { + arsnovaTheme: { + '--button-color': '#80cbc4', + '--background-color': '#b2dfdb', + '--black-color' : '#000000' + }, + dark: { + '--button-color': '#80cbc4', + '--background-color': '#000000', + '--black-color' : '#000000' + } +}; diff --git a/src/theme/theme.directive.ts b/src/theme/theme.directive.ts new file mode 100644 index 000000000..41a493da2 --- /dev/null +++ b/src/theme/theme.directive.ts @@ -0,0 +1,46 @@ +import { Directive, ElementRef, Inject, OnDestroy, OnInit, Renderer2 } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { themes } from './arsnova-theme.const'; +import { ThemeService } from './theme.service'; +import { Subscription } from 'rxjs'; + +@Directive({ + selector: '[appTheme]' +}) + +export class ThemeDirective implements OnInit, OnDestroy { + + private themeName = 'arsnovaTheme'; + private themServiceSubscription: Subscription; + + constructor(private elementRef: ElementRef, + private renderer: Renderer2, + @Inject(DOCUMENT) private document: any, + private themService: ThemeService) { + } + + ngOnInit() { + this.updateTheme(this.themeName); + this.themService.getActiveTheme() + .subscribe(themeName => { + this.themeName = themeName; + this.updateTheme(this.themeName); + }); + } + + updateTheme(themeName: string) { + const them = themes[ themeName ]; + for (const key in them) { + if (them.hasOwnProperty(key)) { + this.renderer.setProperty(this.elementRef.nativeElement, key, them[key]); + this.document.body.style.setProperty(key, them[key]); + } + } + } + + ngOnDestroy() { + if (this.themServiceSubscription) { + this.themServiceSubscription.unsubscribe(); + } + } +} diff --git a/src/theme/theme.module.ts b/src/theme/theme.module.ts new file mode 100644 index 000000000..af6d3e09b --- /dev/null +++ b/src/theme/theme.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ThemeDirective } from './theme.directive'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule + ], + declarations: [ThemeDirective], + exports: [ThemeDirective], +}) +export class ThemeModule { } diff --git a/src/theme/theme.service.ts b/src/theme/theme.service.ts new file mode 100644 index 000000000..9c56dfb37 --- /dev/null +++ b/src/theme/theme.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService { + themeName = localStorage.getItem('classNameOfTheme'); + private activeThem = new BehaviorSubject(this.themeName); + + constructor() { } + + public getActiveTheme() { + return this.activeThem.asObservable(); + } + + public setActiveThem(name) { + this.activeThem.next(name); + } +} -- GitLab