mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 00:30:02 +00:00
247 lines
9.2 KiB
TypeScript
247 lines
9.2 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, 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',
|
|
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;
|
|
|
|
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.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);
|
|
});
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
}
|