mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
WIP: Implement frontend
Implemented 1st version of game-detail-view
This commit is contained in:
@@ -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
|
||||
|
||||
Generated
+1157
-1119
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user