mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Finishing the show details layout for the web app.
This commit is contained in:
parent
bc695560ae
commit
8d063489d9
@ -6,18 +6,21 @@ import { ShowDetailsComponent } from './show-details/show-details.component';
|
|||||||
import { NotFoundComponent } from './not-found/not-found.component';
|
import { NotFoundComponent } from './not-found/not-found.component';
|
||||||
import { ShowResolverService } from './services/show-resolver.service';
|
import { ShowResolverService } from './services/show-resolver.service';
|
||||||
import { LibraryResolverService } from './services/library-resolver.service';
|
import { LibraryResolverService } from './services/library-resolver.service';
|
||||||
|
import { PlayerComponent } from "./player/player.component";
|
||||||
|
import { StreamResolverService } from "./services/stream-resolver.service";
|
||||||
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
|
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
|
||||||
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
|
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
|
||||||
{ path: "shows/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
|
{ path: "shows/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
|
||||||
|
{ path: "watch/:show-slug/s:season-number/e:episode-number", component: PlayerComponent, resolve: { show: StreamResolverService } },
|
||||||
{ path: "**", component: NotFoundComponent }
|
{ path: "**", component: NotFoundComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
providers: [LibraryResolverService, ShowResolverService]
|
providers: [LibraryResolverService, ShowResolverService, StreamResolverService]
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule { }
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
<ul class="navbar-nav flex-row ml-auto">
|
<ul class="navbar-nav flex-row ml-auto">
|
||||||
<li class="nav-item icon">
|
<li class="nav-item icon">
|
||||||
<mat-icon>search</mat-icon>
|
<mat-icon data-toggle="tooltip" data-placement="top" title="Search">search</mat-icon>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="icon" routerLink="/login" routerLinkActive="active">
|
<a class="icon" routerLink="/login" routerLinkActive="active" data-toggle="tooltip" data-placement="top" title="Login">
|
||||||
<mat-icon>account_circle</mat-icon>
|
<mat-icon>account_circle</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -14,6 +14,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { EpisodesListComponent } from './episodes-list/episodes-list.component';
|
import { EpisodesListComponent } from './episodes-list/episodes-list.component';
|
||||||
|
import { PlayerComponent } from './player/player.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -22,7 +23,8 @@ import { EpisodesListComponent } from './episodes-list/episodes-list.component';
|
|||||||
NotFoundComponent,
|
NotFoundComponent,
|
||||||
BrowseComponent,
|
BrowseComponent,
|
||||||
ShowDetailsComponent,
|
ShowDetailsComponent,
|
||||||
EpisodesListComponent
|
EpisodesListComponent,
|
||||||
|
PlayerComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
&.date
|
&.date
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="episodes" id="episodes">
|
<div class="episodes" id="episodes">
|
||||||
<div class="episode" *ngFor="let episode of this.episodes" id="el-{{episode.episodeNumber}}">
|
<div class="episode" *ngFor="let episode of this.episodes" id="el-{{episode.episodeNumber}}">
|
||||||
<div class="img" [style.background-image]="sanitize(episode.thumb)">
|
<div class="img" [style.background-image]="sanitize(episode.thumb, true)">
|
||||||
<button mat-icon-button id="playBtn"><i class="material-icons playIcon">play_circle_outline</i></button>
|
<button mat-icon-button class="playBtn" ><i class="material-icons playIcon">play_circle_outline</i></button>
|
||||||
</div>
|
</div>
|
||||||
<p class="title">S{{episode.seasonNumber}}E{{episode.episodeNumber}} - {{episode.title}}</p>
|
<h6 *ngIf="episode.seasonNumber != 0; else elseBlock;" class="title">S{{episode.seasonNumber}}:E{{episode.episodeNumber}} - {{episode.title}}</h6>
|
||||||
|
<ng-template #elseBlock><h6 class="title">{{episode.title}}</h6></ng-template>
|
||||||
<p class="overview">{{episode.overview}}</p>
|
<p class="overview">{{episode.overview}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
|
|
||||||
.title
|
.title
|
||||||
{
|
{
|
||||||
|
padding-top: .2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -126,17 +127,17 @@
|
|||||||
.img
|
.img
|
||||||
{
|
{
|
||||||
outline: solid var(--accentColor);
|
outline: solid var(--accentColor);
|
||||||
|
|
||||||
|
.playBtn
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title
|
.title
|
||||||
{
|
{
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#playBtn
|
|
||||||
{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,4 +55,9 @@ export class EpisodesListComponent implements OnInit
|
|||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play(episode: Episode)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
Kyoo/ClientApp/src/app/player/player.component.html
Normal file
1
Kyoo/ClientApp/src/app/player/player.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>player works!</p>
|
0
Kyoo/ClientApp/src/app/player/player.component.scss
Normal file
0
Kyoo/ClientApp/src/app/player/player.component.scss
Normal file
25
Kyoo/ClientApp/src/app/player/player.component.spec.ts
Normal file
25
Kyoo/ClientApp/src/app/player/player.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PlayerComponent } from './player.component';
|
||||||
|
|
||||||
|
describe('PlayerComponent', () => {
|
||||||
|
let component: PlayerComponent;
|
||||||
|
let fixture: ComponentFixture<PlayerComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ PlayerComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PlayerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
15
Kyoo/ClientApp/src/app/player/player.component.ts
Normal file
15
Kyoo/ClientApp/src/app/player/player.component.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-player',
|
||||||
|
templateUrl: './player.component.html',
|
||||||
|
styleUrls: ['./player.component.scss']
|
||||||
|
})
|
||||||
|
export class PlayerComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
Kyoo/ClientApp/src/app/services/stream-resolver.service.ts
Normal file
34
Kyoo/ClientApp/src/app/services/stream-resolver.service.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StreamResolverService implements Resolve<Show>
|
||||||
|
{
|
||||||
|
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show>
|
||||||
|
{
|
||||||
|
let slug: string = route.paramMap.get("show-slug");
|
||||||
|
let season: number = parseInt(route.paramMap.get("season-number"));
|
||||||
|
let episode: number = parseInt(route.paramMap.get("episode-number"));
|
||||||
|
return this.http.get<Show>("api/watch/" + slug + "/s" + season + "/e" + episode).pipe(catchError((error: HttpErrorResponse) =>
|
||||||
|
{
|
||||||
|
console.log(error.status + " - " + error.message);
|
||||||
|
if (error.status == 404)
|
||||||
|
{
|
||||||
|
this.snackBar.open("Can't find this episode \"" + slug + "S" + season + ":E" + episode + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
||||||
|
}
|
||||||
|
return EMPTY;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
<img id="backdrop" class="backdrop" src="backdrop/{{this.show.slug}}" />
|
<div class="backdrop">
|
||||||
|
<img id="backdrop" src="backdrop/{{this.show.slug}}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="header container pt-sm-5">
|
<div class="header container pt-sm-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -53,9 +55,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<hr class="d-none d-sm-block">
|
<hr class="d-none d-sm-block">
|
||||||
<div class="col-3 d-none d-sm-block">
|
<div class="col-3 d-none d-sm-block">
|
||||||
<h3>Genres</h3>
|
<h3 style="opacity: .8;">Genres</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let genre of this.show.genres"><b><a routerLink="/genre/{{genre.slug}}">{{genre.name}}</a></b></li>
|
<li *ngFor="let genre of this.show.genres"><b><a class="genre" routerLink="/genre/{{genre.slug}}">{{genre.name}}</a></b></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -80,7 +82,7 @@
|
|||||||
<div class="people-container" id="peopleScroll">
|
<div class="people-container" id="peopleScroll">
|
||||||
<a class="people" *ngFor="let people of this.show.people" routerLink="/people/{{people.slug}}">
|
<a class="people" *ngFor="let people of this.show.people" routerLink="/people/{{people.slug}}">
|
||||||
<img [style.background-image]="getPeopleIcon(people.slug)" />
|
<img [style.background-image]="getPeopleIcon(people.slug)" />
|
||||||
<p class="name">{{people.name}}</p>
|
<h6 class="name">{{people.name}}</h6>
|
||||||
<p class="role">{{people.role}}</p>
|
<p class="role">{{people.role}}</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,22 +9,39 @@ a
|
|||||||
|
|
||||||
.backdrop
|
.backdrop
|
||||||
{
|
{
|
||||||
width: 100%;
|
|
||||||
margin-top: -68px;
|
margin-top: -68px;
|
||||||
max-height: 75vh;
|
position: relative;
|
||||||
object-fit: cover;
|
z-index: -1;
|
||||||
|
|
||||||
|
> img
|
||||||
|
{
|
||||||
|
width: 100%;
|
||||||
|
max-height: 75vh;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after
|
||||||
|
{
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.6) 100%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header
|
.header
|
||||||
{
|
{
|
||||||
@include media-breakpoint-up(sm)
|
@include media-breakpoint-up(sm)
|
||||||
{
|
{
|
||||||
margin-top: -14rem;
|
margin-top: -12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(md)
|
@include media-breakpoint-up(md)
|
||||||
{
|
{
|
||||||
margin-top: -14rem;
|
margin-top: -13rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg)
|
@include media-breakpoint-up(lg)
|
||||||
@ -98,10 +115,17 @@ a
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> div > p
|
||||||
|
{
|
||||||
|
opacity: .87;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overview
|
.overview
|
||||||
{
|
{
|
||||||
|
opacity: .87;
|
||||||
|
|
||||||
@include media-breakpoint-up(sm)
|
@include media-breakpoint-up(sm)
|
||||||
{
|
{
|
||||||
padding-top: 2.25rem;
|
padding-top: 2.25rem;
|
||||||
@ -116,6 +140,11 @@ hr
|
|||||||
height: inherit;
|
height: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.genre
|
||||||
|
{
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
|
||||||
.scroll-row
|
.scroll-row
|
||||||
{
|
{
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -229,7 +258,7 @@ hr
|
|||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
> p
|
> p, h6
|
||||||
{
|
{
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -239,7 +268,6 @@ hr
|
|||||||
|
|
||||||
&.role
|
&.role
|
||||||
{
|
{
|
||||||
opacity: 0.8;
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,15 @@ $body-bg: theme-color("primary");
|
|||||||
$body-color: theme-color("textPrimary");
|
$body-color: theme-color("textPrimary");
|
||||||
$font-family-base: "Roboto", Arial, sans-serif;
|
$font-family-base: "Roboto", Arial, sans-serif;
|
||||||
|
|
||||||
/*body
|
p
|
||||||
{
|
{
|
||||||
background-repeat: no-repeat;
|
opacity: .6;
|
||||||
background-attachment: fixed;
|
}
|
||||||
}*/
|
|
||||||
|
h6
|
||||||
|
{
|
||||||
|
opacity: .87;
|
||||||
|
}
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap";
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user