diff --git a/frontend/src/app/components/library-overview/library-overview.component.html b/frontend/src/app/components/library-overview/library-overview.component.html index 88a3c49..d741353 100644 --- a/frontend/src/app/components/library-overview/library-overview.component.html +++ b/frontend/src/app/components/library-overview/library-overview.component.html @@ -100,6 +100,18 @@ color="primary">{{theme.name}} + + + +

Player Perspectives

+
+ +
+ {{playerPerspective.name}} +
+
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 8e9c1b9..a8efcd1 100644 --- a/frontend/src/app/components/library-overview/library-overview.component.ts +++ b/frontend/src/app/components/library-overview/library-overview.component.ts @@ -5,6 +5,8 @@ import {GenreDto} from "../../models/dtos/GenreDto"; import {ThemeDto} from "../../models/dtos/ThemeDto"; import {firstValueFrom, forkJoin, Observable, pipe} from "rxjs"; import {SortDirection} from "@angular/material/sort"; +import {CompanyDto} from "../../models/dtos/CompanyDto"; +import {PlayerPerspectiveDto} from "../../models/dtos/PlayerPerspectiveDto"; class SortOption { title: string; @@ -48,10 +50,12 @@ export class LibraryOverviewComponent implements AfterContentInit { lanSupportFilterEnabled: boolean = false; activeThemeFilters: string[] = []; activeGenreFilters: string[] = []; + activePlayerPerspectiveFilters: string[] = []; games: DetectedGameDto[] = []; availableGenres: GenreDto[] = []; availableThemes: ThemeDto[] = []; + availablePlayerPerspectives: PlayerPerspectiveDto[] = []; loading: boolean = true; gameLibraryIsEmpty: boolean = false; @@ -72,10 +76,13 @@ export class LibraryOverviewComponent implements AfterContentInit { let genreObservable: Observable = this.gameServerService.getAvailableGenres(); let themeObservable: Observable = this.gameServerService.getAvailableThemes(); + let playerPerspectiveObservable: Observable = this.gameServerService.getAvailablePlayerPerspectives(); + + forkJoin([genreObservable, themeObservable, playerPerspectiveObservable]).subscribe(result => { + this.availableGenres = result[0]; + this.availableThemes = result[1]; + this.availablePlayerPerspectives = result[2]; - forkJoin([themeObservable, genreObservable]).subscribe(result => { - this.availableThemes = result[0]; - this.availableGenres = result[1]; this.refreshLibraryView().then(() => this.loading = false); }); } @@ -109,6 +116,10 @@ export class LibraryOverviewComponent implements AfterContentInit { 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; } @@ -157,4 +168,19 @@ export class LibraryOverviewComponent implements AfterContentInit { 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(); + } + } 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/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'); }