Files
gameyfin/frontend/src/app/components/library-overview/library-overview.component.ts
T
Simon Grimme 165d10c4d5 Chips in game detail view are now clickable
Filters are now expanded if they are active at page load
Fixed bug where filters would be loaded twice, resulting in the user not being able to navigate back
2022-08-16 00:32:04 +02:00

250 lines
9.7 KiB
TypeScript

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 {firstValueFrom, forkJoin, Observable} from "rxjs";
import {SortDirection} from "@angular/material/sort";
import {PlayerPerspectiveDto} from "../../models/dtos/PlayerPerspectiveDto";
import {ActivatedRoute, ActivatedRouteSnapshot, Params, Router} from "@angular/router";
import {Location} from "@angular/common";
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',
templateUrl: './library-overview.component.html',
styleUrls: ['./library-overview.component.scss']
})
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;
private previousStateParams: Params = {};
constructor(private gameServerService: GamesService,
private route: ActivatedRoute,
private router: Router,
private location: Location) {
}
ngAfterContentInit(): void {
this.gameServerService.getAllGames().subscribe(
detectedGames => {
if (detectedGames.length === 0) {
this.gameLibraryIsEmpty = true;
this.loading = false;
return;
}
this.games = detectedGames;
let genreObservable: Observable<ThemeDto[]> = this.gameServerService.getAvailableGenres();
let themeObservable: Observable<GenreDto[]> = this.gameServerService.getAvailableThemes();
let playerPerspectiveObservable: Observable<PlayerPerspectiveDto[]> = this.gameServerService.getAvailablePlayerPerspectives();
forkJoin([genreObservable, themeObservable, playerPerspectiveObservable]).subscribe(result => {
this.availableGenres = result[0];
this.availableThemes = result[1];
this.availablePlayerPerspectives = result[2];
this.previousStateParams = this.route.snapshot.queryParams;
if (this.previousStateParams['search'] !== undefined) this.searchTerm = this.previousStateParams['search'];
if (this.previousStateParams['sort'] !== undefined) this.selectedSortOption = this.matchSelectedSortOptionFromParam(this.previousStateParams['sort']);
if (this.previousStateParams['gamemodes'] !== undefined) this.setSelectedGamemodesFromParam(this.previousStateParams['gamemodes']);
if (this.previousStateParams['genres'] !== undefined) this.activeGenreFilters = this.matchSelectedFilters(this.availableGenres, this.previousStateParams['genres']);
if (this.previousStateParams['themes'] !== undefined) this.activeThemeFilters = this.matchSelectedFilters(this.availableThemes, this.previousStateParams['themes']);
if (this.previousStateParams['playerPerspectives'] !== undefined) this.activePlayerPerspectiveFilters = this.matchSelectedFilters(this.availablePlayerPerspectives, this.previousStateParams['playerPerspectives']);
this.refreshLibraryView().then(() => this.loading = false);
});
}
);
}
async refreshLibraryView(): Promise<void> {
let games: DetectedGameDto[] = await firstValueFrom(this.gameServerService.getAllGames());
this.games = this.sortGames(this.filterGames(games));
this.saveStateToRoute();
}
clearSearchTerm(): void {
this.searchTerm = "";
this.refreshLibraryView();
}
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.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
games = games.filter(game => (game.offlineCoop === this.offlineCoopFilterEnabled || game.onlineCoop === this.onlineCoopFilterEnabled || game.lanSupport === this.lanSupportFilterEnabled));
}
if (this.activeGenreFilters.length > 0) {
games = games.filter(game => this.activeGenreFilters.every(activeGenreFilter => game.genres?.map(g => g.slug).includes(activeGenreFilter)));
}
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)) {
const index = this.activeGenreFilters.indexOf(slug, 0);
if (index > -1) {
this.activeGenreFilters.splice(index, 1);
}
} else {
this.activeGenreFilters.push(slug);
}
this.refreshLibraryView();
}
toggleThemeFilter(slug: string) {
if (this.activeThemeFilters.includes(slug)) {
const index = this.activeThemeFilters.indexOf(slug, 0);
if (index > -1) {
this.activeThemeFilters.splice(index, 1);
}
} else {
this.activeThemeFilters.push(slug);
}
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 newStateParams: Params = {};
if (this.searchTerm.trim().length > 0) newStateParams['search'] = this.searchTerm;
if (this.selectedSortOption !== this.defaultSortOption) newStateParams['sort'] = LibraryOverviewComponent.toParam(this.selectedSortOption);
if (this.getActiveGameModesFilters().length > 0) newStateParams['gamemodes'] = this.getActiveGameModesFilters().join(',');
if (this.activeGenreFilters.length > 0) newStateParams['genres'] = this.activeGenreFilters.join(',');
if (this.activeThemeFilters.length > 0) newStateParams['themes'] = this.activeThemeFilters.join(',');
if (this.activePlayerPerspectiveFilters.length > 0) newStateParams['playerPerspectives'] = this.activePlayerPerspectiveFilters.join(',');
// only update the route if it changed
if (JSON.stringify(this.previousStateParams) !== JSON.stringify(newStateParams)) {
const url = this.router.createUrlTree([], {relativeTo: this.route, queryParams: newStateParams}).toString();
this.previousStateParams = newStateParams;
this.location.go(url);
}
}
private static toParam(sortOption: SortOption): string {
return `${sortOption.field}_${sortOption.direction}`;
}
private matchSelectedSortOptionFromParam(sortParam: string): SortOption {
return this.sortOptions.find(s => sortParam === LibraryOverviewComponent.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;
}
}