diff --git a/Kyoo/ClientApp/src/app/app-routing.module.ts b/Kyoo/ClientApp/src/app/app-routing.module.ts index 06c73426..763aef18 100644 --- a/Kyoo/ClientApp/src/app/app-routing.module.ts +++ b/Kyoo/ClientApp/src/app/app-routing.module.ts @@ -1,8 +1,17 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import { BrowseComponent } from './browse/browse.component'; +import { ShowDetailsComponent } from './show-details/show-details.component'; +import { NotFoundComponent } from './not-found/not-found.component'; -const routes: Routes = []; + +const routes: Routes = [ + { path: "browse", component: BrowseComponent, pathMatch: "full" }, + { path: "browse/:library-slug", component: BrowseComponent }, + { path: "shows/:show-slug", component: ShowDetailsComponent }, + { path: "**", component: NotFoundComponent } +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/Kyoo/ClientApp/src/app/app.component.html b/Kyoo/ClientApp/src/app/app.component.html index 1c80b79c..310f8265 100644 --- a/Kyoo/ClientApp/src/app/app.component.html +++ b/Kyoo/ClientApp/src/app/app.component.html @@ -1,18 +1,37 @@ - + + +
diff --git a/Kyoo/ClientApp/src/app/app.component.scss b/Kyoo/ClientApp/src/app/app.component.scss index 0cbb2677..0ae6493c 100644 --- a/Kyoo/ClientApp/src/app/app.component.scss +++ b/Kyoo/ClientApp/src/app/app.component.scss @@ -3,15 +3,28 @@ justify-content: left; } -.navbar-nav +.nav-link { - flex-direction: row; + padding: 12px; } -.nav-item +.icon { - > a + padding: 8px; + display: inline-block; + + > svg { - padding: 16px; + fill: #d3d3d3; + } + + &:hover + { + cursor: pointer; + + > svg + { + fill: #ffffff; + } } } diff --git a/Kyoo/ClientApp/src/app/app.module.ts b/Kyoo/ClientApp/src/app/app.module.ts index 3f0338cb..19dc2c28 100644 --- a/Kyoo/ClientApp/src/app/app.module.ts +++ b/Kyoo/ClientApp/src/app/app.module.ts @@ -4,10 +4,16 @@ import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; +import { NotFoundComponent } from './not-found/not-found.component'; +import { BrowseComponent } from './browse/browse.component'; +import { ShowDetailsComponent } from './show-details/show-details.component'; @NgModule({ declarations: [ - AppComponent + AppComponent, + NotFoundComponent, + BrowseComponent, + ShowDetailsComponent ], imports: [ BrowserModule, diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.html b/Kyoo/ClientApp/src/app/browse/browse.component.html new file mode 100644 index 00000000..f05a7a86 --- /dev/null +++ b/Kyoo/ClientApp/src/app/browse/browse.component.html @@ -0,0 +1,8 @@ +
+ + +

{{show.title}}

+

{{show.startYear}} - {{show.endYear}}

+

{{show.startYear}}

+
+
diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.scss b/Kyoo/ClientApp/src/app/browse/browse.component.scss new file mode 100644 index 00000000..360e1c68 --- /dev/null +++ b/Kyoo/ClientApp/src/app/browse/browse.component.scss @@ -0,0 +1,54 @@ +@import "~bootstrap//scss/functions"; +@import "~bootstrap/scss/variables"; +@import "~bootstrap/scss//mixins/breakpoints"; + +.show +{ + width: 33%; + list-style: none; + padding: 1em; + display: inline-block; + text-decoration: none; + color: initial; + + @include media-breakpoint-up(md) + { + width: 25%; + } + + @include media-breakpoint-up(lg) + { + width: 20%; + } + + @include media-breakpoint-up(xl) + { + width: 18%; + } + + > img + { + max-width: 100%; + max-height: 100%; + } + + > p + { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + margin-bottom: 0px; + } + + &:hover + { + cursor: pointer; + } +} + +.date +{ + opacity: 0.8; + font-size: 0.8em; +} diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.spec.ts b/Kyoo/ClientApp/src/app/browse/browse.component.spec.ts new file mode 100644 index 00000000..6a60dfb5 --- /dev/null +++ b/Kyoo/ClientApp/src/app/browse/browse.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BrowseComponent } from './browse.component'; + +describe('BrowseComponent', () => { + let component: BrowseComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BrowseComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BrowseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Kyoo/ClientApp/src/app/browse/browse.component.ts b/Kyoo/ClientApp/src/app/browse/browse.component.ts new file mode 100644 index 00000000..ec67ea62 --- /dev/null +++ b/Kyoo/ClientApp/src/app/browse/browse.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; + +@Component({ + selector: 'app-browse', + templateUrl: './browse.component.html', + styleUrls: ['./browse.component.scss'] +}) +export class BrowseComponent implements OnInit +{ + shows: Show[]; + + private watch: any; + + constructor(private http: HttpClient, private route: ActivatedRoute) { } + + ngOnInit() + { + this.watch = this.route.params.subscribe(params => + { + var slug: string = params["library-slug"]; + + if (slug == null) + { + this.http.get("api/shows").subscribe(result => + { + this.shows = result; + }, error => console.log(error)); + } + else + { + this.http.get("api/library/" + slug).subscribe(result => + { + this.shows = result; + }, error => console.log(error)); + } + }); + } + + ngOnDestroy() + { + this.watch.unsubscribe(); + } +} diff --git a/Kyoo/ClientApp/src/app/not-found/not-found.component.html b/Kyoo/ClientApp/src/app/not-found/not-found.component.html new file mode 100644 index 00000000..28767b07 --- /dev/null +++ b/Kyoo/ClientApp/src/app/not-found/not-found.component.html @@ -0,0 +1,9 @@ +
+
+
+
+
+
+

404 Error

+

The page you requested was not found.

+
diff --git a/Kyoo/ClientApp/src/app/not-found/not-found.component.scss b/Kyoo/ClientApp/src/app/not-found/not-found.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/Kyoo/ClientApp/src/app/not-found/not-found.component.spec.ts b/Kyoo/ClientApp/src/app/not-found/not-found.component.spec.ts new file mode 100644 index 00000000..35189ed0 --- /dev/null +++ b/Kyoo/ClientApp/src/app/not-found/not-found.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotFoundComponent } from './not-found.component'; + +describe('NotFoundComponent', () => { + let component: NotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NotFoundComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Kyoo/ClientApp/src/app/not-found/not-found.component.ts b/Kyoo/ClientApp/src/app/not-found/not-found.component.ts new file mode 100644 index 00000000..8a117dc2 --- /dev/null +++ b/Kyoo/ClientApp/src/app/not-found/not-found.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-not-found', + templateUrl: './not-found.component.html', + styleUrls: ['./not-found.component.scss'] +}) +export class NotFoundComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.html b/Kyoo/ClientApp/src/app/show-details/show-details.component.html new file mode 100644 index 00000000..7dd1e78c --- /dev/null +++ b/Kyoo/ClientApp/src/app/show-details/show-details.component.html @@ -0,0 +1 @@ +

Should display show details of: {{this.show.title}}

diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.scss b/Kyoo/ClientApp/src/app/show-details/show-details.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.spec.ts b/Kyoo/ClientApp/src/app/show-details/show-details.component.spec.ts new file mode 100644 index 00000000..03ac65c1 --- /dev/null +++ b/Kyoo/ClientApp/src/app/show-details/show-details.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShowDetailsComponent } from './show-details.component'; + +describe('ShowDetailsComponent', () => { + let component: ShowDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ShowDetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ShowDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Kyoo/ClientApp/src/app/show-details/show-details.component.ts b/Kyoo/ClientApp/src/app/show-details/show-details.component.ts new file mode 100644 index 00000000..b9238c33 --- /dev/null +++ b/Kyoo/ClientApp/src/app/show-details/show-details.component.ts @@ -0,0 +1,36 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; + +@Component({ + selector: 'app-show-details', + templateUrl: './show-details.component.html', + styleUrls: ['./show-details.component.scss'] +}) +export class ShowDetailsComponent implements OnInit +{ + show: Show; + + private watch: any; + + constructor(private http: HttpClient, private route: ActivatedRoute) { } + + ngOnInit() + { + this.watch = this.route.params.subscribe(params => + { + var slug: string = params["show-slug"]; + + this.http.get("api/shows/" + slug).subscribe(result => + { + this.show = result; + }, error => console.log(error)); + }); + } + + ngOnDestroy() + { + this.watch.unsubscribe(); + } + +} diff --git a/Kyoo/ClientApp/src/models/show.ts b/Kyoo/ClientApp/src/models/show.ts new file mode 100644 index 00000000..8e0d0384 --- /dev/null +++ b/Kyoo/ClientApp/src/models/show.ts @@ -0,0 +1,21 @@ +interface Show +{ + id: number; + Slug: string; + title: string; + //IEnumerable < > Aliases; + Path: string; + Overview: string; + //IEnumerable < > Genres; + //Status ? Status; + + StartYear: number; + EndYear : number; + + ImgPrimary: string; + ImgThumb: string; + ImgLogo: string; + ImgBackdrop: string; + + ExternalIDs: string; +} diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index cf3d5040..dd9aaa56 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -46,40 +46,8 @@ namespace Kyoo.InternalAPI foreach (string file in files) { - if(IsVideo(file) && !libraryManager.IsEpisodeRegistered(file)) - { - string patern = config.GetValue("regex"); - Regex regex = new Regex(patern, RegexOptions.IgnoreCase); - Match match = regex.Match(file); - - string showPath = Path.GetDirectoryName(file); - string showName = match.Groups["ShowTitle"].Value; - bool seasonSuccess = long.TryParse(match.Groups["Season"].Value, out long seasonNumber); - bool episodeSucess = long.TryParse(match.Groups["Episode"].Value, out long episodeNumber); - - string showProviderIDs; - if (!libraryManager.IsShowRegistered(showPath, out long showID)) - { - Show show = await metadataProvider.GetShowFromName(showName, showPath); - showProviderIDs = show.ExternalIDs; - showID = libraryManager.RegisterShow(show); - } - else - showProviderIDs = libraryManager.GetShowExternalIDs(showID); - - if(!libraryManager.IsSeasonRegistered(showID, seasonNumber, out long seasonID)) - { - Season season = await metadataProvider.GetSeason(showName, seasonNumber); - season.ShowID = showID; - seasonID = libraryManager.RegisterSeason(season); - } - - Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber); - episode.ShowID = showID; - episode.SeasonID = seasonID; - episode.Path = file; - libraryManager.RegisterEpisode(episode); - } + if (IsVideo(file)) + await TryRegisterEpisode(file); } } @@ -111,6 +79,11 @@ namespace Kyoo.InternalAPI private void FileCreated(object sender, FileSystemEventArgs e) { Debug.WriteLine("&File Created at " + e.FullPath); + if (IsVideo(e.FullPath)) + { + Debug.WriteLine("&Created file is a video"); + _ = TryRegisterEpisode(e.FullPath); + } } private void FileChanged(object sender, FileSystemEventArgs e) @@ -129,6 +102,47 @@ namespace Kyoo.InternalAPI } + + private async Task TryRegisterEpisode(string path) + { + if (!libraryManager.IsEpisodeRegistered(path)) + { + string patern = config.GetValue("regex"); + Regex regex = new Regex(patern, RegexOptions.IgnoreCase); + Match match = regex.Match(path); + + string showPath = Path.GetDirectoryName(path); + string showName = match.Groups["ShowTitle"].Value; + bool seasonSuccess = long.TryParse(match.Groups["Season"].Value, out long seasonNumber); + bool episodeSucess = long.TryParse(match.Groups["Episode"].Value, out long episodeNumber); + + string showProviderIDs; + if (!libraryManager.IsShowRegistered(showPath, out long showID)) + { + Show show = await metadataProvider.GetShowFromName(showName, showPath); + showProviderIDs = show.ExternalIDs; + showID = libraryManager.RegisterShow(show); + } + else + showProviderIDs = libraryManager.GetShowExternalIDs(showID); + + if (!libraryManager.IsSeasonRegistered(showID, seasonNumber, out long seasonID)) + { + Season season = await metadataProvider.GetSeason(showName, seasonNumber); + season.ShowID = showID; + seasonID = libraryManager.RegisterSeason(season); + } + + Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber); + episode.ShowID = showID; + episode.SeasonID = seasonID; + episode.Path = path; + libraryManager.RegisterEpisode(episode); + } + } + + + private static readonly string[] videoExtensions = { ".webm", ".mkv", ".flv", ".vob", ".ogg", ".ogv", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".asf", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".3gp", ".3g2" }; private bool IsVideo(string filePath) diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 47ba434b..d869c34e 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -29,6 +29,14 @@ + + + + + + + +