diff --git a/frontend/src/app/components/library-overview/library-overview.component.scss b/frontend/src/app/components/library-overview/library-overview.component.scss
index d5703ae..28825b2 100644
--- a/frontend/src/app/components/library-overview/library-overview.component.scss
+++ b/frontend/src/app/components/library-overview/library-overview.component.scss
@@ -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;
}
diff --git a/frontend/src/app/components/library-overview/library-overview.component.ts b/frontend/src/app/components/library-overview/library-overview.component.ts
index 774ad40..e5b176f 100644
--- a/frontend/src/app/components/library-overview/library-overview.component.ts
+++ b/frontend/src/app/components/library-overview/library-overview.component.ts
@@ -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;
diff --git a/frontend/src/app/services/cookie.service.spec.ts b/frontend/src/app/services/cookie.service.spec.ts
new file mode 100644
index 0000000..43ea274
--- /dev/null
+++ b/frontend/src/app/services/cookie.service.spec.ts
@@ -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();
+ });
+});
diff --git a/frontend/src/app/services/cookie.service.ts b/frontend/src/app/services/cookie.service.ts
new file mode 100644
index 0000000..00af0e3
--- /dev/null
+++ b/frontend/src/app/services/cookie.service.ts
@@ -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));
+ }
+}
diff --git a/frontend/src/app/services/theming.service.spec.ts b/frontend/src/app/services/theming.service.spec.ts
new file mode 100644
index 0000000..f932a5e
--- /dev/null
+++ b/frontend/src/app/services/theming.service.spec.ts
@@ -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();
+ });
+});
diff --git a/frontend/src/app/services/theming.service.ts b/frontend/src/app/services/theming.service.ts
new file mode 100644
index 0000000..787718b
--- /dev/null
+++ b/frontend/src/app/services/theming.service.ts
@@ -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);
+ }
+}
diff --git a/frontend/src/app/theme/default-theme.scss b/frontend/src/app/themes/dark-theme.scss
similarity index 90%
rename from frontend/src/app/theme/default-theme.scss
rename to frontend/src/app/themes/dark-theme.scss
index 93993e4..e0a9323 100644
--- a/frontend/src/app/theme/default-theme.scss
+++ b/frontend/src/app/themes/dark-theme.scss
@@ -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,
diff --git a/frontend/src/app/themes/light-theme.scss b/frontend/src/app/themes/light-theme.scss
new file mode 100644
index 0000000..83897e1
--- /dev/null
+++ b/frontend/src/app/themes/light-theme.scss
@@ -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
+ )
+));
diff --git a/frontend/src/assets/Gameyfin_Logo_256px_dark.png b/frontend/src/assets/Gameyfin_Logo_256px_dark.png
new file mode 100644
index 0000000000000000000000000000000000000000..b6be344adb9bb372cf785587cb104d13ac9c76bc
GIT binary patch
literal 3006
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