Implemented sorting for library overview

This commit is contained in:
grimsi
2022-08-15 15:49:19 +02:00
parent 9ff6d76cf2
commit 89a7d7ea81
5 changed files with 132 additions and 72 deletions
+40 -38
View File
@@ -50,6 +50,7 @@ import {MatAutocompleteModule} from "@angular/material/autocomplete";
import { NgModelChangeDebouncedDirective } from './directives/ng-model-change-debounced.directive';
import { FooterComponent } from './components/footer/footer.component';
import {MatExpansionModule} from "@angular/material/expansion";
import {MatSelectModule} from "@angular/material/select";
@NgModule({
declarations: [
@@ -70,44 +71,45 @@ import {MatExpansionModule} from "@angular/material/expansion";
NgModelChangeDebouncedDirective,
FooterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
FormsModule,
MatFormFieldModule,
MatCardModule,
MatTabsModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
MatDialogModule,
MatButtonModule,
MatInputModule,
FlexModule,
MatProgressSpinnerModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatSnackBarModule,
MatGridListModule,
FlexLayoutModule,
GridModule,
YouTubePlayerModule,
MatChipsModule,
MatTooltipModule,
MatSlideToggleModule,
MatCheckboxModule,
A11yModule,
MatTableFilterModule,
MatDividerModule,
MatListModule,
MatAutocompleteModule,
MatExpansionModule
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
FormsModule,
MatFormFieldModule,
MatCardModule,
MatTabsModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
MatDialogModule,
MatButtonModule,
MatInputModule,
FlexModule,
MatProgressSpinnerModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatSnackBarModule,
MatGridListModule,
FlexLayoutModule,
GridModule,
YouTubePlayerModule,
MatChipsModule,
MatTooltipModule,
MatSlideToggleModule,
MatCheckboxModule,
A11yModule,
MatTableFilterModule,
MatDividerModule,
MatListModule,
MatAutocompleteModule,
MatExpansionModule,
MatSelectModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
@@ -26,8 +26,9 @@
<div fxFlex.gt-md="0 1 15" fxLayout="column" fxLayoutGap="16px" fxLayoutAlign.lt-lg="start center"
fxFlex.lt-lg="100" [ngClass.gt-md]="'sticky'">
<mat-card fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="6px" style="height: 48px">
<button mat-icon-button *ngIf="searchTerm.length > 0" matTooltip="Clear search input" (click)="clearSearchTerm()">
<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()">
<mat-icon>close</mat-icon>
</button>
@@ -46,6 +47,15 @@
</mat-form-field>
</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-header>
<mat-panel-title fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="6px">
@@ -55,11 +65,14 @@
</mat-expansion-panel-header>
<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 [(ngModel)]="onlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Online Co-op
<mat-checkbox [(ngModel)]="onlineCoopFilterEnabled" (change)="refreshLibraryView()" color="primary">Online
Co-op
</mat-checkbox>
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="refreshLibraryView()" color="primary">LAN Support
<mat-checkbox [(ngModel)]="lanSupportFilterEnabled" (change)="refreshLibraryView()" color="primary">LAN
Support
</mat-checkbox>
</div>
</mat-expansion-panel>
@@ -38,6 +38,10 @@
margin-bottom: 0;
}
.mat-card-48 {
height: 48px;
}
::ng-deep .mat-checkbox-frame {
$config: mat.get-color-config($dark-theme);
$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 {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, 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({
selector: 'app-gameserver-list',
@@ -12,7 +25,24 @@ 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;
@@ -32,7 +62,7 @@ export class LibraryOverviewComponent implements AfterContentInit {
ngAfterContentInit(): void {
this.gameServerService.getAllGames().subscribe(
detectedGames => {
if(detectedGames.length === 0) {
if (detectedGames.length === 0) {
this.gameLibraryIsEmpty = true;
this.loading = false;
return;
@@ -46,15 +76,15 @@ export class LibraryOverviewComponent implements AfterContentInit {
forkJoin([themeObservable, genreObservable]).subscribe(result => {
this.availableThemes = result[0];
this.availableGenres = result[1];
this.refreshLibraryView();
this.loading = false;
this.refreshLibraryView().then(() => this.loading = false);
});
}
);
}
refreshLibraryView(): void {
this.filterGames();
async refreshLibraryView(): Promise<void> {
let games: DetectedGameDto[] = await firstValueFrom(this.gameServerService.getAllGames());
this.games = this.sortGames(this.filterGames(games));
}
clearSearchTerm(): void {
@@ -62,32 +92,43 @@ export class LibraryOverviewComponent implements AfterContentInit {
this.refreshLibraryView();
}
filterGames(): void {
this.gameServerService.getAllGames().subscribe(games => {
let filteredGames: DetectedGameDto[] = games;
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.searchTerm.trim().toLowerCase().length > 0) {
filteredGames = filteredGames.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.offlineCoopFilterEnabled || this.onlineCoopFilterEnabled || this.lanSupportFilterEnabled) {
filteredGames = filteredGames.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.activeGenreFilters.length > 0) {
filteredGames = filteredGames.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.activeThemeFilters.length > 0) {
filteredGames = filteredGames.filter(game => this.activeThemeFilters.every(activeThemeFilter => game.themes?.map(g => g.slug).includes(activeThemeFilter)));
}
return games;
}
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 {
if(this.activeGenreFilters.includes(slug)) {
if (this.activeGenreFilters.includes(slug)) {
const index = this.activeGenreFilters.indexOf(slug, 0);
if (index > -1) {
@@ -98,11 +139,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) {
@@ -113,7 +154,7 @@ export class LibraryOverviewComponent implements AfterContentInit {
this.activeThemeFilters.push(slug);
}
this.filterGames();
this.refreshLibraryView();
}
}
@@ -4,7 +4,7 @@ import {Component, OnInit} from '@angular/core';
selector: 'app-navbar-layout',
template: `
<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>
</div>
<div fxFlex>