WIP: Implement frontend

Implemented 1st version of game-detail-view
This commit is contained in:
grimsi
2022-07-24 14:53:24 +02:00
parent e11611bbe6
commit eab1cf629c
21 changed files with 1364 additions and 1162 deletions
@@ -2,7 +2,8 @@ gameyfin:
#root: C:\Projects\privat\gameyfin-library
root: \\NAS-Simon\Öffentlich\Spiele
cache: ${gameyfin.root}\.gameyfin\cache
db: ${gameyfin.root}\.gameyfin\db # Currently unused
#db: ${gameyfin.root}\.gameyfin\db
db: ./data
igdb:
api:
client-id: 23l3l5qshx4dwjuao6yb8jyf1qrd08
+1157 -1119
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -21,6 +21,7 @@
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0",
"@angular/youtube-player": "^14.1.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
+13 -5
View File
@@ -23,16 +23,19 @@ import {ErrorDialogComponent} from "./components/error-dialog/error-dialog.compo
import {MatDialogModule} from "@angular/material/dialog";
import {MatButtonModule} from "@angular/material/button";
import {MatInputModule} from "@angular/material/input";
import {FlexModule} from "@angular/flex-layout";
import {FlexLayoutModule, FlexModule, GridModule} from "@angular/flex-layout";
import {LibraryOverviewComponent} from './components/library-overview/library-overview.component';
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MatTableModule} from "@angular/material/table";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatSortModule} from "@angular/material/sort";
import { GameCoverComponent } from './components/game-cover/game-cover.component';
import { GameDetailViewComponent } from './components/game-detail-view/game-detail-view.component';
import {GameCoverComponent} from './components/game-cover/game-cover.component';
import {GameDetailViewComponent} from './components/game-detail-view/game-detail-view.component';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatGridListModule} from "@angular/material/grid-list";
import {GameScreenshotComponent} from './components/game-screenshot/game-screenshot.component';
import {YouTubePlayerModule} from "@angular/youtube-player";
import { GameVideoComponent } from './components/game-video/game-video.component';
@NgModule({
declarations: [
@@ -45,7 +48,9 @@ import {MatGridListModule} from "@angular/material/grid-list";
ErrorDialogComponent,
LibraryOverviewComponent,
GameCoverComponent,
GameDetailViewComponent
GameDetailViewComponent,
GameScreenshotComponent,
GameVideoComponent
],
imports: [
BrowserModule,
@@ -70,7 +75,10 @@ import {MatGridListModule} from "@angular/material/grid-list";
MatPaginatorModule,
MatSortModule,
MatSnackBarModule,
MatGridListModule
MatGridListModule,
FlexLayoutModule,
GridModule,
YouTubePlayerModule
],
providers: [
{
@@ -1,5 +1,4 @@
<div class="game-cover-container">
<a routerLink="/game/{{game.slug}}">
<img src="{{'v1/images/' + game.coverId}}" alt="Cover of {{game.title}}">
</a>
</div>
<a routerLink="/game/{{game.slug}}">
<div class="game-cover-container shine" [style.background-image]="'url(v1/images/' + game.coverId + ')'">
</div>
</a>
@@ -1,6 +1,11 @@
@use '@angular/material' as mat;
.game-cover-container img {
.game-cover-container {
height: 352px;
width: 264px;
background-repeat: no-repeat;
background-position: center bottom;
@include mat.elevation-transition();
@include mat.elevation(4);
@@ -9,3 +14,38 @@
@include mat.elevation(24);
}
}
.shine {
position: relative;
overflow: hidden;
&::before {
background: linear-gradient(
to right,
fade_out(#fff, 1) 0%,
fade_out(#fff, 0.7) 100%
);
content: "";
display: block;
height: 100%;
left: -100%;
position: absolute;
top: 0;
transform: skewX(-25deg);
width: 50%;
z-index: 2;
}
&:hover,
&:focus {
&::before {
animation: shine 0.85s;
}
}
@keyframes shine {
100% {
left: 125%;
}
}
}
@@ -1,5 +1,5 @@
import {Component, Input, OnInit} from '@angular/core';
import {GameOverviewDto} from "../../models/dtos/GameOverviewDto";
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
@Component({
selector: 'game-cover',
@@ -8,7 +8,7 @@ import {GameOverviewDto} from "../../models/dtos/GameOverviewDto";
})
export class GameCoverComponent implements OnInit {
@Input() game!: GameOverviewDto;
@Input() game!: DetectedGameDto;
constructor() {
}
@@ -1 +1,28 @@
<pre><code>{{game | json}}</code></pre>
<div fxLayout="row" fxLayoutAlign="center" style="margin-top: 16px;">
<div fxLayout="column" fxFlex="0 1 70" fxLayoutGap="16px" fxFlex.lt-xl="95">
<div fxLayout="row" fxLayoutGap="16px" style="background: aqua">
<img src="v1/images/{{game.coverId}}" alt="Game cover">
<div fxFlex="30" fxLayout="column" id="game-details" style="background: darkolivegreen">
<h2>{{game.title}}</h2>
<p id="game-summary">{{game.summary}}</p>
</div>
<div fxLayout="column" fxFlex style="background: coral">
</div>
</div>
<div fxLayout="column" fxLayoutGap="16px">
<h2>Screenshots</h2>
<div fxLayout="row wrap" fxLayoutGap="8px">
<div *ngFor="let screenshotId of game.screenshotIds">
<game-screenshot [screenshotId]="screenshotId"></game-screenshot>
</div>
</div>
<h2>Videos</h2>
<div fxLayout="row wrap" fxLayoutGap="8px">
<div *ngFor="let videoId of game.videoIds">
<game-video [videoId]="videoId" [width]="555" [height]="312"></game-video>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1 @@
<img src="v1/images/{{screenshotId}}" alt="Screenshot">
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GameScreenshotComponent } from './game-screenshot.component';
describe('GameScreenshotComponent', () => {
let component: GameScreenshotComponent;
let fixture: ComponentFixture<GameScreenshotComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ GameScreenshotComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(GameScreenshotComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,17 @@
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'game-screenshot',
templateUrl: './game-screenshot.component.html',
styleUrls: ['./game-screenshot.component.scss']
})
export class GameScreenshotComponent implements OnInit {
@Input() screenshotId!: string;
constructor() { }
ngOnInit(): void {
}
}
@@ -0,0 +1 @@
<youtube-player [videoId]="videoId" [width]="width" [height]="height"></youtube-player>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GameVideoComponent } from './game-video.component';
describe('GameVideoComponent', () => {
let component: GameVideoComponent;
let fixture: ComponentFixture<GameVideoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ GameVideoComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(GameVideoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,22 @@
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'game-video',
templateUrl: './game-video.component.html',
styleUrls: ['./game-video.component.scss']
})
export class GameVideoComponent implements OnInit {
@Input() videoId!: string;
@Input() height!: number;
@Input() width!: number;
constructor() { }
ngOnInit(): void {
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(tag);
}
}
@@ -1,4 +1,7 @@
<mat-toolbar role="heading">
<mat-toolbar style="position: sticky; top: 0; z-index: 99999">
<button mat-icon-button (click)="goToLibraryScreen()" *ngIf="notOnLibraryScreen()">
<mat-icon>home</mat-icon>
</button>
<span class="spacer"></span>
@@ -1,29 +1,34 @@
import {Component, OnInit} from '@angular/core';
import {NavMenuItem} from '../../models/objects/NavMenuItem';
import {Component} from '@angular/core';
import {LibraryService} from "../../services/library.service";
import {MatSnackBar} from '@angular/material/snack-bar';
import {timeInterval} from "rxjs";
import {ActivatedRoute, Router} from "@angular/router";
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
activeItem: NavMenuItem | undefined;
export class HeaderComponent {
constructor(private libraryService: LibraryService,
private snackBar: MatSnackBar) {
private snackBar: MatSnackBar,
private router: Router) {
}
ngOnInit() {
}
reloadLibrary() {
reloadLibrary(): void {
this.libraryService.scanLibrary().pipe(timeInterval()).subscribe({
next: value => this.snackBar.open(`Library scan completed in ${Math.trunc(value.interval/1000)} seconds.`),
next: value => this.snackBar.open(`Library scan completed in ${Math.trunc(value.interval / 1000)} seconds.`),
error: error => this.snackBar.open(`Error while scanning library: ${error}`)
})
}
goToLibraryScreen(): void {
this.router.navigate(['/']);
}
notOnLibraryScreen(): boolean {
return !(this.router.url === "/library");
}
}
@@ -14,12 +14,3 @@
.content {
padding: 16px;
}
.content > mat-card {
margin-bottom: 16px;
}
.column-label {
margin-right: 8px;
font-size: 1rem;
}
@@ -8,11 +8,14 @@
<h2>Your game library is empty!</h2>
</div>
<div class="content">
<div fxLayout="row wrap" fxLayoutGap="16px grid">
<div fxFlex="25% 1" fxFlex.xs="100%" fxFlex.sm="33%" *ngFor="let game of detectedGames">
<div class="content" fxLayout="row" fxFlexFill="100" *ngIf="!this.loading && this.detectedGames.length > 0">
<div fxFlex="10" fxHide fxShow.gt-md><!--SPACER--></div>
<div fxFlex="0 1 20"><!--FILTER--></div>
<div fxFlex fxLayout="row wrap" fxLayoutGap="16px grid">
<div *ngFor="let game of detectedGames">
<game-cover [game]="game"></game-cover>
</div>
</div>
<div fxFlex="0 1 10" fxHide fxShow.gt-lg><!--SPACER--></div>
</div>
</div>
@@ -1,7 +1,6 @@
import {AfterViewInit, Component} from '@angular/core';
import {GamesService} from "../../services/games.service";
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
import {GameOverviewDto} from "../../models/dtos/GameOverviewDto";
@Component({
selector: 'app-gameserver-list',
@@ -10,15 +9,15 @@ import {GameOverviewDto} from "../../models/dtos/GameOverviewDto";
})
export class LibraryOverviewComponent implements AfterViewInit {
detectedGames: GameOverviewDto[] = [];
detectedGames: DetectedGameDto[] = [];
loading: boolean = true;
constructor(private gameServerService: GamesService) {
}
ngAfterViewInit(): void {
this.gameServerService.getGameOverviews().subscribe(
(detectedGames: GameOverviewDto[]) => {
this.gameServerService.getAllGames().subscribe(
(detectedGames: DetectedGameDto[]) => {
this.detectedGames = detectedGames;
this.loading = false;
}