Implementing seasons inside the web app.

Implementing episodes inside the API.
This commit is contained in:
Zoe Roux 2019-09-02 00:41:21 +02:00
parent 3a378a0ac9
commit d5358ad685
20 changed files with 183 additions and 15 deletions

View File

@ -11,7 +11,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
declarations: [
@ -28,7 +30,8 @@ import { MatIconModule } from '@angular/material/icon';
MatSnackBarModule,
MatProgressBarModule,
MatButtonModule,
MatIconModule
MatIconModule,
MatSelectModule
],
providers: [],
bootstrap: [AppComponent]

View File

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
import { Show } from "../../models/show";
@Component({
selector: 'app-browse',

View File

@ -3,6 +3,7 @@ import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/r
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Show } from "../../models/show";
import { MatSnackBar } from '@angular/material/snack-bar';

View File

@ -3,6 +3,7 @@ import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/r
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Show } from "../../models/show";
import { MatSnackBar } from '@angular/material/snack-bar';

View File

@ -13,6 +13,9 @@
<button mat-mini-fab data-toggle="tooltip" data-placement="top" title="Play" class="mr-3">
<mat-icon>play_arrow</mat-icon>
</button>
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Trailer">
<mat-icon>local_movies</mat-icon>
</button>
<button mat-icon-button data-toggle="tooltip" data-placement="top" title="Download">
<mat-icon>cloud_download</mat-icon>
</button>
@ -58,6 +61,19 @@
</div>
</div>
<div class="container-fluid">
<mat-form-field>
<mat-label>Season</mat-label>
<mat-select [(value)]="season">
<mat-option *ngFor="let season of this.show.seasons" [value]="season.seasonNumber">{{season.title}}</mat-option>
</mat-select>
</mat-form-field>
<p>You selected: {{season}}</p>
</div>
<!--<div class="container-fluid">
<div class="row">
<h3 class="col">Staff</h3>

View File

@ -29,12 +29,12 @@ a
@include media-breakpoint-up(lg)
{
margin-top: -20rem;
margin-top: -19rem;
}
@include media-breakpoint-up(xl)
{
margin-top: -24rem;
margin-top: -23rem;
}
}

View File

@ -1,6 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
import { Show } from "../../models/show";
import { Episode } from "../../models/episode";
import { HttpErrorResponse, HttpClient } from "@angular/common/http";
import { catchError } from "rxjs/operators";
import { MatSnackBar } from "@angular/material/snack-bar";
@Component({
selector: 'app-show-details',
@ -10,20 +15,32 @@ import { DomSanitizer } from '@angular/platform-browser';
export class ShowDetailsComponent implements OnInit
{
show: Show;
season;
private toolbar: HTMLElement
private backdrop: HTMLElement
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer) { }
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private http: HttpClient, private snackBar: MatSnackBar)
{
this.route.queryParams.subscribe(params =>
{
this.season = params["season"];
});
}
ngOnInit()
{
this.show = this.route.snapshot.data.show;
if (this.season == null || this.show.seasons.find(x => x.seasonNumber == this.season) == null)
this.season = this.show.seasons[0].seasonNumber;
this.toolbar = document.getElementById("toolbar");
this.backdrop = document.getElementById("backdrop");
window.addEventListener("scroll", this.scroll, true);
this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, 0) !important`);
this.getEpisodes();
}
ngOnDestroy()
@ -37,6 +54,22 @@ export class ShowDetailsComponent implements OnInit
this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, ${opacity}) !important`);
}
getEpisodes()
{
console.log("getting episodes");
this.http.get<Episode[]>("api/episodes/" + this.show.slug + "/season/" + this.season).subscribe((episodes: Episode[]) =>
{
console.log(episodes.length);
}, error =>
{
console.log(error.status + " - " + error.message);
this.snackBar.open("An unknow error occured while getting episodes.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
});
console.log("Episodes got");
}
getPeopleIcon(slug: string)

View File

@ -0,0 +1,9 @@
export interface Episode
{
episodeNumber: number;
title: string;
overview: string;
releaseDate;
runtimeInMinutes: number;
externalIDs: string;
}

View File

@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=season.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"season.js","sourceRoot":"","sources":["season.ts"],"names":[],"mappings":""}

View File

@ -0,0 +1,9 @@
import { Episode } from "./episode";
export interface Season
{
seasonNumber: number;
title: string;
overview: string;
episode: Episode[];
}

View File

@ -1 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=show.js.map

View File

@ -1,4 +1,6 @@
interface Show
import { Season } from "./season";
export interface Show
{
id: number;
slug: string;
@ -18,4 +20,6 @@ interface Show
imgBackdrop: string;
externalIDs: string;
seasons: Season[];
}

View File

@ -32,7 +32,7 @@ $font-family-base: "Roboto", Arial, sans-serif;
// Define the default theme (same as the example above).
$primary: (default: #0a1128);
$accent: (default: #e23c00, lighter: #ff9149);
$theme: mat-light-theme($primary, $accent);
$theme: mat-dark-theme($primary, $accent);
// Include the default theme styles.
@include angular-material-theme($theme);

View File

@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.InternalAPI;
using Kyoo.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@ -11,9 +13,33 @@ namespace Kyoo.Controllers
[ApiController]
public class EpisodesController : ControllerBase
{
public string Index()
private readonly ILibraryManager libraryManager;
public EpisodesController(ILibraryManager libraryManager)
{
return "Episode Test";
this.libraryManager = libraryManager;
}
[HttpGet("{showSlug}/season/{seasonNumber}")]
public ActionResult<IEnumerable<Episode>> GetEpisodesForSeason(string showSlug, long seasonNumber)
{
List<Episode> episodes = libraryManager.GetEpisodes(showSlug, seasonNumber);
if(episodes == null)
return NotFound();
return episodes;
}
[HttpGet("{showSlug}/season/{seasonNumber}/episode/{episodeNumber}")]
public ActionResult<Episode> GetEpisode(string showSlug, long seasonNumber, long episodeNumber)
{
Episode episode = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
if (episode == null)
return NotFound();
return episode;
}
}
}

View File

@ -2,7 +2,6 @@
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Diagnostics;
namespace Kyoo.Controllers
{

View File

@ -18,6 +18,8 @@ namespace Kyoo.InternalAPI
IEnumerable<Library> GetLibraries();
Show GetShowBySlug(string slug);
Season GetSeason(string showSlug, long seasonNumber);
List<Episode> GetEpisodes(string showSlug, long seasonNumber);
Episode GetEpisode(string showSlug, long seasonNumber, long episodeNumber);
People GetPeopleBySlug(string slug);
Genre GetGenreBySlug(string slug);
Studio GetStudioBySlug(string slug);

View File

@ -56,6 +56,7 @@ namespace Kyoo.InternalAPI
id INTEGER PRIMARY KEY UNIQUE,
showID INTEGER,
seasonID INTEGER,
seasonNumber INTEGER,
episodeNumber INTEGER,
path TEXT,
title TEXT,
@ -264,6 +265,43 @@ namespace Kyoo.InternalAPI
}
}
public List<Episode> GetEpisodes(string showSlug, long seasonNumber)
{
string query = "SELECT * FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber;";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue("$showSlug", showSlug);
cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber);
SQLiteDataReader reader = cmd.ExecuteReader();
List<Episode> episodes = new List<Episode>();
while (reader.Read())
episodes.Add(Episode.FromReader(reader));
return episodes;
}
}
public Episode GetEpisode(string showSlug, long seasonNumber, long episodeNumber)
{
string query = "SELECT * FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber;";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue("$showSlug", showSlug);
cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber);
cmd.Parameters.AddWithValue("$episodeNumber", episodeNumber);
SQLiteDataReader reader = cmd.ExecuteReader();
if (reader.Read())
return Episode.FromReader(reader);
else
return null;
}
}
public List<People> GetPeople(long showID)
{
string query = "SELECT people.id, people.slug, people.name, people.imgPrimary, people.externalIDs, l.role, l.type FROM people JOIN peopleLinks l ON l.peopleID = people.id WHERE l.showID = $showID;";
@ -550,11 +588,12 @@ namespace Kyoo.InternalAPI
public long RegisterEpisode(Episode episode)
{
string query = "INSERT INTO episodes (showID, seasonID, episodeNumber, path, title, overview, releaseDate, runtime, imgPrimary, externalIDs) VALUES($showID, $seasonID, $episodeNumber, $path, $title, $overview, $releaseDate, $runtime, $imgPrimary, $externalIDs);";
string query = "INSERT INTO episodes (showID, seasonID, seasonNumber, episodeNumber, path, title, overview, releaseDate, runtime, imgPrimary, externalIDs) VALUES($showID, $seasonID, $seasonNumber,$episodeNumber, $path, $title, $overview, $releaseDate, $runtime, $imgPrimary, $externalIDs);";
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
{
cmd.Parameters.AddWithValue("$showID", episode.ShowID);
cmd.Parameters.AddWithValue("$seasonID", episode.SeasonID);
cmd.Parameters.AddWithValue("$seasonNUmber", episode.seasonNumber);
cmd.Parameters.AddWithValue("$episodeNumber", episode.episodeNumber);
cmd.Parameters.AddWithValue("$path", episode.Path);
cmd.Parameters.AddWithValue("$title", episode.Title);

View File

@ -307,7 +307,7 @@ namespace Kyoo.InternalAPI.MetadataProvider
dynamic episode = data.data[index];
DateTime dateTime = DateTime.ParseExact((string)episode.firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture);
return new Episode(episodeNumber, (string)episode.episodeName, (string)episode.overview, dateTime, -1, "https://www.thetvdb.com/banners/" + episode.filename, string.Format("TvDB={0}|", episode.id));
return new Episode(seasonNumber, episodeNumber, (string)episode.episodeName, (string)episode.overview, dateTime, -1, "https://www.thetvdb.com/banners/" + episode.filename, string.Format("TvDB={0}|", episode.id));
}
}
else

View File

@ -9,11 +9,12 @@ namespace Kyoo.Models
[JsonIgnore] public long ShowID;
[JsonIgnore] public long SeasonID;
public long seasonNumber;
public long episodeNumber;
[JsonIgnore] public string Path;
public string Title;
public string Overview;
public DateTime ReleaseDate;
public DateTime? ReleaseDate;
public long Runtime; //This runtime variable should be in seconds (used by the video manager so we need precisions)
@ -31,11 +32,12 @@ namespace Kyoo.Models
public Episode() { }
public Episode(long episodeNumber, string title, string overview, DateTime releaseDate, long runtime, string imgPrimary, string externalIDs)
public Episode(long seasonNumber, long episodeNumber, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
{
id = -1;
ShowID = -1;
SeasonID = -1;
this.seasonNumber = seasonNumber;
this.episodeNumber = episodeNumber;
Title = title;
Overview = overview;
@ -45,11 +47,12 @@ namespace Kyoo.Models
ExternalIDs = externalIDs;
}
public Episode(long id, long showID, long seasonID, long episodeNumber, string path, string title, string overview, DateTime releaseDate, long runtime, string imgPrimary, string externalIDs)
public Episode(long id, long showID, long seasonID, long seasonNumber, long episodeNumber, string path, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
{
this.id = id;
ShowID = showID;
SeasonID = seasonID;
this.seasonNumber = seasonNumber;
this.episodeNumber = episodeNumber;
Path = path;
Title = title;
@ -59,5 +62,21 @@ namespace Kyoo.Models
ImgPrimary = imgPrimary;
ExternalIDs = externalIDs;
}
public static Episode FromReader(System.Data.SQLite.SQLiteDataReader reader)
{
return new Episode((long)reader["id"],
(long)reader["showID"],
(long)reader["seasonID"],
(long)reader["seasonNumber"],
(long)reader["episodeNumber"],
reader["path"] as string,
reader["title"] as string,
reader["overview"] as string,
reader["releaseDate"] as DateTime?,
(long)reader["runtime"],
reader["imgPrimary"] as string,
reader["externalIDs"] as string);
}
}
}