mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Implemented sorting for library overview
This commit is contained in:
@@ -50,6 +50,7 @@ import {MatAutocompleteModule} from "@angular/material/autocomplete";
|
|||||||
import { NgModelChangeDebouncedDirective } from './directives/ng-model-change-debounced.directive';
|
import { NgModelChangeDebouncedDirective } from './directives/ng-model-change-debounced.directive';
|
||||||
import { FooterComponent } from './components/footer/footer.component';
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
import {MatExpansionModule} from "@angular/material/expansion";
|
import {MatExpansionModule} from "@angular/material/expansion";
|
||||||
|
import {MatSelectModule} from "@angular/material/select";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -70,44 +71,45 @@ import {MatExpansionModule} from "@angular/material/expansion";
|
|||||||
NgModelChangeDebouncedDirective,
|
NgModelChangeDebouncedDirective,
|
||||||
FooterComponent
|
FooterComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
FlexModule,
|
FlexModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatGridListModule,
|
MatGridListModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
GridModule,
|
GridModule,
|
||||||
YouTubePlayerModule,
|
YouTubePlayerModule,
|
||||||
MatChipsModule,
|
MatChipsModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
MatSlideToggleModule,
|
MatSlideToggleModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
A11yModule,
|
A11yModule,
|
||||||
MatTableFilterModule,
|
MatTableFilterModule,
|
||||||
MatDividerModule,
|
MatDividerModule,
|
||||||
MatListModule,
|
MatListModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatExpansionModule
|
MatExpansionModule,
|
||||||
],
|
MatSelectModule
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
|||||||
@@ -26,8 +26,9 @@
|
|||||||
<div fxFlex.gt-md="0 1 15" fxLayout="column" fxLayoutGap="16px" fxLayoutAlign.lt-lg="start center"
|
<div fxFlex.gt-md="0 1 15" fxLayout="column" fxLayoutGap="16px" fxLayoutAlign.lt-lg="start center"
|
||||||
fxFlex.lt-lg="100" [ngClass.gt-md]="'sticky'">
|
fxFlex.lt-lg="100" [ngClass.gt-md]="'sticky'">
|
||||||
|
|
||||||
<mat-card fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="6px" style="height: 48px">
|
<mat-card fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="6px" class="mat-card-48">
|
||||||
<button mat-icon-button *ngIf="searchTerm.length > 0" matTooltip="Clear search input" (click)="clearSearchTerm()">
|
<button mat-icon-button *ngIf="searchTerm.length > 0" matTooltip="Clear search input"
|
||||||
|
(click)="clearSearchTerm()">
|
||||||
<mat-icon>close</mat-icon>
|
<mat-icon>close</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -46,6 +47,15 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-card fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="20px" class="mat-card-48">
|
||||||
|
<h3 class="filter-category-title" style="white-space: nowrap; padding-left: 6px">Sort by: </h3>
|
||||||
|
<mat-select [(value)]="selectedSortOption" (valueChange)="refreshLibraryView()">
|
||||||
|
<mat-option *ngFor="let sortOption of sortOptions" [value]="sortOption">
|
||||||
|
{{sortOption.title}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="6px">
|
<mat-panel-title fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="6px">
|
||||||
@@ -55,11 +65,14 @@
|
|||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<div fxLayout="column">
|
<div fxLayout="column">
|
||||||
<mat-checkbox [(ngModel)]="offlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Offline Co-op
|
<mat-checkbox [(ngModel)]="offlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Offline
|
||||||
|
Co-op
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<mat-checkbox [(ngModel)]="onlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Online Co-op
|
<mat-checkbox [(ngModel)]="onlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Online
|
||||||
|
Co-op
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="refreshLibraryView()" color="primary">LAN Support
|
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="refreshLibraryView()" color="primary">LAN
|
||||||
|
Support
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|||||||
@@ -38,6 +38,10 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-card-48 {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep .mat-checkbox-frame {
|
::ng-deep .mat-checkbox-frame {
|
||||||
$config: mat.get-color-config($dark-theme);
|
$config: mat.get-color-config($dark-theme);
|
||||||
$primary-palette: map.get($config, 'primary');
|
$primary-palette: map.get($config, 'primary');
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
import {AfterContentInit, AfterViewInit, Component, Input} from '@angular/core';
|
import {AfterContentInit, Component} from '@angular/core';
|
||||||
import {GamesService} from "../../services/games.service";
|
import {GamesService} from "../../services/games.service";
|
||||||
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
|
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
|
||||||
import {GenreDto} from "../../models/dtos/GenreDto";
|
import {GenreDto} from "../../models/dtos/GenreDto";
|
||||||
import {ThemeDto} from "../../models/dtos/ThemeDto";
|
import {ThemeDto} from "../../models/dtos/ThemeDto";
|
||||||
import {forkJoin, Observable} from "rxjs";
|
import {firstValueFrom, forkJoin, Observable, pipe} from "rxjs";
|
||||||
|
import {SortDirection} from "@angular/material/sort";
|
||||||
|
|
||||||
|
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({
|
@Component({
|
||||||
selector: 'app-gameserver-list',
|
selector: 'app-gameserver-list',
|
||||||
@@ -12,7 +25,24 @@ import {forkJoin, Observable} from "rxjs";
|
|||||||
})
|
})
|
||||||
export class LibraryOverviewComponent implements AfterContentInit {
|
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 = "";
|
searchTerm: string = "";
|
||||||
|
selectedSortOption: SortOption = this.defaultSortOption;
|
||||||
offlineCoopFilterEnabled: boolean = false;
|
offlineCoopFilterEnabled: boolean = false;
|
||||||
onlineCoopFilterEnabled: boolean = false;
|
onlineCoopFilterEnabled: boolean = false;
|
||||||
lanSupportFilterEnabled: boolean = false;
|
lanSupportFilterEnabled: boolean = false;
|
||||||
@@ -32,7 +62,7 @@ export class LibraryOverviewComponent implements AfterContentInit {
|
|||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.gameServerService.getAllGames().subscribe(
|
this.gameServerService.getAllGames().subscribe(
|
||||||
detectedGames => {
|
detectedGames => {
|
||||||
if(detectedGames.length === 0) {
|
if (detectedGames.length === 0) {
|
||||||
this.gameLibraryIsEmpty = true;
|
this.gameLibraryIsEmpty = true;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
return;
|
return;
|
||||||
@@ -46,15 +76,15 @@ export class LibraryOverviewComponent implements AfterContentInit {
|
|||||||
forkJoin([themeObservable, genreObservable]).subscribe(result => {
|
forkJoin([themeObservable, genreObservable]).subscribe(result => {
|
||||||
this.availableThemes = result[0];
|
this.availableThemes = result[0];
|
||||||
this.availableGenres = result[1];
|
this.availableGenres = result[1];
|
||||||
this.refreshLibraryView();
|
this.refreshLibraryView().then(() => this.loading = false);
|
||||||
this.loading = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLibraryView(): void {
|
async refreshLibraryView(): Promise<void> {
|
||||||
this.filterGames();
|
let games: DetectedGameDto[] = await firstValueFrom(this.gameServerService.getAllGames());
|
||||||
|
this.games = this.sortGames(this.filterGames(games));
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSearchTerm(): void {
|
clearSearchTerm(): void {
|
||||||
@@ -62,32 +92,43 @@ export class LibraryOverviewComponent implements AfterContentInit {
|
|||||||
this.refreshLibraryView();
|
this.refreshLibraryView();
|
||||||
}
|
}
|
||||||
|
|
||||||
filterGames(): void {
|
filterGames(games: DetectedGameDto[]): DetectedGameDto[] {
|
||||||
this.gameServerService.getAllGames().subscribe(games => {
|
if (this.searchTerm.trim().toLowerCase().length > 0) {
|
||||||
let filteredGames: DetectedGameDto[] = games;
|
games = games.filter(game => game.title.trim().toLowerCase().includes(this.searchTerm.trim().toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
if(this.searchTerm.trim().toLowerCase().length > 0) {
|
if (this.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
|
||||||
filteredGames = filteredGames.filter(game => game.title.trim().toLowerCase().includes(this.searchTerm.trim().toLowerCase()));
|
games = games.filter(game => (game.offlineCoop === this.offlineCoopFilterEnabled || game.onlineCoop === this.onlineCoopFilterEnabled || game.lanSupport === this.lanSupportFilterEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
|
if (this.activeGenreFilters.length > 0) {
|
||||||
filteredGames = filteredGames.filter(game => (game.offlineCoop === this.offlineCoopFilterEnabled || game.onlineCoop === this.onlineCoopFilterEnabled || game.lanSupport === this.lanSupportFilterEnabled));
|
games = games.filter(game => this.activeGenreFilters.every(activeGenreFilter => game.genres?.map(g => g.slug).includes(activeGenreFilter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.activeGenreFilters.length > 0) {
|
if (this.activeThemeFilters.length > 0) {
|
||||||
filteredGames = filteredGames.filter(game => this.activeGenreFilters.every(activeGenreFilter => game.genres?.map(g => g.slug).includes(activeGenreFilter)));
|
games = games.filter(game => this.activeThemeFilters.every(activeThemeFilter => game.themes?.map(g => g.slug).includes(activeThemeFilter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.activeThemeFilters.length > 0) {
|
return games;
|
||||||
filteredGames = filteredGames.filter(game => this.activeThemeFilters.every(activeThemeFilter => game.themes?.map(g => g.slug).includes(activeThemeFilter)));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.games = filteredGames;
|
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 {
|
toggleGenreFilter(slug: string): void {
|
||||||
if(this.activeGenreFilters.includes(slug)) {
|
if (this.activeGenreFilters.includes(slug)) {
|
||||||
|
|
||||||
const index = this.activeGenreFilters.indexOf(slug, 0);
|
const index = this.activeGenreFilters.indexOf(slug, 0);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -98,11 +139,11 @@ export class LibraryOverviewComponent implements AfterContentInit {
|
|||||||
this.activeGenreFilters.push(slug);
|
this.activeGenreFilters.push(slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterGames();
|
this.refreshLibraryView();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleThemeFilter(slug: string) {
|
toggleThemeFilter(slug: string) {
|
||||||
if(this.activeThemeFilters.includes(slug)) {
|
if (this.activeThemeFilters.includes(slug)) {
|
||||||
|
|
||||||
const index = this.activeThemeFilters.indexOf(slug, 0);
|
const index = this.activeThemeFilters.indexOf(slug, 0);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
@@ -113,7 +154,7 @@ export class LibraryOverviewComponent implements AfterContentInit {
|
|||||||
this.activeThemeFilters.push(slug);
|
this.activeThemeFilters.push(slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterGames();
|
this.refreshLibraryView();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {Component, OnInit} from '@angular/core';
|
|||||||
selector: 'app-navbar-layout',
|
selector: 'app-navbar-layout',
|
||||||
template: `
|
template: `
|
||||||
<div class="main-container" fxLayout="column">
|
<div class="main-container" fxLayout="column">
|
||||||
<div fxFlex="none" style="position: sticky; top: 0; z-index: 99999">
|
<div fxFlex="none" style="position: sticky; top: 0; z-index: 999">
|
||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
</div>
|
</div>
|
||||||
<div fxFlex>
|
<div fxFlex>
|
||||||
|
|||||||
Reference in New Issue
Block a user