Refactored and improved dark-mode implementation

This commit is contained in:
grimsi
2022-08-15 14:31:44 +02:00
parent 1f24aa73e5
commit 9ff6d76cf2
17 changed files with 192 additions and 106 deletions
+1 -1
View File
@@ -121,7 +121,7 @@ import {MatExpansionModule} from "@angular/material/expansion";
},
{
provide: MAT_SNACK_BAR_DEFAULT_OPTIONS,
useValue: { panelClass: ['snackbar-dark'] },
useValue: { panelClass: ['formatted-snackbar'] },
}
],
bootstrap: [AppComponent]
@@ -1,9 +1,9 @@
@use 'sass:map';
@use '@angular/material' as mat;
@import '../../theme/default-theme';
@import 'src/app/themes/light-theme';
a {
$config: mat.get-color-config($custom-theme);
$config: mat.get-color-config($light-theme);
$primary-palette: map.get($config, 'primary');
color: mat.get-color-from-palette($primary-palette, 500);
}
@@ -5,7 +5,8 @@
<span class="spacer"></span>
<img class="logo" src="assets/Gameyfin_Logo_256px.png" alt="Gameyfin Logo">
<img *ngIf="document.body.style.colorScheme == 'dark'" class="logo" src="assets/Gameyfin_Logo_256px.png" alt="Gameyfin Logo">
<img *ngIf="document.body.style.colorScheme == 'light'" class="logo" src="assets/Gameyfin_Logo_256px_dark.png" alt="Gameyfin Logo">
<button mat-icon-button matTooltip="Reload library" (click)="reloadLibrary()" *ngIf="onLibraryScreen()">
<mat-icon>refresh</mat-icon>
@@ -1,9 +1,11 @@
import {Component} from '@angular/core';
import {Component, Inject} from '@angular/core';
import {LibraryService} from "../../services/library.service";
import {MatSnackBar} from '@angular/material/snack-bar';
import {timeInterval} from "rxjs";
import {Router} from "@angular/router";
import {GamesService} from "../../services/games.service";
import {ThemingService} from "../../services/theming.service";
import {DOCUMENT} from '@angular/common';
@Component({
selector: 'app-header',
@@ -12,43 +14,14 @@ import {GamesService} from "../../services/games.service";
})
export class HeaderComponent {
darkmodeEnabled: boolean;
// Maybe bad practice? IDK, but I need to access the document from the template of this component
document: Document = document;
constructor(private libraryService: LibraryService,
private gameService: GamesService,
private themingService: ThemingService,
private snackBar: MatSnackBar,
private router: Router) {
if(this.getCookie("darkmode") !== null) {
this.darkmodeEnabled = this.getCookie("darkmode") === "true";
} else
if (window.matchMedia) {
this.darkmodeEnabled = window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
this.darkmodeEnabled = false;
}
this.setTheme();
}
toggleTheme(): void {
this.darkmodeEnabled = !this.darkmodeEnabled;
this.setTheme();
}
private setTheme(): void {
this.darkmodeEnabled ? this.setDarkmode() : this.setLightmode();
this.setCookie("darkmode", this.darkmodeEnabled);
}
private setDarkmode(): void {
document.body.style.background = "#303030";
document.body.style.color = "white";
}
private setLightmode(): void {
document.body.style.background = "white";
document.body.style.color = "black";
}
scanLibrary(): void {
@@ -57,7 +30,7 @@ export class HeaderComponent {
// Refresh the current page "angular style"
this.router.navigate([this.router.url]).then(() =>
this.snackBar.open(`Library scan completed in ${Math.trunc(value.interval / 1000)} seconds.`, undefined, {duration: 5000})
)
)
},
error: error => this.snackBar.open(`Error while scanning library: ${error.error.message}`, undefined, {duration: 5000})
})
@@ -84,31 +57,7 @@ export class HeaderComponent {
return this.router.url === "/library-management";
}
private setCookie(name: string, value: any): void {
let d:Date = new Date();
document.cookie = `${name}=${value.toString()};`;
toggleTheme(): void {
this.themingService.toggleTheme();
}
private getCookie(name: string): string | null {
var dc = document.cookie;
var prefix = name + "=";
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
}
else
{
begin += 2;
var end = document.cookie.indexOf(";", begin);
if (end == -1) {
end = dc.length;
}
}
// because unescape has been deprecated, replaced with decodeURI
//return unescape(dc.substring(begin + prefix.length, end));
// @ts-ignore
return decodeURI(dc.substring(begin + prefix.length, end));
}
}
@@ -1,10 +1 @@
@use 'sass:map';
@use '@angular/material' as mat;
@import 'src/app/theme/default-theme';
@import 'src/app/components/library-overview/library-overview.component';
mat-tab-group {
$config: mat.get-color-config($custom-theme);
$background: map.get($config, background);
background: mat.get-color-from-palette($background, app-bar);
}
@@ -12,8 +12,8 @@ import {UnmappedFileDto} from "../../models/dtos/UnmappedFileDto";
export class LibraryManagementComponent implements OnInit {
loggedIn: boolean = false;
mappedGames!: DetectedGameDto[];
unmappedFiles!: UnmappedFileDto[];
mappedGames: DetectedGameDto[] = [];
unmappedFiles: UnmappedFileDto[] = [];
constructor(private gamesService: GamesService,
private libraryManagementService: LibraryManagementService) {
@@ -23,19 +23,28 @@
*ngIf="!this.loading && !this.gameLibraryIsEmpty">
<div fxFlex="10" fxHide fxShow.gt-md><!--SPACER--></div>
<div fxFlex.gt-md="0 1 15" fxLayout="column" fxLayoutGap="16px" fxLayoutAlign.lt-lg="start center" fxFlex.lt-lg="100" [ngClass.gt-md]="'sticky'">
<div fxFlex.gt-md="0 1 15" fxLayout="column" fxLayoutGap="16px" fxLayoutAlign.lt-lg="start center"
fxFlex.lt-lg="100" [ngClass.gt-md]="'sticky'">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="6px">
<mat-icon matTooltip="Search for games by title">search</mat-icon>
<mat-form-field fxFlex="100" class="filter-category-content">
<input type="text" matInput [matAutocomplete]="librarySearchAutocomplete" [(ngModel)]="searchTerm" (ngModelChange)="filterGames()">
<mat-card fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="6px" style="height: 48px">
<button mat-icon-button *ngIf="searchTerm.length > 0" matTooltip="Clear search input" (click)="clearSearchTerm()">
<mat-icon>close</mat-icon>
</button>
<button mat-icon-button *ngIf="searchTerm.length === 0" matTooltip="Search for games by title">
<mat-icon>search</mat-icon>
</button>
<mat-form-field fxFlex>
<input type="text" matInput [matAutocomplete]="librarySearchAutocomplete" [(ngModel)]="searchTerm"
(ngModelChange)="refreshLibraryView()">
<mat-autocomplete #librarySearchAutocomplete="matAutocomplete">
<mat-option *ngFor="let game of games" [value]="game.title">
{{game.title}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
</mat-card>
<mat-expansion-panel>
<mat-expansion-panel-header>
@@ -46,11 +55,11 @@
</mat-expansion-panel-header>
<div fxLayout="column">
<mat-checkbox [(ngModel)]="offlineCoopFilterEnabled" (change)="filterGames()" color="primary">Offline Co-op
<mat-checkbox [(ngModel)]="offlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Offline Co-op
</mat-checkbox>
<mat-checkbox [(ngModel)]="onlineCoopFilterEnabled" (change)="filterGames()" color="primary">Online Co-op
<mat-checkbox [(ngModel)]="onlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Online Co-op
</mat-checkbox>
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="filterGames()" color="primary">LAN Support
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="refreshLibraryView()" color="primary">LAN Support
</mat-checkbox>
</div>
</mat-expansion-panel>
@@ -80,7 +89,7 @@
</mat-expansion-panel>
</div>
<div fxFlex="0 1 1" fxHide fxShow.gt-lg><!--SPACER--></div>
<div fxFlex="0 1 1"><!--SPACER--></div>
<div fxFlex fxLayout="row wrap" fxLayoutGap="16px grid">
<div *ngFor="let game of games">
@@ -1,6 +1,6 @@
@use 'sass:map';
@use '@angular/material' as mat;
@import '../../theme/default-theme';
@import 'src/app/themes/dark-theme';
.fullscreen-overlay {
position: absolute;
@@ -19,21 +19,15 @@
@include mat.elevation(16);
$config: mat.get-color-config($custom-theme);
$background: map.get($config, background);
position: absolute;
right: 56px;
top: 72px;
width: 250px;
border-radius: 6px;
background: mat.get-color-from-palette($background, app-bar);
border-color: mat.get-color-from-palette($background, app-bar);
border-style: solid;
color: white;
p {
padding: 0 12px 12px 16px;
}
}
}
.content {
@@ -45,13 +39,13 @@
}
::ng-deep .mat-checkbox-frame {
$config: mat.get-color-config($custom-theme);
$config: mat.get-color-config($dark-theme);
$primary-palette: map.get($config, 'primary');
border-color: mat.get-color-from-palette($primary-palette, 500);
border-color: mat.get-color-from-palette($primary-palette, 500) !important;
}
::ng-deep .mat-form-field-underline {
$config: mat.get-color-config($custom-theme);
$config: mat.get-color-config($dark-theme);
$primary-palette: map.get($config, 'primary');
background-color: mat.get-color-from-palette($primary-palette, 500) !important;
}
@@ -46,13 +46,22 @@ export class LibraryOverviewComponent implements AfterContentInit {
forkJoin([themeObservable, genreObservable]).subscribe(result => {
this.availableThemes = result[0];
this.availableGenres = result[1];
this.filterGames();
this.refreshLibraryView();
this.loading = false;
});
}
);
}
refreshLibraryView(): void {
this.filterGames();
}
clearSearchTerm(): void {
this.searchTerm = "";
this.refreshLibraryView();
}
filterGames(): void {
this.gameServerService.getAllGames().subscribe(games => {
let filteredGames: DetectedGameDto[] = games;
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CookieService } from './cookie.service';
describe('CookieService', () => {
let service: CookieService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CookieService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,34 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CookieService {
constructor() {
}
setCookie(name: string, value: any): void {
document.cookie = `${name}=${value.toString()};`;
}
getCookie(name: string): string | null {
let end;
const dc = document.cookie;
const prefix = name + "=";
let begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else {
begin += 2;
end = document.cookie.indexOf(";", begin);
if (end == -1) {
end = dc.length;
}
}
return decodeURI(dc.substring(begin + prefix.length, end));
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ThemingService } from './theming.service';
describe('ThemingService', () => {
let service: ThemingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ThemingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,53 @@
import {Injectable} from '@angular/core';
import {OverlayContainer} from "@angular/cdk/overlay";
import {CookieService} from "./cookie.service";
@Injectable({
providedIn: 'root'
})
export class ThemingService {
private darkmodeEnabled!: boolean;
private darkmodeClassName: string = 'darkMode';
constructor(private cookieService: CookieService,
private overlay: OverlayContainer) {
if (this.cookieService.getCookie("darkmode") !== null) {
this.darkmodeEnabled = this.cookieService.getCookie("darkmode") === "true";
} else if (window.matchMedia) {
this.darkmodeEnabled = window.matchMedia('(prefers-color-scheme: dark)').matches;
} else {
this.darkmodeEnabled = false;
}
this.setTheme();
}
toggleTheme(): void {
this.darkmodeEnabled = !this.darkmodeEnabled;
this.setTheme();
}
private setTheme(): void {
this.darkmodeEnabled ? this.setDarkmode() : this.setLightmode();
this.cookieService.setCookie("darkmode", this.darkmodeEnabled);
}
private setDarkmode(): void {
document.body.classList.add(this.darkmodeClassName);
document.body.style.colorScheme = "dark";
document.body.style.background = "#303030";
document.body.style.color = "white";
this.overlay.getContainerElement().classList.add(this.darkmodeClassName);
}
private setLightmode(): void {
document.body.classList.remove(this.darkmodeClassName);
document.body.style.colorScheme = "light";
document.body.style.background = "white";
document.body.style.color = "black";
this.overlay.getContainerElement().classList.remove(this.darkmodeClassName);
}
}
@@ -5,7 +5,7 @@ $custom-theme-primary: mat.define-palette(mat.$green-palette);
$custom-theme-accent: mat.define-palette(mat.$grey-palette, A200, A100, A400);
$custom-theme-warn: mat.define-palette(mat.$red-palette);
$custom-theme: mat.define-dark-theme((
$dark-theme: mat.define-dark-theme((
color: (
primary: $custom-theme-primary,
accent: $custom-theme-accent,
+14
View File
@@ -0,0 +1,14 @@
@use '@angular/material' as mat;
@import "@angular/material/theming";
$custom-theme-primary: mat.define-palette(mat.$green-palette);
$custom-theme-accent: mat.define-palette(mat.$grey-palette, A200, A100, A400);
$custom-theme-warn: mat.define-palette(mat.$red-palette);
$light-theme: mat.define-light-theme((
color: (
primary: $custom-theme-primary,
accent: $custom-theme-accent,
warn: $custom-theme-warn
)
));
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+8 -8
View File
@@ -4,7 +4,8 @@
@use '@angular/material' as mat;
// Plus imports for other components in your app.
@import "src/app/theme/default-theme";
@import "src/app/themes/light-theme";
@import "src/app/themes/dark-theme";
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@@ -13,7 +14,11 @@
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include mat.all-component-themes($custom-theme);
@include mat.all-component-themes($light-theme);
.darkMode {
@include mat.all-component-colors($dark-theme);
}
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; }
@@ -23,12 +28,7 @@ html {
overflow-y: scroll;
}
.snackbar-dark {
color: white;
$config: mat.get-color-config($custom-theme);
$background: map.get($config, background);
background: mat.get-color-from-palette($background, app-bar);
.formatted-snackbar {
// add support for formatting (newlines)
white-space: pre-wrap;
}