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 92d78a1..c3d9f7e 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 {
@@ -44,18 +38,18 @@
margin-bottom: 0;
}
-.filter-category-content {
- margin-left: 6px;
+.mat-card-48 {
+ height: 48px;
}
::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..c40b4f7 100644
--- a/frontend/src/app/components/library-overview/library-overview.component.ts
+++ b/frontend/src/app/components/library-overview/library-overview.component.ts
@@ -1,9 +1,26 @@
-import {AfterContentInit, AfterViewInit, Component, Input} from '@angular/core';
+import {AfterContentInit, Component} from '@angular/core';
import {GamesService} from "../../services/games.service";
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
import {GenreDto} from "../../models/dtos/GenreDto";
import {ThemeDto} from "../../models/dtos/ThemeDto";
-import {forkJoin, Observable} from "rxjs";
+import {firstValueFrom, forkJoin, Observable} from "rxjs";
+import {SortDirection} from "@angular/material/sort";
+import {PlayerPerspectiveDto} from "../../models/dtos/PlayerPerspectiveDto";
+import {ActivatedRoute, Params, Router} from "@angular/router";
+import {Location} from "@angular/common";
+import {HttpParams} from "@angular/common/http";
+
+class SortOption {
+ title: string;
+ field: string;
+ direction: SortDirection;
+
+ constructor(title: string, field: string, direction: SortDirection) {
+ this.title = title;
+ this.field = field;
+ this.direction = direction;
+ }
+}
@Component({
selector: 'app-gameserver-list',
@@ -12,27 +29,49 @@ import {forkJoin, Observable} from "rxjs";
})
export class LibraryOverviewComponent implements AfterContentInit {
+ defaultSortOption: SortOption = new SortOption("Title (A-Z)", "title", "asc");
+
+ sortOptions: SortOption[] = [
+ this.defaultSortOption,
+ new SortOption("Title (Z-A)", "title", "desc"),
+
+ new SortOption("Release (newest first)", "releaseDate", "desc"),
+ new SortOption("Release (oldest first)", "releaseDate", "asc"),
+
+ new SortOption("Added to library (newest first)", "addedToLibrary", "desc"),
+ new SortOption("Added to library (oldest first)", "addedToLibrary", "asc"),
+
+ new SortOption("Rating (highest first)", "totalRating", "desc"),
+ new SortOption("Rating (lowest first)", "totalRating", "asc")
+ ];
+
searchTerm: string = "";
+ selectedSortOption: SortOption = this.defaultSortOption;
offlineCoopFilterEnabled: boolean = false;
onlineCoopFilterEnabled: boolean = false;
lanSupportFilterEnabled: boolean = false;
activeThemeFilters: string[] = [];
activeGenreFilters: string[] = [];
+ activePlayerPerspectiveFilters: string[] = [];
games: DetectedGameDto[] = [];
availableGenres: GenreDto[] = [];
availableThemes: ThemeDto[] = [];
+ availablePlayerPerspectives: PlayerPerspectiveDto[] = [];
loading: boolean = true;
gameLibraryIsEmpty: boolean = false;
- constructor(private gameServerService: GamesService) {
+ constructor(private gameServerService: GamesService,
+ private route: ActivatedRoute,
+ private router: Router,
+ private location: Location) {
}
ngAfterContentInit(): void {
this.gameServerService.getAllGames().subscribe(
detectedGames => {
- if(detectedGames.length === 0) {
+ if (detectedGames.length === 0) {
this.gameLibraryIsEmpty = true;
this.loading = false;
return;
@@ -42,43 +81,80 @@ export class LibraryOverviewComponent implements AfterContentInit {
let genreObservable: Observable
= this.gameServerService.getAvailableGenres();
let themeObservable: Observable = this.gameServerService.getAvailableThemes();
+ let playerPerspectiveObservable: Observable = this.gameServerService.getAvailablePlayerPerspectives();
- forkJoin([themeObservable, genreObservable]).subscribe(result => {
- this.availableThemes = result[0];
- this.availableGenres = result[1];
- this.filterGames();
- this.loading = false;
+ forkJoin([genreObservable, themeObservable, playerPerspectiveObservable]).subscribe(result => {
+ this.availableGenres = result[0];
+ this.availableThemes = result[1];
+ this.availablePlayerPerspectives = result[2];
+
+ this.route.queryParams.subscribe(params => {
+ if (params['search'] !== undefined) this.searchTerm = params['search'];
+ if (params['sort'] !== undefined) this.selectedSortOption = this.matchSelectedSortOptionFromParam(params['sort']);
+ if (params['gamemodes'] !== undefined) this.setSelectedGamemodesFromParam(params['gamemodes']);
+ if (params['genres'] !== undefined) this.activeGenreFilters = this.matchSelectedFilters(this.availableGenres, params['genres']);
+ if (params['themes'] !== undefined) this.activeThemeFilters = this.matchSelectedFilters(this.availableThemes, params['themes']);
+ if (params['playerPerspectives'] !== undefined) this.activePlayerPerspectiveFilters = this.matchSelectedFilters(this.availablePlayerPerspectives, params['playerPerspectives']);
+
+ this.refreshLibraryView().then(() => this.loading = false);
+ });
});
}
);
}
- filterGames(): void {
- this.gameServerService.getAllGames().subscribe(games => {
- let filteredGames: DetectedGameDto[] = games;
+ async refreshLibraryView(): Promise {
+ let games: DetectedGameDto[] = await firstValueFrom(this.gameServerService.getAllGames());
+ this.games = this.sortGames(this.filterGames(games));
+ this.saveStateToRoute();
+ }
- if(this.searchTerm.trim().toLowerCase().length > 0) {
- filteredGames = filteredGames.filter(game => game.title.trim().toLowerCase().includes(this.searchTerm.trim().toLowerCase()));
- }
+ clearSearchTerm(): void {
+ this.searchTerm = "";
+ this.refreshLibraryView();
+ }
- if(this.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
- filteredGames = filteredGames.filter(game => (game.offlineCoop === this.offlineCoopFilterEnabled || game.onlineCoop === this.onlineCoopFilterEnabled || game.lanSupport === this.lanSupportFilterEnabled));
- }
+ filterGames(games: DetectedGameDto[]): DetectedGameDto[] {
+ if (this.searchTerm.trim().toLowerCase().length > 0) {
+ games = games.filter(game => game.title.trim().toLowerCase().includes(this.searchTerm.trim().toLowerCase()));
+ }
- if(this.activeGenreFilters.length > 0) {
- filteredGames = filteredGames.filter(game => this.activeGenreFilters.every(activeGenreFilter => game.genres?.map(g => g.slug).includes(activeGenreFilter)));
- }
+ if (this.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
+ games = games.filter(game => (game.offlineCoop === this.offlineCoopFilterEnabled || game.onlineCoop === this.onlineCoopFilterEnabled || game.lanSupport === this.lanSupportFilterEnabled));
+ }
- if(this.activeThemeFilters.length > 0) {
- filteredGames = filteredGames.filter(game => this.activeThemeFilters.every(activeThemeFilter => game.themes?.map(g => g.slug).includes(activeThemeFilter)));
- }
+ if (this.activeGenreFilters.length > 0) {
+ games = games.filter(game => this.activeGenreFilters.every(activeGenreFilter => game.genres?.map(g => g.slug).includes(activeGenreFilter)));
+ }
- this.games = filteredGames;
- })
+ if (this.activeThemeFilters.length > 0) {
+ games = games.filter(game => this.activeThemeFilters.every(activeThemeFilter => game.themes?.map(g => g.slug).includes(activeThemeFilter)));
+ }
+
+ if (this.activePlayerPerspectiveFilters.length > 0) {
+ games = games.filter(game => this.activePlayerPerspectiveFilters.every(activePlayerPerspectiveFilter => game.playerPerspectives?.map(g => g.slug).includes(activePlayerPerspectiveFilter)));
+ }
+
+ return games;
+ }
+
+ sortGames(games: DetectedGameDto[]): DetectedGameDto[] {
+ games = games.sort((g1, g2) => {
+ // @ts-ignore
+ let f1 = g1[this.selectedSortOption.field];
+ // @ts-ignore
+ let f2 = g2[this.selectedSortOption.field];
+
+ if (f1 > f2) return 1;
+ if (f1 < f2) return -1;
+ return 0;
+ });
+ if (this.selectedSortOption.direction === "desc") games = games.reverse();
+ return games;
}
toggleGenreFilter(slug: string): void {
- if(this.activeGenreFilters.includes(slug)) {
+ if (this.activeGenreFilters.includes(slug)) {
const index = this.activeGenreFilters.indexOf(slug, 0);
if (index > -1) {
@@ -89,11 +165,11 @@ export class LibraryOverviewComponent implements AfterContentInit {
this.activeGenreFilters.push(slug);
}
- this.filterGames();
+ this.refreshLibraryView();
}
toggleThemeFilter(slug: string) {
- if(this.activeThemeFilters.includes(slug)) {
+ if (this.activeThemeFilters.includes(slug)) {
const index = this.activeThemeFilters.indexOf(slug, 0);
if (index > -1) {
@@ -104,7 +180,67 @@ export class LibraryOverviewComponent implements AfterContentInit {
this.activeThemeFilters.push(slug);
}
- this.filterGames();
+ this.refreshLibraryView();
+ }
+
+ togglePlayerPerspectiveFilter(slug: string) {
+ if (this.activePlayerPerspectiveFilters.includes(slug)) {
+
+ const index = this.activePlayerPerspectiveFilters.indexOf(slug, 0);
+ if (index > -1) {
+ this.activePlayerPerspectiveFilters.splice(index, 1);
+ }
+
+ } else {
+ this.activePlayerPerspectiveFilters.push(slug);
+ }
+
+ this.refreshLibraryView();
+ }
+
+ private saveStateToRoute(): void {
+ let stateParams: Params = {};
+
+ if (this.searchTerm.trim().length > 0) stateParams['search'] = this.searchTerm;
+ if (this.selectedSortOption !== this.defaultSortOption) stateParams['sort'] = this.toParam(this.selectedSortOption);
+ if (this.getActiveGameModesFilters().length > 0) stateParams['gamemodes'] = this.getActiveGameModesFilters().join(',');
+ if (this.activeGenreFilters.length > 0) stateParams['genres'] = this.activeGenreFilters.join(',');
+ if (this.activeThemeFilters.length > 0) stateParams['themes'] = this.activeThemeFilters.join(',');
+ if (this.activePlayerPerspectiveFilters.length > 0) stateParams['playerPerspectives'] = this.activePlayerPerspectiveFilters.join(',');
+
+ const url = this.router.createUrlTree([], {relativeTo: this.route, queryParams: stateParams}).toString();
+ this.location.go(url);
+ }
+
+ private toParam(sortOption: SortOption): string {
+ return `${sortOption.field}_${sortOption.direction}`;
+ }
+
+ private matchSelectedSortOptionFromParam(sortParam: string): SortOption {
+ return this.sortOptions.find(s => sortParam === this.toParam(s)) ?? this.defaultSortOption;
+ }
+
+ private matchSelectedFilters(options: any[], paramString: string): string[] {
+ let params: string[] = paramString.split(",");
+ return options.filter(o => params.includes(o.slug)).map(o => o.slug);
+ }
+
+ private getActiveGameModesFilters(): string[] {
+ let activeFilters: string[] = [];
+
+ if (this.offlineCoopFilterEnabled) activeFilters.push('offlineCoop');
+ if (this.onlineCoopFilterEnabled) activeFilters.push('onlineCoop');
+ if (this.lanSupportFilterEnabled) activeFilters.push('lanSupport');
+
+ return activeFilters;
+ }
+
+ private setSelectedGamemodesFromParam(paramString: string): void {
+ let params: string[] = paramString.split(",");
+
+ if (params.includes('offlineCoop')) this.offlineCoopFilterEnabled = true;
+ if (params.includes('onlineCoop')) this.onlineCoopFilterEnabled = true;
+ if (params.includes('lanSupport')) this.lanSupportFilterEnabled = true;
}
}
diff --git a/frontend/src/app/layouts/navbar-layout/navbar-layout.component.ts b/frontend/src/app/layouts/navbar-layout/navbar-layout.component.ts
index 9ce31f1..397b20a 100644
--- a/frontend/src/app/layouts/navbar-layout/navbar-layout.component.ts
+++ b/frontend/src/app/layouts/navbar-layout/navbar-layout.component.ts
@@ -4,7 +4,7 @@ import {Component, OnInit} from '@angular/core';
selector: 'app-navbar-layout',
template: `
-
+
diff --git a/frontend/src/app/models/dtos/DetectedGameDto.ts b/frontend/src/app/models/dtos/DetectedGameDto.ts
index 543c091..ccb7bff 100644
--- a/frontend/src/app/models/dtos/DetectedGameDto.ts
+++ b/frontend/src/app/models/dtos/DetectedGameDto.ts
@@ -30,4 +30,5 @@ export class DetectedGameDto {
path!: string;
diskSize!: number;
confirmedMatch!: boolean | undefined;
+ addedToLibrary!: Date;
}
diff --git a/frontend/src/app/models/dtos/PlayerPerspectiveDto.ts b/frontend/src/app/models/dtos/PlayerPerspectiveDto.ts
index ff0ee82..e4f7e0d 100644
--- a/frontend/src/app/models/dtos/PlayerPerspectiveDto.ts
+++ b/frontend/src/app/models/dtos/PlayerPerspectiveDto.ts
@@ -1,4 +1,4 @@
export class PlayerPerspectiveDto {
slug!: string;
- name?: string;
+ name!: string;
}
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/games.service.ts b/frontend/src/app/services/games.service.ts
index c91f940..87bd6f4 100644
--- a/frontend/src/app/services/games.service.ts
+++ b/frontend/src/app/services/games.service.ts
@@ -6,6 +6,8 @@ import {DetectedGameDto} from "../models/dtos/DetectedGameDto";
import {GameOverviewDto} from "../models/dtos/GameOverviewDto";
import {GenreDto} from "../models/dtos/GenreDto";
import {ThemeDto} from "../models/dtos/ThemeDto";
+import {CompanyDto} from "../models/dtos/CompanyDto";
+import {PlayerPerspectiveDto} from "../models/dtos/PlayerPerspectiveDto";
@Injectable({
providedIn: 'root'
@@ -64,7 +66,7 @@ export class GamesService implements GamesApi {
return this.getAllGames().pipe(
map
(
games => {
- let availableThemesMap: Map = new Map;
+ let availableThemesMap: Map = new Map;
games.map(game => game.themes === undefined ? [] : game.themes).flat().forEach(theme => availableThemesMap.set(theme.slug, theme));
return Array.from(availableThemesMap.values()).sort((t1, t2) => t1.name.localeCompare(t2.name));
}
@@ -72,6 +74,20 @@ export class GamesService implements GamesApi {
);
}
+ // TODO: This method of removing duplicates is most certainly an anti-pattern in RxJS
+ // TODO: However, I did not get the 'distinct()' pipe to work properly, so I have to take another look in the future
+ getAvailablePlayerPerspectives(): Observable {
+ return this.getAllGames().pipe(
+ map(
+ games => {
+ let availablePlayerPerspectivesMap: Map = new Map;
+ games.map(game => game.playerPerspectives === undefined ? [] : game.playerPerspectives).flat().forEach(playerPerspective => availablePlayerPerspectivesMap.set(playerPerspective.slug, playerPerspective));
+ return Array.from(availablePlayerPerspectivesMap.values()).sort((t1, t2) => t1.name.localeCompare(t2.name));
+ }
+ )
+ );
+ }
+
downloadGame(slug: String): void {
window.open(`v1${this.apiPath}/game/${slug}/download`, '_top');
}
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 0000000..b6be344
Binary files /dev/null and b/frontend/src/assets/Gameyfin_Logo_256px_dark.png differ
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index 1b418e3..5ea48e4 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -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;
}
diff --git a/pom.xml b/pom.xml
index dab9593..fd45415 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
de.grimsi
gameyfin
- 1.0.1
+ 1.1.0
gameyfin
gameyfin