Replacing spaces with tabs

This commit is contained in:
Zoe Roux 2020-02-11 23:17:23 +01:00
parent 03bf97dab6
commit a19bf9bce8
51 changed files with 1693 additions and 1697 deletions

View File

@ -1,35 +1,35 @@
<header id="nav" style="height: 68px;"> <header id="nav" style="height: 68px;">
<div class="fixed-top"> <div class="fixed-top">
<nav id="toolbar" class="navbar navbar-dark bg-secondary"> <nav id="toolbar" class="navbar navbar-dark bg-secondary">
<a class="navbar-brand nav-item ml-3" routerLink="/"> <a class="navbar-brand nav-item ml-3" routerLink="/">
Kyoo Kyoo
</a> </a>
<ul class="navbar-nav flex-row"> <ul class="navbar-nav flex-row">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" routerLink="/browse" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">All</a> <a class="nav-link" routerLink="/browse" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">All</a>
</li> </li>
<li class="nav-item" *ngFor="let library of this.libraries"> <li class="nav-item" *ngFor="let library of this.libraries">
<a class="nav-link" routerLink="/browse/{{library.slug}}" routerLinkActive="active">{{library.name}}</a> <a class="nav-link" routerLink="/browse/{{library.slug}}" routerLinkActive="active">{{library.name}}</a>
</li> </li>
</ul> </ul>
<ul class="navbar-nav flex-row ml-auto"> <ul class="navbar-nav flex-row ml-auto">
<li class="nav-item icon searchbar"> <li class="nav-item icon searchbar">
<input placeholder="Search" id="search" type="search" (input)="onUpdateValue($event)"/> <input placeholder="Search" id="search" type="search" (input)="onUpdateValue($event)"/>
<mat-icon matTooltipPosition="below" matTooltip="Search" (click)="openSearch()">search</mat-icon> <mat-icon matTooltipPosition="below" matTooltip="Search" (click)="openSearch()">search</mat-icon>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="icon" routerLink="/login" routerLinkActive="active" matTooltipPosition="below" matTooltip="Login"> <a class="icon" routerLink="/login" routerLinkActive="active" matTooltipPosition="below" matTooltip="Login">
<mat-icon>account_circle</mat-icon> <mat-icon>account_circle</mat-icon>
</a> </a>
</li> </li>
</ul> </ul>
</nav> </nav>
<mat-progress-bar *ngIf="this.isLoading" color="accent" mode="indeterminate"> </mat-progress-bar> <mat-progress-bar *ngIf="this.isLoading" color="accent" mode="indeterminate"> </mat-progress-bar>
</div> </div>
</header> </header>
<main > <main >
<router-outlet></router-outlet> <router-outlet></router-outlet>
</main> </main>

View File

@ -4,83 +4,83 @@
.navbar .navbar
{ {
justify-content: left; justify-content: left;
} }
.nav-item .nav-item
{ {
outline: none; outline: none;
> a > a
{ {
outline: none; outline: none;
color: inherit; color: inherit;
} }
} }
.nav-link .nav-link
{ {
padding: 12px; padding: 12px;
color: rgba(255, 255, 255, 0.7) !important; color: rgba(255, 255, 255, 0.7) !important;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
color: white !important; color: white !important;
} }
&.active &.active
{ {
color: var(--accentColor) !important; color: var(--accentColor) !important;
} }
} }
.navbar-brand:hover .navbar-brand:hover
{ {
color: var(--accentColor); color: var(--accentColor);
} }
.searchbar .searchbar
{ {
border-radius: 30px; border-radius: 30px;
> input > input
{ {
background: none !important; background: none !important;
color: white; color: white;
outline: none; outline: none;
border: none; border: none;
border-bottom: 1px solid #cfcfcf; border-bottom: 1px solid #cfcfcf;
width: 0; width: 0;
padding: 0; padding: 0;
transition: width 0.4s ease-in-out; transition: width 0.4s ease-in-out;
&:focus, &.searching &:focus, &.searching
{ {
width: 12rem; width: 12rem;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
width: 20rem; width: 20rem;
} }
} }
} }
} }
input::-webkit-search-cancel-button input::-webkit-search-cancel-button
{ {
display: none; display: none;
} }
.icon .icon
{ {
padding: 8px; padding: 8px;
display: inline-block; display: inline-block;
opacity: 0.7; opacity: 0.7;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
cursor: pointer; cursor: pointer;
opacity: 1; opacity: 1;
} }
} }

View File

@ -26,35 +26,35 @@ import { ShowsListComponent } from './shows-list/shows-list.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
NotFoundComponent, NotFoundComponent,
BrowseComponent, BrowseComponent,
ShowDetailsComponent, ShowDetailsComponent,
EpisodesListComponent, EpisodesListComponent,
PlayerComponent, PlayerComponent,
CollectionComponent, CollectionComponent,
SearchComponent, SearchComponent,
PeopleListComponent, PeopleListComponent,
ShowsListComponent ShowsListComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
HttpClientModule, HttpClientModule,
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
MatSnackBarModule, MatSnackBarModule,
MatProgressBarModule, MatProgressBarModule,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatSelectModule, MatSelectModule,
MatMenuModule, MatMenuModule,
MatSliderModule, MatSliderModule,
MatTooltipModule, MatTooltipModule,
MatRippleModule, MatRippleModule,
MatCardModule MatCardModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule { }

View File

@ -1,30 +1,30 @@
<div class="container-fluid justify-content-center"> <div class="container-fluid justify-content-center">
<button mat-icon-button matTooltipPosition="below" matTooltip="Filter"> <button mat-icon-button matTooltipPosition="below" matTooltip="Filter">
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button mat-button matTooltipPosition="below" matTooltip="Sort" [matMenuTriggerFor]="sortMenu"> <button mat-button matTooltipPosition="below" matTooltip="Sort" [matMenuTriggerFor]="sortMenu">
<mat-icon>sort</mat-icon> Sort by {{this.sortType}} <i *ngIf="this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="!this.sortUp" class="material-icons arrow">arrow_downward</i> <mat-icon>sort</mat-icon> Sort by {{this.sortType}} <i *ngIf="this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="!this.sortUp" class="material-icons arrow">arrow_downward</i>
</button> </button>
</div> </div>
<mat-menu #sortMenu="matMenu"> <mat-menu #sortMenu="matMenu">
<div *ngFor="let type of this.sortTypes"> <div *ngFor="let type of this.sortTypes">
<button *ngIf="type != this.sortType; else elseBlock;" mat-menu-item (click)="sort(type, true)"> <button *ngIf="type != this.sortType; else elseBlock;" mat-menu-item (click)="sort(type, true)">
Sort by {{type}} Sort by {{type}}
</button> </button>
<ng-template #elseBlock> <ng-template #elseBlock>
<button mat-menu-item (click)="sort(type, !this.sortUp)"> <button mat-menu-item (click)="sort(type, !this.sortUp)">
Sort by {{type}} <i *ngIf="!this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="this.sortUp" class="material-icons arrow">arrow_downward</i> Sort by {{type}} <i *ngIf="!this.sortUp" class="material-icons arrow">arrow_upward</i><i *ngIf="this.sortUp" class="material-icons arrow">arrow_downward</i>
</button> </button>
</ng-template> </ng-template>
</div> </div>
</mat-menu> </mat-menu>
<div class="container-fluid justify-content-center"> <div class="container-fluid justify-content-center">
<a class="show" *ngFor="let show of this.shows" [href]="getLink(show)" [routerLink]="getLink(show)"> <a class="show" *ngFor="let show of this.shows" [href]="getLink(show)" [routerLink]="getLink(show)">
<div matRipple [style.background-image]="getThumb(show.slug)" > </div> <div matRipple [style.background-image]="getThumb(show.slug)" > </div>
<p class="title">{{show.title}}</p> <p class="title">{{show.title}}</p>
<p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p> <p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template> <ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
</a> </a>
</div> </div>

View File

@ -4,93 +4,93 @@
button button
{ {
outline: none; outline: none;
} }
.arrow .arrow
{ {
font-size: 12px; font-size: 12px;
} }
.container-fluid .container-fluid
{ {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.show .show
{ {
width: 33%; width: 33%;
min-width: 120px; min-width: 120px;
max-width: 200px; max-width: 200px;
list-style: none; list-style: none;
padding: .5em; padding: .5em;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
outline: none; outline: none;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
width: 25%; width: 25%;
} }
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
width: 20%; width: 20%;
padding: 1em; padding: 1em;
} }
@include media-breakpoint-up(lg) @include media-breakpoint-up(lg)
{ {
width: 18%; width: 18%;
} }
@include media-breakpoint-up(xl) @include media-breakpoint-up(xl)
{ {
width: 15%; width: 15%;
} }
&:focus, &:hover &:focus, &:hover
{ {
> div > div
{ {
outline: solid var(--accentColor); outline: solid var(--accentColor);
} }
> .title > .title
{ {
text-decoration: underline; text-decoration: underline;
} }
} }
> div > div
{ {
width: 100%; width: 100%;
height: 0; height: 0;
padding-top: 147.0588%; padding-top: 147.0588%;
background-size: cover; background-size: cover;
background-color: #333333; background-color: #333333;
} }
> p > p
{ {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center; text-align: center;
margin-bottom: 0px; margin-bottom: 0px;
opacity: 1; opacity: 1;
&.date &.date
{ {
opacity: 0.8; opacity: 0.8;
font-size: 0.8em; font-size: 0.8em;
} }
} }
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
cursor: pointer; cursor: pointer;
} }
} }

View File

@ -4,17 +4,17 @@ import { DomSanitizer } from '@angular/platform-browser';
import { Show } from "../../models/show"; import { Show } from "../../models/show";
@Component({ @Component({
selector: 'app-browse', selector: 'app-browse',
templateUrl: './browse.component.html', templateUrl: './browse.component.html',
styleUrls: ['./browse.component.scss'] styleUrls: ['./browse.component.scss']
}) })
export class BrowseComponent export class BrowseComponent
{ {
@Input() shows: Show[]; @Input() shows: Show[];
sortType: string = "title"; sortType: string = "title";
sortUp: boolean = true; sortUp: boolean = true;
sortTypes: string[] = ["title", "release date"]; sortTypes: string[] = ["title", "release date"];
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer) constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer)
{ {
@ -24,37 +24,37 @@ export class BrowseComponent
}); });
} }
getThumb(slug: string) getThumb(slug: string)
{ {
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")"); return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
} }
getLink(show: Show) getLink(show: Show)
{ {
if (show.isCollection) if (show.isCollection)
return "/collection/" + show.slug; return "/collection/" + show.slug;
else else
return "/show/" + show.slug; return "/show/" + show.slug;
} }
sort(type: string, order: boolean) sort(type: string, order: boolean)
{ {
this.sortType = type; this.sortType = type;
this.sortUp = order; this.sortUp = order;
if (type == this.sortTypes[0]) if (type == this.sortTypes[0])
{ {
if (order) if (order)
this.shows.sort((a, b) => { if (a.title < b.title) return -1; else if (a.title > b.title) return 1; return 0; }); this.shows.sort((a, b) => { if (a.title < b.title) return -1; else if (a.title > b.title) return 1; return 0; });
else else
this.shows.sort((a, b) => { if (a.title < b.title) return 1; else if (a.title > b.title) return -1; return 0; }); this.shows.sort((a, b) => { if (a.title < b.title) return 1; else if (a.title > b.title) return -1; return 0; });
} }
else if (type == this.sortTypes[1]) else if (type == this.sortTypes[1])
{ {
if (order) if (order)
this.shows.sort((a, b) => a.startYear - b.startYear); this.shows.sort((a, b) => a.startYear - b.startYear);
else else
this.shows.sort((a, b) => b.startYear - a.startYear); this.shows.sort((a, b) => b.startYear - a.startYear);
} }
} }
} }

View File

@ -1,14 +1,14 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-4 col-lg-3 col-xl-2 collection-info"> <div class="col-md-4 col-lg-3 col-xl-2 collection-info">
<div [style.background-image]="getThumb()"></div> <div [style.background-image]="getThumb()"></div>
</div> </div>
<div class="col-md-8 col-lg-9 col-xl-10"> <div class="col-md-8 col-lg-9 col-xl-10">
<h3 class="text-center text-md-left p-2 p-md-3">{{collection.name}}</h3> <h3 class="text-center text-md-left p-2 p-md-3">{{collection.name}}</h3>
<h5 class="date" *ngIf="collection.endYear; else elseBlock">{{collection.startYear}} - {{collection.endYear}}</h5> <h5 class="date" *ngIf="collection.endYear; else elseBlock">{{collection.startYear}} - {{collection.endYear}}</h5>
<ng-template #elseBlock><h5 class="date">{{collection.startYear}}</h5></ng-template> <ng-template #elseBlock><h5 class="date">{{collection.startYear}}</h5></ng-template>
<hr /> <hr />
<app-browse [shows]="collection.shows"></app-browse> <app-browse [shows]="collection.shows"></app-browse>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,23 +1,23 @@
.collection-info .collection-info
{ {
width: 60%; width: 60%;
> div > div
{ {
width: 100%; width: 100%;
height: 0; height: 0;
padding-top: 147.0588%; padding-top: 147.0588%;
background-size: cover; background-size: cover;
background-color: #333333; background-color: #333333;
margin: 10px; margin: 10px;
} }
} }
hr hr
{ {
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
border-top: 1px solid rgba(255, 255, 255, .60); border-top: 1px solid rgba(255, 255, 255, .60);
border-left: 0; border-left: 0;
width: inherit; width: inherit;
height: 2px; height: 2px;
} }

View File

@ -4,13 +4,13 @@ import { ActivatedRoute } from "@angular/router";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
@Component({ @Component({
selector: 'app-collection', selector: 'app-collection',
templateUrl: './collection.component.html', templateUrl: './collection.component.html',
styleUrls: ['./collection.component.scss'] styleUrls: ['./collection.component.scss']
}) })
export class CollectionComponent export class CollectionComponent
{ {
collection: Collection; collection: Collection;
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer) constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer)
{ {

View File

@ -1,21 +1,21 @@
<div class="root"> <div class="root">
<div class="episodes" #scrollView (scroll)="onScroll()"> <div class="episodes" #scrollView (scroll)="onScroll()">
<a class="episode" *ngFor="let episode of this.episodes" #episodeDom routerLink="/watch/{{episode.link}}" href="/watch/{{episode.link}}"> <a class="episode" *ngFor="let episode of this.episodes" #episodeDom routerLink="/watch/{{episode.link}}" href="/watch/{{episode.link}}">
<div matRipple class="img" [style.background-image]="sanitize(episode.thumb)"> <div matRipple class="img" [style.background-image]="sanitize(episode.thumb)">
<button mat-icon-button class="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>
<ng-container *ngIf="displayShowTitle; else noTitle;"> <ng-container *ngIf="displayShowTitle; else noTitle;">
<h6 *ngIf="episode.seasonNumber != 0; else elseBlock;" class="title">{{episode.showTitle}} - S{{episode.seasonNumber}}:E{{episode.episodeNumber}}</h6> <h6 *ngIf="episode.seasonNumber != 0; else elseBlock;" class="title">{{episode.showTitle}} - S{{episode.seasonNumber}}:E{{episode.episodeNumber}}</h6>
<ng-template #elseBlock><h6 class="title">{{episode.showTitle}}</h6></ng-template> <ng-template #elseBlock><h6 class="title">{{episode.showTitle}}</h6></ng-template>
<p class="subtitle">{{episode.title}}</p> <p class="subtitle">{{episode.title}}</p>
</ng-container> </ng-container>
<ng-template #noTitle> <ng-template #noTitle>
<h6 *ngIf="episode.seasonNumber != 0; else elseBlock;" class="title">S{{episode.seasonNumber}}:E{{episode.episodeNumber}} - {{episode.title}}</h6> <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> <ng-template #elseBlock><h6 class="title">{{episode.title}}</h6></ng-template>
<p class="overview">{{episode.overview}}</p> <p class="overview">{{episode.overview}}</p>
</ng-template> </ng-template>
</a> </a>
</div> </div>
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div> </div>

View File

@ -4,179 +4,179 @@
.root .root
{ {
position: relative; position: relative;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
.scrollBtn .scrollBtn
{ {
display: block; display: block;
} }
} }
} }
.episodes .episodes
{ {
display: flex; display: flex;
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
overflow-x: auto; overflow-x: auto;
min-width: 100%; min-width: 100%;
flex-shrink: 0; flex-shrink: 0;
flex-direction: row; flex-direction: row;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: #999 transparent; scrollbar-color: #999 transparent;
&::-webkit-scrollbar &::-webkit-scrollbar
{ {
height: 4px; height: 4px;
background: transparent; background: transparent;
} }
&::-webkit-scrollbar-thumb &::-webkit-scrollbar-thumb
{ {
background-color: #999; background-color: #999;
border-radius: 90px; border-radius: 90px;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
background-color: rgb(134, 127, 127); background-color: rgb(134, 127, 127);
} }
} }
} }
.episode .episode
{ {
visibility: visible; visibility: visible;
display: inline-block; display: inline-block;
padding: .25rem; padding: .25rem;
flex-shrink: 0; flex-shrink: 0;
width: 55%; width: 55%;
cursor: pointer; cursor: pointer;
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
width: 40%; width: 40%;
} }
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
width: 33%; width: 33%;
} }
@include media-breakpoint-up(lg) @include media-breakpoint-up(lg)
{ {
width: 28%; width: 28%;
} }
@include media-breakpoint-up(xl) @include media-breakpoint-up(xl)
{ {
width: 18%; width: 18%;
} }
.img .img
{ {
width: 100%; width: 100%;
height: 0; height: 0;
padding-top: 56.25%; padding-top: 56.25%;
background-color: #333333; background-color: #333333;
background-size: contain; background-size: contain;
position: relative; position: relative;
> button > button
{ {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
margin: auto; margin: auto;
width: 64px; width: 64px;
height: 64px; height: 64px;
outline: none; outline: none;
display: none; display: none;
} }
} }
.title .title
{ {
padding-top: .2rem; padding-top: .2rem;
font-weight: 600; font-weight: 600;
margin-bottom: 0; margin-bottom: 0;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
} }
.overview .overview
{ {
font-weight: 300; font-weight: 300;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 4; -webkit-line-clamp: 4;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
} }
.subtitle .subtitle
{ {
font-weight: 300; font-weight: 300;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
} }
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
.img .img
{ {
outline: solid var(--accentColor); outline: solid var(--accentColor);
.playBtn .playBtn
{ {
display: block; display: block;
} }
} }
.title .title
{ {
text-decoration: underline; text-decoration: underline;
} }
} }
} }
.playIcon .playIcon
{ {
font-size: 64px; font-size: 64px;
width: 64px; width: 64px;
height: 64px; height: 64px;
line-height: 64px; line-height: 64px;
} }
.scrollBtn .scrollBtn
{ {
padding: 0; padding: 0;
outline: none; outline: none;
min-width: 0; min-width: 0;
position: absolute; position: absolute;
top: 20%; top: 20%;
bottom: 60%; bottom: 60%;
display: none; display: none;
&.leftBtn &.leftBtn
{ {
left: 0; left: 0;
padding-left: 10px; padding-left: 10px;
padding-right: 2px; padding-right: 2px;
} }
&.rightBtn &.rightBtn
{ {
right: 0; right: 0;
padding-right: 10px; padding-right: 10px;
padding-left: 2px; padding-left: 2px;
} }
} }

View File

@ -4,14 +4,14 @@ import { DomSanitizer } from "@angular/platform-browser";
import { Episode } from "../../models/episode"; import { Episode } from "../../models/episode";
@Component({ @Component({
selector: 'app-episodes-list', selector: 'app-episodes-list',
templateUrl: './episodes-list.component.html', templateUrl: './episodes-list.component.html',
styleUrls: ['./episodes-list.component.scss'] styleUrls: ['./episodes-list.component.scss']
}) })
export class EpisodesListComponent export class EpisodesListComponent
{ {
@Input() displayShowTitle: boolean = false; @Input() displayShowTitle: boolean = false;
@Input() episodes: Episode[]; @Input() episodes: Episode[];
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef; @ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton; @ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton; @ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
@ -53,8 +53,8 @@ export class EpisodesListComponent
this.rightBtn._elementRef.nativeElement.classList.remove("d-none"); this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
} }
sanitize(url: string) sanitize(url: string)
{ {
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")"); return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
} }
} }

View File

@ -4,6 +4,6 @@
<br/> <br/>
<br/> <br/>
<div class="text-center"> <div class="text-center">
<h1>404 Error</h1> <h1>404 Error</h1>
<p>The page you requested was not found.</p> <p>The page you requested was not found.</p>
</div> </div>

View File

@ -1,15 +1,15 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'app-not-found', selector: 'app-not-found',
templateUrl: './not-found.component.html', templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.scss'] styleUrls: ['./not-found.component.scss']
}) })
export class NotFoundComponent implements OnInit { export class NotFoundComponent implements OnInit {
constructor() { } constructor() { }
ngOnInit() { ngOnInit() {
} }
} }

View File

@ -1,11 +1,11 @@
<div class="scroll-row mb-5"> <div class="scroll-row mb-5">
<div class="people-container" #scrollView (scroll)="onScroll()"> <div class="people-container" #scrollView (scroll)="onScroll()">
<a class="people" *ngFor="let people of this.people" routerLink="/people/{{people.slug}}" href="/people/{{people.slug}}"> <a class="people" *ngFor="let people of this.people" routerLink="/people/{{people.slug}}" href="/people/{{people.slug}}">
<div matRipple [style.background-image]="getPeopleIcon(people.slug)"> </div> <div matRipple [style.background-image]="getPeopleIcon(people.slug)"> </div>
<h6 class="name">{{people.name}}</h6> <h6 class="name">{{people.name}}</h6>
<p class="role">{{people.role}}</p> <p class="role">{{people.role}}</p>
</a> </a>
</div> </div>
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div> </div>

View File

@ -4,138 +4,138 @@
.people-container .people-container
{ {
display: flex; display: flex;
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
overflow-x: auto; overflow-x: auto;
min-width: 100%; min-width: 100%;
flex-shrink: 0; flex-shrink: 0;
flex-direction: row; flex-direction: row;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: #999 transparent; scrollbar-color: #999 transparent;
&::-webkit-scrollbar &::-webkit-scrollbar
{ {
height: 4px; height: 4px;
background: transparent; background: transparent;
} }
&::-webkit-scrollbar-thumb &::-webkit-scrollbar-thumb
{ {
background-color: #999; background-color: #999;
border-radius: 90px; border-radius: 90px;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
background-color: rgb(134, 127, 127); background-color: rgb(134, 127, 127);
} }
} }
} }
.people .people
{ {
visibility: visible; visibility: visible;
margin: .25rem; margin: .25rem;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
outline: none; outline: none;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
width: 33%; width: 33%;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
width: 22%; width: 22%;
} }
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
width: 20%; width: 20%;
} }
@include media-breakpoint-up(lg) @include media-breakpoint-up(lg)
{ {
width: 15%; width: 15%;
} }
@include media-breakpoint-up(xl) @include media-breakpoint-up(xl)
{ {
width: 10%; width: 10%;
} }
> div > div
{ {
width: 100%; width: 100%;
height: 0; height: 0;
padding-top: 147.0588%; padding-top: 147.0588%;
background-size: cover; background-size: cover;
background-color: #333333; background-color: #333333;
} }
> p, h6 > p, h6
{ {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center; text-align: center;
margin-bottom: 0px; margin-bottom: 0px;
&.role &.role
{ {
font-size: 0.8em; font-size: 0.8em;
} }
} }
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
cursor: pointer; cursor: pointer;
> img > img
{ {
outline: solid var(--accentColor); outline: solid var(--accentColor);
} }
.name .name
{ {
text-decoration: underline; text-decoration: underline;
} }
} }
} }
.scroll-row .scroll-row
{ {
position: relative; position: relative;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
.scrollBtn .scrollBtn
{ {
display: block; display: block;
} }
} }
} }
.scrollBtn .scrollBtn
{ {
padding: 0; padding: 0;
outline: none; outline: none;
min-width: 0; min-width: 0;
position: absolute; position: absolute;
top: 30%; top: 30%;
bottom: 40%; bottom: 40%;
display: none; display: none;
&.leftBtn &.leftBtn
{ {
left: 0; left: 0;
padding-left: 10px; padding-left: 10px;
padding-right: 2px; padding-right: 2px;
} }
&.rightBtn &.rightBtn
{ {
right: 0; right: 0;
padding-right: 10px; padding-right: 10px;
padding-left: 2px; padding-left: 2px;
} }
} }

View File

@ -1,154 +1,154 @@
<div id="root"> <div id="root">
<div class="player data-vjs-player"> <div class="player data-vjs-player">
<video id="player" poster="backdrop/{{this.item.showSlug}}" autoplay muted (click)="videoClicked()"> <video id="player" poster="backdrop/{{this.item.showSlug}}" autoplay muted (click)="videoClicked()">
</video> </video>
</div> </div>
<div id="loadIndicator"> <div id="loadIndicator">
<div class="spinner-border align-self-center" role="status"></div> <div class="spinner-border align-self-center" role="status"></div>
</div> </div>
<mat-card class="d-none w-25 m-5" [ngClass]="{'d-block': this.displayStats}"> <mat-card class="d-none w-25 m-5" [ngClass]="{'d-block': this.displayStats}">
<mat-card-header style="margin-bottom: 0.5rem;"> <mat-card-header style="margin-bottom: 0.5rem;">
<h4 style="align-self: center; margin-bottom: 0;">Stats</h4> <h4 style="align-self: center; margin-bottom: 0;">Stats</h4>
<div style="flex: 1 1 auto"></div> <div style="flex: 1 1 auto"></div>
<button mat-icon-button aria-label="Close" (click)="this.displayStats = false" style="outline: none;"> <button mat-icon-button aria-label="Close" (click)="this.displayStats = false" style="outline: none;">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
Play method: <span style="float: right">{{this.playMethod}}</span> Play method: <span style="float: right">{{this.playMethod}}</span>
<br /> <br />
<br /> <br />
Video Container: <span style="float: right">{{this.item.container}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("container")}}</i></span> Video Container: <span style="float: right">{{this.item.container}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("container")}}</i></span>
<br /> <br />
Video Codec: <span style="float: right">{{this.item.video.codec}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("video")}}</i></span> Video Codec: <span style="float: right">{{this.item.video.codec}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("video")}}</i></span>
<br /> <br />
Audio Codec: <span style="float: right">{{this.item.audios[0].codec}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("audio")}}</i></span> Audio Codec: <span style="float: right">{{this.item.audios[0].codec}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("audio")}}</i></span>
<br /> <br />
Subtitle Codec: <span style="float: right">{{this.selectedSubtitle ? this.selectedSubtitle.codec : "none"}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("subtitle")}}</i></span> Subtitle Codec: <span style="float: right">{{this.selectedSubtitle ? this.selectedSubtitle.codec : "none"}} <i class="material-icons" style="vertical-align: middle; font-size: 14px">{{getSupportedFeature("subtitle")}}</i></span>
<br /> <br />
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<div id="hover"> <div id="hover">
<div class="back"> <div class="back">
<a mat-icon-button matTooltipPosition="below" matTooltip="Back" (click)="back()"> <a mat-icon-button matTooltipPosition="below" matTooltip="Back" (click)="back()">
<mat-icon>arrow_back</mat-icon> <mat-icon>arrow_back</mat-icon>
</a> </a>
<h5>{{this.item.showTitle}}</h5> <h5>{{this.item.showTitle}}</h5>
</div> </div>
<div class="controller container-fluid" id="controller"> <div class="controller container-fluid" id="controller">
<div class="img d-none d-sm-block"> <div class="img d-none d-sm-block">
<img src="poster/{{this.item.showSlug}}" /> <img src="poster/{{this.item.showSlug}}" />
</div> </div>
<div class="content"> <div class="content">
<h3>S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}</h3> <h3>S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}</h3>
<div id="progress-bar"> <div id="progress-bar">
<div class="seek-bar"> <div class="seek-bar">
<div id="buffered"></div> <div id="buffered"></div>
<div id="progress"></div> <div id="progress"></div>
</div> </div>
<div id="thumb"><div></div></div> <div id="thumb"><div></div></div>
</div> </div>
<div class="buttons"> <div class="buttons">
<div class="left"> <div class="left">
<a *ngIf="this.item.previousEpisode" mat-icon-button matTooltipPosition="above" matTooltip="Previous" (click)="previous()"> <a *ngIf="this.item.previousEpisode" mat-icon-button matTooltipPosition="above" matTooltip="Previous" (click)="previous()">
<mat-icon>skip_previous</mat-icon> <mat-icon>skip_previous</mat-icon>
</a> </a>
<button mat-icon-button matTooltipPosition="above" [matTooltip]="playTooltip" id="play" (click)="tooglePlayback()"> <button mat-icon-button matTooltipPosition="above" [matTooltip]="playTooltip" id="play" (click)="tooglePlayback()">
<mat-icon>{{this.playIcon}}</mat-icon> <mat-icon>{{this.playIcon}}</mat-icon>
</button> </button>
<a mat-icon-button id="nextBtn" *ngIf="this.item.nextEpisode" (click)="next()"> <a mat-icon-button id="nextBtn" *ngIf="this.item.nextEpisode" (click)="next()">
<mat-icon>skip_next</mat-icon> <mat-icon>skip_next</mat-icon>
<div id="next"> <div id="next">
<div id="main"> <div id="main">
<img src="{{this.item.nextEpisode.thumb}}" /> <img src="{{this.item.nextEpisode.thumb}}" />
</div> </div>
<div id="overview"> <div id="overview">
<h6>S{{this.item.nextEpisode.seasonNumber}}:E{{this.item.nextEpisode.episodeNumber}} - {{this.item.nextEpisode.title}}</h6> <h6>S{{this.item.nextEpisode.seasonNumber}}:E{{this.item.nextEpisode.episodeNumber}} - {{this.item.nextEpisode.title}}</h6>
<p>{{this.item.nextEpisode.overview}}</p> <p>{{this.item.nextEpisode.overview}}</p>
</div> </div>
</div> </div>
</a> </a>
<div id="volume"> <div id="volume">
<button mat-icon-button matTooltipPosition="above" matTooltip="Volume" (click)="toogleMute()"> <button mat-icon-button matTooltipPosition="above" matTooltip="Volume" (click)="toogleMute()">
<mat-icon>{{this.volumeIcon}}</mat-icon> <mat-icon>{{this.volumeIcon}}</mat-icon>
</button> </button>
<mat-slider [value]="volume" (input)="changeVolume($event.value)"></mat-slider> <mat-slider [value]="volume" (input)="changeVolume($event.value)"></mat-slider>
</div> </div>
<p *ngIf="this.maxHours; else elseBlock">{{this.hours | number: '2.0-0'}}:{{this.minutes | number: '2.0-0'}}:{{this.seconds | number: '2.0-0'}} / {{this.maxHours | number: '2.0-0'}}:{{this.maxMinutes | number: '2.0-0'}}:{{this.maxSeconds | number: '2.0-0'}}</p> <p *ngIf="this.maxHours; else elseBlock">{{this.hours | number: '2.0-0'}}:{{this.minutes | number: '2.0-0'}}:{{this.seconds | number: '2.0-0'}} / {{this.maxHours | number: '2.0-0'}}:{{this.maxMinutes | number: '2.0-0'}}:{{this.maxSeconds | number: '2.0-0'}}</p>
<ng-template #elseBlock><p>{{this.minutes | number: '2.0-0'}}:{{this.seconds | number: '2.0-0'}} / {{this.maxMinutes | number: '2.0-0'}}:{{this.maxSeconds | number: '2.0-0'}}</p></ng-template> <ng-template #elseBlock><p>{{this.minutes | number: '2.0-0'}}:{{this.seconds | number: '2.0-0'}} / {{this.maxMinutes | number: '2.0-0'}}:{{this.maxSeconds | number: '2.0-0'}}</p></ng-template>
</div> </div>
<div class="right"> <div class="right">
<button id="volume" *ngIf="this.item.audios.length > 1" mat-icon-button matTooltipPosition="above" matTooltip="Select audio track"> <button id="volume" *ngIf="this.item.audios.length > 1" mat-icon-button matTooltipPosition="above" matTooltip="Select audio track">
<mat-icon>music_note</mat-icon> <mat-icon>music_note</mat-icon>
</button> </button>
<button *ngIf="this.item.subtitles.length > 0" mat-icon-button [matMenuTriggerFor]="subtitles" matTooltipPosition="above" matTooltip="Select subtitle track"> <button *ngIf="this.item.subtitles.length > 0" mat-icon-button [matMenuTriggerFor]="subtitles" matTooltipPosition="above" matTooltip="Select subtitle track">
<mat-icon>closed_caption</mat-icon> <mat-icon>closed_caption</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" matTooltip="Cast"> <button mat-icon-button matTooltipPosition="above" matTooltip="Cast">
<mat-icon>cast</mat-icon> <mat-icon>cast</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" [matMenuTriggerFor]="settings" matTooltip="Settings"> <button mat-icon-button matTooltipPosition="above" [matMenuTriggerFor]="settings" matTooltip="Settings">
<mat-icon>settings</mat-icon> <mat-icon>settings</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" [matTooltip]="fullscreenTooltip" id="fullscreen" (click)="fullscreen()"> <button mat-icon-button matTooltipPosition="above" [matTooltip]="fullscreenTooltip" id="fullscreen" (click)="fullscreen()">
<mat-icon>{{fullscreenIcon}}</mat-icon> <mat-icon>{{fullscreenIcon}}</mat-icon>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<mat-menu #subtitles="matMenu"> <mat-menu #subtitles="matMenu">
<ng-template matMenuContent> <ng-template matMenuContent>
<button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)"> <button [ngClass]="{'selected': this.selectedSubtitle == null}" mat-menu-item (click)="selectSubtitle(null)">
<span>None</span> <span>None</span>
</button> </button>
<div *ngFor="let subtitle of this.item.subtitles"> <div *ngFor="let subtitle of this.item.subtitles">
<button [ngClass]="{'selected': this.selectedSubtitle == subtitle}" mat-menu-item *ngIf="subtitle.codec == 'ass' || subtitle.codec == 'subrip'; else elseBlock" (click)="selectSubtitle(subtitle)"> <button [ngClass]="{'selected': this.selectedSubtitle == subtitle}" mat-menu-item *ngIf="subtitle.codec == 'ass' || subtitle.codec == 'subrip'; else elseBlock" (click)="selectSubtitle(subtitle)">
<span>{{subtitle.displayName}}</span> <span>{{subtitle.displayName}}</span>
</button> </button>
<ng-template #elseBlock> <ng-template #elseBlock>
<button mat-menu-item disabled> <button mat-menu-item disabled>
<span>{{subtitle.displayName}} ({{subtitle.codec}})</span> <span>{{subtitle.displayName}} ({{subtitle.codec}})</span>
</button> </button>
</ng-template> </ng-template>
</div> </div>
</ng-template> </ng-template>
</mat-menu> </mat-menu>
<mat-menu #settings="matMenu"> <mat-menu #settings="matMenu">
<ng-template matMenuContent> <ng-template matMenuContent>
<button mat-menu-item (click)="this.displayStats = !this.displayStats"> <button mat-menu-item (click)="this.displayStats = !this.displayStats">
<span>Stats</span> <span>Stats</span>
</button> </button>
<button mat-menu-item [matMenuTriggerFor]="method"> <button mat-menu-item [matMenuTriggerFor]="method">
<span>Method</span> <span>Method</span>
</button> </button>
</ng-template> </ng-template>
</mat-menu> </mat-menu>
<mat-menu #method="matMenu"> <mat-menu #method="matMenu">
<ng-template matMenuContent> <ng-template matMenuContent>
<button mat-menu-item (click)="selectPlayMethod(methodType.direct)"> <button mat-menu-item (click)="selectPlayMethod(methodType.direct)">
<span>Direct Play</span> <span>Direct Play</span>
</button> </button>
<button mat-menu-item (click)="selectPlayMethod(methodType.transmux)"> <button mat-menu-item (click)="selectPlayMethod(methodType.transmux)">
<span>Transmux</span> <span>Transmux</span>
</button> </button>
<button mat-menu-item (click)="selectPlayMethod(methodType.transcode)"> <button mat-menu-item (click)="selectPlayMethod(methodType.transcode)">
<span>Transcode</span> <span>Transcode</span>
</button> </button>
</ng-template> </ng-template>
</mat-menu> </mat-menu>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,339 +3,339 @@
.player .player
{ {
position: fixed; position: fixed;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: #000; background: #000;
> video > video
{ {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
} }
} }
#hover #hover
{ {
transition: opacity .2s linear; transition: opacity .2s linear;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
&.idle &.idle
{ {
transition: opacity .6s linear, visibility 0s .6s; transition: opacity .6s linear, visibility 0s .6s;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
} }
} }
.back .back
{ {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
padding: .33%; padding: .33%;
display: flex; display: flex;
> a > a
{ {
outline: none; outline: none;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
> h5 > h5
{ {
margin: 0; margin: 0;
margin-left: .5rem; margin-left: .5rem;
align-self: center; align-self: center;
} }
} }
.controller .controller
{ {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
display: flex; display: flex;
padding: 1%; padding: 1%;
.img .img
{ {
width: 15%; width: 15%;
position: relative; position: relative;
height: auto; height: auto;
> img > img
{ {
width: 100%; width: 100%;
height: auto; height: auto;
bottom: 0; bottom: 0;
position: absolute; position: absolute;
} }
} }
.content .content
{ {
width: 100%; width: 100%;
margin-left: 1rem; margin-left: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.buttons .buttons
{ {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
> div > div
{ {
&.left &.left
{ {
align-self: start; align-self: start;
display: flex; display: flex;
> p > p
{ {
margin: 0; margin: 0;
margin-left: 1rem; margin-left: 1rem;
align-self: center; align-self: center;
} }
} }
&.right &.right
{ {
align-self: end; align-self: end;
} }
> button > button
{ {
margin-left: .3rem; margin-left: .3rem;
margin-right: .3rem; margin-right: .3rem;
outline: none; outline: none;
} }
> a > a
{ {
margin-left: .3rem; margin-left: .3rem;
margin-right: .3rem; margin-right: .3rem;
outline: none; outline: none;
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
} }
} }
} }
} }
} }
#progress-bar #progress-bar
{ {
width: 100%; width: 100%;
height: auto; height: auto;
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
position: relative; position: relative;
.seek-bar .seek-bar
{ {
width: 100%; width: 100%;
height: 4px; height: 4px;
position: relative; position: relative;
background-color: rgba(255, 255, 255, .2); background-color: rgba(255, 255, 255, .2);
transform: scaleY(.6); transform: scaleY(.6);
#progress #progress
{ {
width: 0; width: 0;
height: 100%; height: 100%;
background-color: var(--accentColor); background-color: var(--accentColor);
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
#buffered #buffered
{ {
width: 0; width: 0;
height: 100%; height: 100%;
background-color: rgba(255, 255, 255, .5); background-color: rgba(255, 255, 255, .5);
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
} }
#thumb #thumb
{ {
width: 100%; width: 100%;
height: 12px; height: 12px;
position: absolute; position: absolute;
left: -6px; left: -6px;
top: 0; top: 0;
bottom: 0; bottom: 0;
margin: auto; margin: auto;
opacity: 0; opacity: 0;
> div > div
{ {
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 6px; border-radius: 6px;
background-color: var(--accentColor); background-color: var(--accentColor);
} }
} }
.hoverEnabled &:hover, &.seeking .hoverEnabled &:hover, &.seeking
{ {
cursor: pointer; cursor: pointer;
.seek-bar .seek-bar
{ {
transform: scaleY(1); transform: scaleY(1);
} }
#thumb #thumb
{ {
opacity: 1; opacity: 1;
} }
} }
} }
#nextBtn #nextBtn
{ {
position: relative; position: relative;
.hoverEnabled &:hover .hoverEnabled &:hover
{ {
#next #next
{ {
display: flex; display: flex;
} }
} }
#next #next
{ {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 100%; bottom: 100%;
display: none; display: none;
background-color: #212121; background-color: #212121;
white-space: normal; white-space: normal;
line-height: normal; line-height: normal;
cursor: default; cursor: default;
height: 150px; height: 150px;
#main #main
{ {
width: auto; width: auto;
height: 100%; height: 100%;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
> img > img
{ {
width: auto; width: auto;
height: 100%; height: 100%;
} }
} }
#overview #overview
{ {
padding: 1%; padding: 1%;
width: 50%; width: 50%;
min-width: 300px; min-width: 300px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
> p > p
{ {
text-align: justify; text-align: justify;
font-weight: 300; font-weight: 300;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
} }
} }
} }
} }
#volume #volume
{ {
display: flex; display: flex;
> button > button
{ {
outline: none; outline: none;
} }
.hoverEnabled &:hover, &:focus-within .hoverEnabled &:hover, &:focus-within
{ {
> mat-slider > mat-slider
{ {
width: 100px; width: 100px;
} }
} }
> mat-slider > mat-slider
{ {
width: 0px; width: 0px;
min-width: 0px; min-width: 0px;
padding: 0; padding: 0;
height: 40px; height: 40px;
overflow: hidden; overflow: hidden;
transition: width .2s cubic-bezier(0.4,0, 1, 1); transition: width .2s cubic-bezier(0.4,0, 1, 1);
> div > div
{ {
top: 19px; top: 19px;
left: 10px; left: 10px;
right: 10px; right: 10px;
} }
} }
} }
.mat-menu-item .mat-menu-item
{ {
outline: none !important; outline: none !important;
} }
.selected .selected
{ {
background: #595959 !important; background: #595959 !important;
color: var(--accentColor); color: var(--accentColor);
font-weight: 900; font-weight: 900;
} }
#loadIndicator #loadIndicator
{ {
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
pointer-events: none; pointer-events: none;
background: rgba(0, 0, 0, 0.3); background: rgba(0, 0, 0, 0.3);
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.volume .volume
{ {
min-width: 0px !important; min-width: 0px !important;
} }
.info-panel .info-panel
{ {
min-width: 250px !important; min-width: 250px !important;
max-width: 300px !important; max-width: 300px !important;
} }

View File

@ -371,7 +371,7 @@ export class PlayerComponent implements OnInit
let queryMethod: string = this.route.snapshot.queryParams["method"]; let queryMethod: string = this.route.snapshot.queryParams["method"];
if (queryMethod) if (queryMethod)
this.playMethod = method[queryMethod]; this.playMethod = method[queryMethod];
else else
this.playMethod = getPlaybackMethod(this.player, this.item); this.playMethod = getPlaybackMethod(this.player, this.item);
this.selectPlayMethod(this.playMethod); this.selectPlayMethod(this.playMethod);
@ -428,7 +428,7 @@ export class PlayerComponent implements OnInit
next() next()
{ {
if (this.item.nextEpisode != null) if (this.item.nextEpisode != null)
this.router.navigate(["/watch/" + this.item.nextEpisode.link], { queryParamsHandling: "merge", replaceUrl: true }); this.router.navigate(["/watch/" + this.item.nextEpisode.link], { queryParamsHandling: "merge", replaceUrl: true });
} }
previous() previous()

View File

@ -1,5 +1,5 @@
::cue ::cue
{ {
background-color: transparent; background-color: transparent;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
} }

View File

@ -1,12 +1,12 @@
<div *ngIf="items.shows.length > 0" class="container-fluid mt-3"> <div *ngIf="items.shows.length > 0" class="container-fluid mt-3">
<h3>Shows</h3> <h3>Shows</h3>
</div> </div>
<app-shows-list [shows]="items.shows"></app-shows-list> <app-shows-list [shows]="items.shows"></app-shows-list>
<div *ngIf="items.episodes.length > 0" class="container-fluid mt-5"> <div *ngIf="items.episodes.length > 0" class="container-fluid mt-5">
<h3>Episodes</h3> <h3>Episodes</h3>
</div> </div>
<app-episodes-list displayShowTitle="true" [episodes]="items.episodes"></app-episodes-list> <app-episodes-list displayShowTitle="true" [episodes]="items.episodes"></app-episodes-list>
<div *ngIf="items.people.length > 0" class="container-fluid mt-5"> <div *ngIf="items.people.length > 0" class="container-fluid mt-5">
<h3>People</h3> <h3>People</h3>
</div> </div>
<app-people-list [people]="items.people"></app-people-list> <app-people-list [people]="items.people"></app-people-list>

View File

@ -4,9 +4,9 @@ import { SearchResut } from "../../models/search-result";
import { Title } from "@angular/platform-browser"; import { Title } from "@angular/platform-browser";
@Component({ @Component({
selector: 'app-search', selector: 'app-search',
templateUrl: './search.component.html', templateUrl: './search.component.html',
styleUrls: ['./search.component.scss'] styleUrls: ['./search.component.scss']
}) })
export class SearchComponent implements OnInit, OnDestroy export class SearchComponent implements OnInit, OnDestroy
{ {

View File

@ -7,27 +7,27 @@ import { catchError } from 'rxjs/operators'
import { Collection } from "../../models/collection"; import { Collection } from "../../models/collection";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CollectionResolverService implements Resolve<Collection> export class CollectionResolverService implements Resolve<Collection>
{ {
constructor(private http: HttpClient, private snackBar: MatSnackBar) { } constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Collection | Observable<Collection> | Promise<Collection> resolve(route: ActivatedRouteSnapshot): Collection | Observable<Collection> | Promise<Collection>
{ {
let collection: string = route.paramMap.get("collection-slug"); let collection: string = route.paramMap.get("collection-slug");
return this.http.get<Collection>("api/collection/" + collection).pipe(catchError((error: HttpErrorResponse) => return this.http.get<Collection>("api/collection/" + collection).pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
if (error.status == 404) if (error.status == 404)
{ {
this.snackBar.open("Collection \"" + collection + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("Collection \"" + collection + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
else else
{ {
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
return EMPTY; return EMPTY;
})); }));
} }
} }

View File

@ -10,36 +10,36 @@ import { Show } from "../../models/show";
@Injectable() @Injectable()
export class LibraryResolverService implements Resolve<Show[]> export class LibraryResolverService implements Resolve<Show[]>
{ {
constructor(private http: HttpClient, private snackBar: MatSnackBar) { } constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Show[] | Observable<Show[]> | Promise<Show[]> resolve(route: ActivatedRouteSnapshot): Show[] | Observable<Show[]> | Promise<Show[]>
{ {
let slug: string = route.paramMap.get("library-slug"); let slug: string = route.paramMap.get("library-slug");
if (slug == null) if (slug == null)
{ {
return this.http.get<Show[]>("api/shows").pipe(catchError((error: HttpErrorResponse) => return this.http.get<Show[]>("api/shows").pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
return EMPTY; return EMPTY;
})); }));
} }
else else
{ {
return this.http.get<Show[]>("api/libraries/" + slug).pipe(catchError((error: HttpErrorResponse) => return this.http.get<Show[]>("api/libraries/" + slug).pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
if (error.status == 404) if (error.status == 404)
{ {
this.snackBar.open("Library \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("Library \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
else else
{ {
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
return EMPTY; return EMPTY;
})); }));
} }
} }
} }

View File

@ -8,7 +8,7 @@ import { Collection } from "../../models/collection";
import { People } from "../../models/people"; import { People } from "../../models/people";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PeopleResolverService implements Resolve<Collection> export class PeopleResolverService implements Resolve<Collection>
{ {

View File

@ -7,20 +7,20 @@ import { catchError } from 'rxjs/operators';
import { SearchResut } from "../../models/search-result"; import { SearchResut } from "../../models/search-result";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class SearchResolverService implements Resolve<SearchResut> export class SearchResolverService implements Resolve<SearchResut>
{ {
constructor(private http: HttpClient, private snackBar: MatSnackBar) { } constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): SearchResut | Observable<SearchResut> | Promise<SearchResut> resolve(route: ActivatedRouteSnapshot): SearchResut | Observable<SearchResut> | Promise<SearchResut>
{ {
let query: string = route.paramMap.get("query"); let query: string = route.paramMap.get("query");
return this.http.get<SearchResut>("api/search/" + query).pipe(catchError((error: HttpErrorResponse) => return this.http.get<SearchResut>("api/search/" + query).pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
return EMPTY; return EMPTY;
})); }));
} }
} }

View File

@ -9,23 +9,19 @@ import { Show } from "../../models/show";
@Injectable() @Injectable()
export class ShowResolverService implements Resolve<Show> export class ShowResolverService implements Resolve<Show>
{ {
constructor(private http: HttpClient, private snackBar: MatSnackBar) { } constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show> resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show>
{ {
let slug: string = route.paramMap.get("show-slug"); let slug: string = route.paramMap.get("show-slug");
return this.http.get<Show>("api/shows/" + slug).pipe(catchError((error: HttpErrorResponse) => return this.http.get<Show>("api/shows/" + slug).pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
if (error.status == 404) if (error.status == 404)
{ this.snackBar.open("Show \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
this.snackBar.open("Show \"" + slug + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); else
} this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
else return EMPTY;
{ }));
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); }
}
return EMPTY;
}));
}
} }

View File

@ -10,23 +10,23 @@ import { WatchItem } from "../../models/watch-item";
@Injectable() @Injectable()
export class StreamResolverService implements Resolve<WatchItem> export class StreamResolverService implements Resolve<WatchItem>
{ {
constructor(private http: HttpClient, private snackBar: MatSnackBar) { } constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): WatchItem | Observable<WatchItem> | Promise<WatchItem> resolve(route: ActivatedRouteSnapshot): WatchItem | Observable<WatchItem> | Promise<WatchItem>
{ {
let item: string = route.paramMap.get("item"); let item: string = route.paramMap.get("item");
return this.http.get<WatchItem>("api/watch/" + item).pipe(catchError((error: HttpErrorResponse) => return this.http.get<WatchItem>("api/watch/" + item).pipe(catchError((error: HttpErrorResponse) =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
if (error.status == 404) if (error.status == 404)
{ {
this.snackBar.open("Episode \"" + item + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("Episode \"" + item + "\" not found.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
else else
{ {
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
} }
return EMPTY; return EMPTY;
})); }));
} }
} }

View File

@ -1,79 +1,79 @@
<div class="backdrop"> <div class="backdrop">
<img id="backdrop" src="backdrop/{{this.show.slug}}" /> <img id="backdrop" src="backdrop/{{this.show.slug}}" />
</div> </div>
<div class="header container pt-sm-5"> <div class="header container pt-sm-5">
<div class="row"> <div class="row">
<img class="poster d-none d-sm-block" src="poster/{{this.show.slug}}" /> <img class="poster d-none d-sm-block" src="poster/{{this.show.slug}}" />
<div class="main col"> <div class="main col">
<div class="info"> <div class="info">
<h1 class="title">{{this.show.title}}</h1> <h1 class="title">{{this.show.title}}</h1>
<h2 class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</h2> <h2 class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</h2>
<ng-template #elseBlock><h2 class="date">{{show.startYear}}</h2></ng-template> <ng-template #elseBlock><h2 class="date">{{show.startYear}}</h2></ng-template>
</div> </div>
<div class="buttons"> <div class="buttons">
<button mat-mini-fab matTooltipPosition="above" matTooltip="Play" class="mr-3"> <button mat-mini-fab matTooltipPosition="above" matTooltip="Play" class="mr-3">
<mat-icon>play_arrow</mat-icon> <mat-icon>play_arrow</mat-icon>
</button> </button>
<button *ngIf="this.show.trailerUrl" mat-icon-button matTooltipPosition="above" matTooltip="Trailer"> <button *ngIf="this.show.trailerUrl" mat-icon-button matTooltipPosition="above" matTooltip="Trailer">
<mat-icon>local_movies</mat-icon> <mat-icon>local_movies</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" matTooltip="Download"> <button mat-icon-button matTooltipPosition="above" matTooltip="Download">
<mat-icon>cloud_download</mat-icon> <mat-icon>cloud_download</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" matTooltip="Watched"> <button mat-icon-button matTooltipPosition="above" matTooltip="Watched">
<mat-icon>done</mat-icon> <mat-icon>done</mat-icon>
</button> </button>
<button mat-icon-button matTooltipPosition="above" matTooltip="More"> <button mat-icon-button matTooltipPosition="above" matTooltip="More">
<mat-icon>more_horiz</mat-icon> <mat-icon>more_horiz</mat-icon>
</button> </button>
</div> </div>
</div> </div>
<div class="col-3 secondary d-none d-md-block"> <div class="col-3 secondary d-none d-md-block">
<img src="logo/{{this.show.slug}}" /> <img src="logo/{{this.show.slug}}" />
<div> <div>
<p>Studio: <b><a routerLink="/studio/{{this.show.studio?.slug}}">{{this.show.studio?.name}}</a></b></p> <p>Studio: <b><a routerLink="/studio/{{this.show.studio?.slug}}">{{this.show.studio?.name}}</a></b></p>
</div> </div>
</div> </div>
</div> </div>
<div class="row pt-3 d-md-none"> <div class="row pt-3 d-md-none">
<div class="col"> <div class="col">
<p class="mr-1 d-inline-block">Studio: <b><a routerLink="/studio/{{this.show.studio?.slug}}">{{this.show.studio?.name}}</a></b></p> <p class="mr-1 d-inline-block">Studio: <b><a routerLink="/studio/{{this.show.studio?.slug}}">{{this.show.studio?.name}}</a></b></p>
<div class="d-sm-none"> <div class="d-sm-none">
<p>Genres: <span *ngFor="let genre of this.show.genres; let isLast = last"><b><a routerLink="/genre/{{genre.slug}}">{{genre.name}}</a></b>{{isLast ? "" : ", "}}</span></p> <p>Genres: <span *ngFor="let genre of this.show.genres; let isLast = last"><b><a routerLink="/genre/{{genre.slug}}">{{genre.name}}</a></b>{{isLast ? "" : ", "}}</span></p>
</div> </div>
</div> </div>
</div> </div>
<div class="row pt-3"> <div class="row pt-3">
<div class="col"> <div class="col">
<p class="text-justify overview">{{this.show.overview}}</p> <p class="text-justify overview">{{this.show.overview}}</p>
</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 style="opacity: .8;">Genres</h3> <h3 style="opacity: .8;">Genres</h3>
<ul> <ul>
<li *ngFor="let genre of this.show.genres"><b><a class="genre" 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>
</div> </div>
<div class="container-fluid mt-3"> <div class="container-fluid mt-3">
<mat-form-field> <mat-form-field>
<mat-label>Season</mat-label> <mat-label>Season</mat-label>
<mat-select [(value)]="season" (selectionChange)="getEpisodes()"> <mat-select [(value)]="season" (selectionChange)="getEpisodes()">
<mat-option *ngFor="let season of this.show.seasons" [value]="season.seasonNumber">{{season.title}}</mat-option> <mat-option *ngFor="let season of this.show.seasons" [value]="season.seasonNumber">{{season.title}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<app-episodes-list [episodes]="episodes"></app-episodes-list> <app-episodes-list [episodes]="episodes"></app-episodes-list>
<div class="container-fluid mt-5"> <div class="container-fluid mt-5">
<h3>Staff</h3> <h3>Staff</h3>
</div> </div>
<app-people-list [people]="show.people"></app-people-list> <app-people-list [people]="show.people"></app-people-list>

View File

@ -4,149 +4,149 @@
a a
{ {
color: #ffffff; color: #ffffff;
} }
.backdrop .backdrop
{ {
margin-top: -68px; margin-top: -68px;
position: relative; position: relative;
z-index: -1; z-index: -1;
> img > img
{ {
width: 100%; width: 100%;
max-height: 75vh; max-height: 75vh;
object-fit: cover; object-fit: cover;
min-height: 20vh; min-height: 20vh;
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
min-height: 60vh; min-height: 60vh;
} }
} }
&:after &:after
{ {
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.6) 100%); 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: -12rem; margin-top: -12rem;
} }
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
margin-top: -13rem; margin-top: -13rem;
} }
@include media-breakpoint-up(lg) @include media-breakpoint-up(lg)
{ {
margin-top: -19rem; margin-top: -19rem;
} }
@include media-breakpoint-up(xl) @include media-breakpoint-up(xl)
{ {
margin-top: -23rem; margin-top: -23rem;
} }
} }
.poster .poster
{ {
width: 33%; width: 33%;
background-color: #333333; background-color: #333333;
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
width: 25%; width: 25%;
} }
} }
.main .main
{ {
align-self: center; align-self: center;
padding-left: 2.5em; padding-left: 2.5em;
.info .info
{ {
margin-top: -3.25rem; margin-top: -3.25rem;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
margin-top: 0; margin-top: 0;
} }
.title .title
{ {
font-weight: 900 !important; font-weight: 900 !important;
} }
.date .date
{ {
font-weight: 300 !important; font-weight: 300 !important;
} }
} }
.buttons .buttons
{ {
> button > button
{ {
outline: none; outline: none;
margin: .3em; margin: .3em;
} }
} }
} }
.secondary .secondary
{ {
position: relative; position: relative;
> img > img
{ {
max-width: 100%; max-width: 100%;
} }
> div > div
{ {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }
> div > p > div > p
{ {
opacity: .87; opacity: .87;
} }
} }
.overview .overview
{ {
opacity: .87; opacity: .87;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
padding-top: 2.25rem; padding-top: 2.25rem;
} }
} }
hr hr
{ {
margin: 0 10px 0 10px; margin: 0 10px 0 10px;
border-right: 1px solid rgba(255, 255, 255, .60); border-right: 1px solid rgba(255, 255, 255, .60);
border-top: 0; border-top: 0;
height: inherit; height: inherit;
} }
.genre .genre
{ {
opacity: .8; opacity: .8;
} }

View File

@ -7,76 +7,76 @@ import { Episode } from "../../models/episode";
import { Show } from "../../models/show"; import { Show } from "../../models/show";
@Component({ @Component({
selector: 'app-show-details', selector: 'app-show-details',
templateUrl: './show-details.component.html', templateUrl: './show-details.component.html',
styleUrls: ['./show-details.component.scss'] styleUrls: ['./show-details.component.scss']
}) })
export class ShowDetailsComponent implements OnInit export class ShowDetailsComponent implements OnInit
{ {
show: Show; show: Show;
episodes: Episode[] = null; episodes: Episode[] = null;
season: number; season: number;
private toolbar: HTMLElement; private toolbar: HTMLElement;
private backdrop: HTMLElement; private backdrop: HTMLElement;
constructor(private route: ActivatedRoute, private http: HttpClient, private snackBar: MatSnackBar, private title: Title) constructor(private route: ActivatedRoute, private http: HttpClient, private snackBar: MatSnackBar, private title: Title)
{ {
this.route.queryParams.subscribe(params => this.route.queryParams.subscribe(params =>
{ {
this.season = params["season"]; this.season = params["season"];
}); });
this.route.data.subscribe(data => this.route.data.subscribe(data =>
{ {
this.show = data.show; this.show = data.show;
this.title.setTitle(this.show.title + " - Kyoo"); this.title.setTitle(this.show.title + " - Kyoo");
if (this.season == null || this.show.seasons.find(x => x.seasonNumber == this.season) == null) if (this.season == null || this.show.seasons.find(x => x.seasonNumber == this.season) == null)
this.season = 1; this.season = 1;
this.getEpisodes(); this.getEpisodes();
}); });
} }
ngOnInit() ngOnInit()
{ {
this.toolbar = document.getElementById("toolbar"); this.toolbar = document.getElementById("toolbar");
this.backdrop = document.getElementById("backdrop"); this.backdrop = document.getElementById("backdrop");
window.addEventListener("scroll", this.scroll, true); window.addEventListener("scroll", this.scroll, true);
this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, 0) !important`); this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, 0) !important`);
} }
ngOnDestroy() ngOnDestroy()
{ {
window.removeEventListener("scroll", this.scroll, true); window.removeEventListener("scroll", this.scroll, true);
this.title.setTitle("Kyoo"); this.title.setTitle("Kyoo");
this.toolbar.setAttribute("style", `background-color: #000000 !important`); this.toolbar.setAttribute("style", `background-color: #000000 !important`);
} }
scroll = () => scroll = () =>
{ {
let opacity: number = 2 * window.scrollY / this.backdrop.clientHeight; let opacity: number = 2 * window.scrollY / this.backdrop.clientHeight;
this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, ${opacity}) !important`); this.toolbar.setAttribute("style", `background-color: rgba(0, 0, 0, ${opacity}) !important`);
} }
getEpisodes() getEpisodes()
{ {
if (this.show == null) if (this.show == null)
return; return;
if (this.show.seasons.find(x => x.seasonNumber == this.season).episodes != null) if (this.show.seasons.find(x => x.seasonNumber == this.season).episodes != null)
this.episodes = this.show.seasons.find(x => x.seasonNumber == this.season).episodes; this.episodes = this.show.seasons.find(x => x.seasonNumber == this.season).episodes;
this.http.get<Episode[]>("api/episodes/" + this.show.slug + "/season/" + this.season).subscribe((episodes: Episode[]) => this.http.get<Episode[]>("api/episodes/" + this.show.slug + "/season/" + this.season).subscribe((episodes: Episode[]) =>
{ {
this.show.seasons.find(x => x.seasonNumber == this.season).episodes = episodes; this.show.seasons.find(x => x.seasonNumber == this.season).episodes = episodes;
this.episodes = episodes; this.episodes = episodes;
}, error => }, error =>
{ {
console.log(error.status + " - " + error.message); console.log(error.status + " - " + error.message);
this.snackBar.open("An unknow error occured while getting episodes.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 }); this.snackBar.open("An unknow error occured while getting episodes.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
}); });
} }
} }

View File

@ -1,12 +1,12 @@
<div class="scroll-row mb-5"> <div class="scroll-row mb-5">
<div class="shows-container" #scrollView (scroll)="onScroll()"> <div class="shows-container" #scrollView (scroll)="onScroll()">
<a class="show" *ngFor="let show of this.shows" routerLink="/show/{{show.slug}}" href="/show/{{show.slug}}"> <a class="show" *ngFor="let show of this.shows" routerLink="/show/{{show.slug}}" href="/show/{{show.slug}}">
<div matRipple [style.background-image]="getThumb(show.slug)"> </div> <div matRipple [style.background-image]="getThumb(show.slug)"> </div>
<p class="title">{{show.title}}</p> <p class="title">{{show.title}}</p>
<p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p> <p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template> <ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
</a> </a>
</div> </div>
<button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn leftBtn d-none" #leftBtn (click)="scrollLeft()"><mat-icon>arrow_left</mat-icon></button>
<button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button> <button mat-raised-button color="accent" class="scrollBtn rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div> </div>

View File

@ -4,145 +4,145 @@
.shows-container .shows-container
{ {
display: flex; display: flex;
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
overflow-x: auto; overflow-x: auto;
min-width: 100%; min-width: 100%;
flex-shrink: 0; flex-shrink: 0;
flex-direction: row; flex-direction: row;
&::-webkit-scrollbar &::-webkit-scrollbar
{ {
height: 4px; height: 4px;
background: transparent; background: transparent;
} }
&::-webkit-scrollbar-thumb &::-webkit-scrollbar-thumb
{ {
background-color: #999; background-color: #999;
border-radius: 90px; border-radius: 90px;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
background-color: rgb(134, 127, 127); background-color: rgb(134, 127, 127);
} }
} }
} }
.show .show
{ {
width: 33%; width: 33%;
min-width: 120px; min-width: 120px;
max-width: 200px; max-width: 200px;
list-style: none; list-style: none;
padding: .5em; padding: .5em;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
outline: none; outline: none;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
@include media-breakpoint-up(sm) @include media-breakpoint-up(sm)
{ {
width: 25%; width: 25%;
} }
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
width: 20%; width: 20%;
padding: 1em; padding: 1em;
} }
@include media-breakpoint-up(lg) @include media-breakpoint-up(lg)
{ {
width: 18%; width: 18%;
} }
@include media-breakpoint-up(xl) @include media-breakpoint-up(xl)
{ {
width: 15%; width: 15%;
} }
&:focus, &:hover &:focus, &:hover
{ {
> div > div
{ {
outline: solid var(--accentColor); outline: solid var(--accentColor);
} }
> .title > .title
{ {
text-decoration: underline; text-decoration: underline;
} }
} }
> div > div
{ {
width: 100%; width: 100%;
height: 0; height: 0;
padding-top: 147.0588%; padding-top: 147.0588%;
background-size: cover; background-size: cover;
background-color: #333333; background-color: #333333;
} }
> p > p
{ {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center; text-align: center;
margin-bottom: 0px; margin-bottom: 0px;
opacity: 1; opacity: 1;
&.date &.date
{ {
opacity: 0.8; opacity: 0.8;
font-size: 0.8em; font-size: 0.8em;
} }
} }
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
cursor: pointer; cursor: pointer;
} }
} }
.scroll-row .scroll-row
{ {
position: relative; position: relative;
&:host-context(.hoverEnabled) &:hover &:host-context(.hoverEnabled) &:hover
{ {
.scrollBtn .scrollBtn
{ {
display: block; display: block;
} }
} }
} }
.scrollBtn .scrollBtn
{ {
padding: 0; padding: 0;
outline: none; outline: none;
min-width: 0; min-width: 0;
position: absolute; position: absolute;
top: 30%; top: 30%;
bottom: 40%; bottom: 40%;
display: none; display: none;
&.leftBtn &.leftBtn
{ {
left: 0; left: 0;
padding-left: 10px; padding-left: 10px;
padding-right: 2px; padding-right: 2px;
} }
&.rightBtn &.rightBtn
{ {
right: 0; right: 0;
padding-right: 10px; padding-right: 10px;
padding-left: 2px; padding-left: 2px;
} }
} }

View File

@ -1,3 +1,3 @@
export const environment = { export const environment = {
production: true production: true
}; };

View File

@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
production: false production: false
}; };
/* /*
@ -13,4 +13,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact * This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown. * on performance if an error is thrown.
*/ */
// import 'zone.js/dist/zone-error'; // Included with Angular CLI. // import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@ -1,15 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Kyoo</title> <title>Kyoo</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
</body> </body>
</html> </html>

View File

@ -72,8 +72,8 @@ let SubtitleManager = (function() {
} }
var det_C0_C1 = (C[0] * C[3]) - (C[2] * C[1]); var det_C0_C1 = (C[0] * C[3]) - (C[2] * C[1]);
var det_C0_X = (C[0] * X[1]) - (C[2] * X[0]); var det_C0_X = (C[0] * X[1]) - (C[2] * X[0]);
var det_X_C1 = (X[0] * C[3]) - (X[1] * C[1]); var det_X_C1 = (X[0] * C[3]) - (X[1] * C[1]);
var alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1; var alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
var alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1; var alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
var segLength = norm(subtract(points[0], points[len-1])); var segLength = norm(subtract(points[0], points[len-1]));
@ -209,8 +209,8 @@ let SubtitleManager = (function() {
} else if (curr.start_time < prev.end_time) { } else if (curr.start_time < prev.end_time) {
let [prev_start_time,prev_start_value,prev_prev_accel] = points[points.length-2]; let [prev_start_time,prev_start_value,prev_prev_accel] = points[points.length-2];
let last = points[points.length-1]; let last = points[points.length-1];
last[0] = curr.start_time; last[0] = curr.start_time;
last[1] = prev_start_value + (prev.end_value - prev_start_value) * Math.pow((curr.start_time - prev_start_time) / (prev.end_time - prev_start_time), last[2]); last[1] = prev_start_value + (prev.end_value - prev_start_value) * Math.pow((curr.start_time - prev_start_time) / (prev.end_time - prev_start_time), last[2]);
} }
points.push([curr.end_time,curr.end_value,curr.accel]); points.push([curr.end_time,curr.end_value,curr.accel]);
@ -251,8 +251,8 @@ let SubtitleManager = (function() {
let combineAdjacentBlocks = text => text.replace(reAdjacentBlocks,"$1$2").replace(reAdjacentBlocks,"$1$2"); let combineAdjacentBlocks = text => text.replace(reAdjacentBlocks,"$1$2").replace(reAdjacentBlocks,"$1$2");
// Map to convert SSAv4 alignment values to ASSv4+ values. // Map to convert SSAv4 alignment values to ASSv4+ values.
// 1, 2, 3, 5, 6, 7, 9, 10, 11 // 1, 2, 3, 5, 6, 7, 9, 10, 11
let SSA_ALIGNMENT_MAP = [0, 1, 2, 3, 0, 7, 8, 9, 0, 4, 5, 6]; let SSA_ALIGNMENT_MAP = [0, 1, 2, 3, 0, 7, 8, 9, 0, 4, 5, 6];
// Alias for creating SVG elements. // Alias for creating SVG elements.
let createSVGElement = document.createElementNS.bind(document,"http://www.w3.org/2000/svg"); let createSVGElement = document.createElementNS.bind(document,"http://www.w3.org/2000/svg");
@ -803,10 +803,10 @@ let SubtitleManager = (function() {
return computedPaths[pathID]; return computedPaths[pathID];
path = path.toLowerCase(); path = path.toLowerCase();
path = path.replace(/b/g,"C"); // cubic bézier curve to point 3 using point 1 and 2 as the control points path = path.replace(/b/g,"C"); // cubic bézier curve to point 3 using point 1 and 2 as the control points
path = path.replace(/l/g,"L"); // line-to <x>, <y> path = path.replace(/l/g,"L"); // line-to <x>, <y>
path = path.replace(/m/g,"Z M"); // move-to <x>, <y> (closing the shape first) path = path.replace(/m/g,"Z M"); // move-to <x>, <y> (closing the shape first)
path = path.replace(/n/g,"M"); // move-to <x>, <y> (without closing the shape) path = path.replace(/n/g,"M"); // move-to <x>, <y> (without closing the shape)
// extend b-spline to <x>, <y> // extend b-spline to <x>, <y>
// The "p" command is only supposed to be used after an "s" command, // The "p" command is only supposed to be used after an "s" command,
@ -829,7 +829,7 @@ let SubtitleManager = (function() {
// done by copying the starting location and the first two points // done by copying the starting location and the first two points
// to the end of the spline. // to the end of the spline.
// before: x0 y0 s x1 y1 x2 y2 ... c // before: x0 y0 s x1 y1 x2 y2 ... c
// after: x0 y0 s x1 y1 x2 y2 ... x0 y0 x1 y1 x2 y2 // after: x0 y0 s x1 y1 x2 y2 ... x0 y0 x1 y1 x2 y2
changes = true; changes = true;
while (changes) { while (changes) {
changes = false; changes = false;
@ -842,10 +842,10 @@ let SubtitleManager = (function() {
// 3rd degree uniform b-spline // 3rd degree uniform b-spline
// SVG doesn't have this, so we have convert them to a series of // SVG doesn't have this, so we have convert them to a series of
// Bézier curves. // Bézier curves.
// x0 y0 s x1 y1 x2 y2 x3 y3 x4 y4 x5 y5 // x0 y0 s x1 y1 x2 y2 x3 y3 x4 y4 x5 y5
// |-----------------------| Bézier 1 // |-----------------------| Bézier 1
// |---------------------| Bézier 2 // |---------------------| Bézier 2
// |---------------------| Bézier 3 // |---------------------| Bézier 3
// Since the start point for a Bézier is different from a spline, // Since the start point for a Bézier is different from a spline,
// we also need to add a move before the first Bézier and after the // we also need to add a move before the first Bézier and after the
// last Bézier. However, the bounding box of b-splines is different // last Bézier. However, the bounding box of b-splines is different
@ -1099,8 +1099,8 @@ let SubtitleManager = (function() {
let newPieces = []; let newPieces = [];
for (let piece of pieces) { for (let piece of pieces) {
// convert text // convert text
// from "{overide1}some text here{overridde2}more text ..." // from "{overide1}some text here{overridde2}more text ..."
// to [["override1",["some"," ","text"," ","here"]], ["override2",["more"," ","text"]], ...] // to [["override1",["some"," ","text"," ","here"]], ["override2",["more"," ","text"]], ...]
// taking care not to split on non-breaking spaces or paths // taking care not to split on non-breaking spaces or paths
let data = piece.text.split("{").slice(1).map(a => a.split("}")).map(b => [b[0], isPath(b[0]) ? [b[1]] : b[1].split(/([^\S\xA0]+)/g)]); let data = piece.text.split("{").slice(1).map(a => a.split("}")).map(b => [b[0], isPath(b[0]) ? [b[1]] : b[1].split(/([^\S\xA0]+)/g)]);
@ -1230,19 +1230,19 @@ let SubtitleManager = (function() {
if (slice.width) slices.push(slice); if (slice.width) slices.push(slice);
// Bubble pieces down through the slices while keeping the constraints: // Bubble pieces down through the slices while keeping the constraints:
// don't make a line shorter than the one after it // don't make a line shorter than the one after it
// don't include whitespace at the start or end // don't include whitespace at the start or end
let bubbled, last_slice = slices.length - 1; let bubbled, last_slice = slices.length - 1;
do { do {
bubbled = false; bubbled = false;
for (let i = last_slice; i > 0; --i) { for (let i = last_slice; i > 0; --i) {
let curr = slices[i], prev = slices[i-1]; let curr = slices[i], prev = slices[i-1];
/* prev curr /* prev curr
|----------| |--| Before |----------| |--| Before
XXXXXXXX XX XXXX XXXXXXXX XX XXXX
|------| |------| After |------| |------| After
prev curr prev curr
*/ */
while (true) { while (true) {
// Find the next non-whitespace piece // Find the next non-whitespace piece
@ -1376,19 +1376,19 @@ let SubtitleManager = (function() {
if (slice.width) slices.unshift(slice); if (slice.width) slices.unshift(slice);
// Bubble pieces up through the slices while keeping the constraints: // Bubble pieces up through the slices while keeping the constraints:
// don't make a line shorter than the one before it // don't make a line shorter than the one before it
// don't include whitespace at the start or end // don't include whitespace at the start or end
let bubbled, last_slice = slices.length - 1; let bubbled, last_slice = slices.length - 1;
do { do {
bubbled = false; bubbled = false;
for (let i = 0; i < last_slice; ++i) { for (let i = 0; i < last_slice; ++i) {
let curr = slices[i], next = slices[i+1]; let curr = slices[i], next = slices[i+1];
/* curr next /* curr next
|--| |----------| Before |--| |----------| Before
XXXX XX XXXXXXXX XXXX XX XXXXXXXX
|------| |------| After |------| |------| After
curr next curr next
*/ */
while (true) { while (true) {
// Find the next non-whitespace piece // Find the next non-whitespace piece
@ -2196,8 +2196,8 @@ let SubtitleManager = (function() {
this.group = null; this.group = null;
this.Margin = {"L" : (data.MarginL && parseInt(data.MarginL,10)) || renderer.styles[data.Style].MarginL, this.Margin = {"L" : (data.MarginL && parseInt(data.MarginL,10)) || renderer.styles[data.Style].MarginL,
"R" : (data.MarginR && parseInt(data.MarginR,10)) || renderer.styles[data.Style].MarginR, "R" : (data.MarginR && parseInt(data.MarginR,10)) || renderer.styles[data.Style].MarginR,
"V" : (data.MarginV && parseInt(data.MarginV,10)) || renderer.styles[data.Style].MarginV}; "V" : (data.MarginV && parseInt(data.MarginV,10)) || renderer.styles[data.Style].MarginV};
this.time = {"start" : timeConvert(data.Start), "end" : timeConvert(data.End)}; this.time = {"start" : timeConvert(data.Start), "end" : timeConvert(data.End)};
this.time.milliseconds = (this.time.end - this.time.start) * 1000; this.time.milliseconds = (this.time.end - this.time.start) * 1000;
@ -2982,8 +2982,8 @@ let SubtitleManager = (function() {
// Create the font-face CSS. // Create the font-face CSS.
css += "@font-face {\n"; css += "@font-face {\n";
css += " font-family: \"" + fontname + "\";\n"; css += " font-family: \"" + fontname + "\";\n";
css += " src: url(data:font/" + submime + ";charset=utf-8;base64," + btoa(fontdata) + ");\n"; css += " src: url(data:font/" + submime + ";charset=utf-8;base64," + btoa(fontdata) + ");\n";
css += "}\n\n"; css += "}\n\n";
} }

View File

@ -6,8 +6,8 @@ import { environment } from './environments/environment';
import "hammerjs" import "hammerjs"
if (environment.production) { if (environment.production) {
enableProdMode(); enableProdMode();
} }
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err)); .catch(err => console.error(err));

View File

@ -2,11 +2,11 @@ import { Show } from "./show";
export interface Collection export interface Collection
{ {
slug: string; slug: string;
name: string; name: string;
poster: string; poster: string;
overview: string; overview: string;
startYear: number, startYear: number,
endYear: number, endYear: number,
shows: Show[]; shows: Show[];
} }

View File

@ -1,13 +1,13 @@
export interface Episode export interface Episode
{ {
seasonNumber: number; seasonNumber: number;
episodeNumber: number; episodeNumber: number;
title: string; title: string;
thumb: string; thumb: string;
link: string; link: string;
overview: string; overview: string;
releaseDate; releaseDate;
runtime: number; runtime: number;
externalIDs: string; externalIDs: string;
showTitle: string; showTitle: string;
} }

View File

@ -1,5 +1,5 @@
export interface Genre export interface Genre
{ {
slug: string; slug: string;
name: string; name: string;
} }

View File

@ -1,9 +1,9 @@
export interface People export interface People
{ {
slug: string; slug: string;
name: string; name: string;
role: string; role: string;
type: string; type: string;
externalIDs: string; externalIDs: string;
} }

View File

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

View File

@ -5,20 +5,20 @@ import { Studio } from "./studio";
export interface Show export interface Show
{ {
slug: string; slug: string;
title: string; title: string;
Aliases: string[]; Aliases: string[];
overview: string; overview: string;
genres: Genre[]; genres: Genre[];
status: string; status: string;
studio: Studio; studio: Studio;
people: People[]; people: People[];
seasons: Season[]; seasons: Season[];
trailerUrl: string; trailerUrl: string;
isCollection: boolean; isCollection: boolean;
startYear: number; startYear: number;
endYear : number; endYear : number;
externalIDs: string; externalIDs: string;
} }

View File

@ -1,5 +1,5 @@
export interface Studio export interface Studio
{ {
slug: string; slug: string;
name: string; name: string;
} }

View File

@ -2,31 +2,31 @@ import { Episode } from "./episode";
export interface WatchItem export interface WatchItem
{ {
showTitle: string; showTitle: string;
showSlug: string; showSlug: string;
seasonNumber: number; seasonNumber: number;
episodeNumber: number; episodeNumber: number;
title: string; title: string;
link: string; link: string;
duration: number; duration: number;
releaseDate; releaseDate;
previousEpisode: string; previousEpisode: string;
nextEpisode: Episode; nextEpisode: Episode;
container: string; container: string;
video: Track; video: Track;
audios: Track[]; audios: Track[];
subtitles: Track[]; subtitles: Track[];
} }
export interface Track export interface Track
{ {
displayName: string; displayName: string;
title: string; title: string;
language: string; language: string;
isDefault: boolean; isDefault: boolean;
isForced: boolean; isForced: boolean;
codec: string; codec: string;
link: string; link: string;
} }

View File

@ -3,9 +3,9 @@
* You can add your own extra polyfills to this file. * You can add your own extra polyfills to this file.
* *
* This file is divided into 2 sections: * This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file. * file.
* *
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
@ -19,14 +19,14 @@
*/ */
/** IE10 and IE11 requires the following for NgClass support on SVG elements */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`. // import 'classlist.js'; // Run `npm install --save classlist.js`.
/** /**
* Web Animations `@angular/platform-browser/animations` * Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/ */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`. // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** /**
* By default, zone.js will patch all possible macroTask and DomEvents * By default, zone.js will patch all possible macroTask and DomEvents
@ -45,17 +45,17 @@
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
* *
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge * with the following flag, it will bypass `zone.js` patch for IE/Edge
* *
* (window as any).__Zone_enable_cross_context_check = true; * (window as any).__Zone_enable_cross_context_check = true;
* *
*/ */
/*************************************************************************************************** /***************************************************************************************************
* Zone JS is required by default for Angular itself. * Zone JS is required by default for Angular itself.
*/ */
import 'zone.js/dist/zone'; // Included with Angular CLI. import 'zone.js/dist/zone'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************

View File

@ -6,10 +6,10 @@
@import "~bootstrap/scss/variables"; @import "~bootstrap/scss/variables";
$theme-colors: ( $theme-colors: (
"primary": #0a1128, "primary": #0a1128,
"secondary": #000000, "secondary": #000000,
"accentColor": #e23c00, "accentColor": #e23c00,
"textPrimary": #ffffff "textPrimary": #ffffff
); );
$body-bg: theme-color("primary"); $body-bg: theme-color("primary");
@ -18,12 +18,12 @@ $font-family-base: "Roboto", Arial, sans-serif;
p p
{ {
opacity: .6; opacity: .6;
} }
h6 h6
{ {
opacity: .87; opacity: .87;
} }
@import "~bootstrap/scss/bootstrap"; @import "~bootstrap/scss/bootstrap";
@ -43,63 +43,63 @@ $theme: mat-dark-theme($primary, $accent);
.mat-ripple-element .mat-ripple-element
{ {
background-color: rgba(255, 255, 255, .3) !important; background-color: rgba(255, 255, 255, .3) !important;
} }
.mat-card-header-text .mat-card-header-text
{ {
margin: 0 5px !important; margin: 0 5px !important;
} }
//Material Icons //Material Icons
@font-face { @font-face {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(/iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */ src: url(/iconfont/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'), src: local('Material Icons'),
local('MaterialIcons-Regular'), local('MaterialIcons-Regular'),
url(/iconfont/MaterialIcons-Regular.woff2) format('woff2'), url(/iconfont/MaterialIcons-Regular.woff2) format('woff2'),
url(/iconfont/MaterialIcons-Regular.woff) format('woff'), url(/iconfont/MaterialIcons-Regular.woff) format('woff'),
url(/iconfont/MaterialIcons-Regular.ttf) format('truetype'); url(/iconfont/MaterialIcons-Regular.ttf) format('truetype');
} }
.material-icons .material-icons
{ {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 24px; /* Preferred icon size */ font-size: 24px; /* Preferred icon size */
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
text-transform: none; text-transform: none;
letter-spacing: normal; letter-spacing: normal;
word-wrap: normal; word-wrap: normal;
white-space: nowrap; white-space: nowrap;
direction: ltr; direction: ltr;
/* Support for all WebKit browsers. */ /* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */ /* Support for Safari and Chrome. */
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
/* Support for Firefox. */ /* Support for Firefox. */
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
/* Support for IE. */ /* Support for IE. */
font-feature-settings: 'liga'; font-feature-settings: 'liga';
} }
mat-icon mat-icon
{ {
vertical-align: middle; vertical-align: middle;
} }
.snackError .snackError
{ {
background-color: theme-color("accentColor"); background-color: theme-color("accentColor");
color: theme-color("textPrimary"); color: theme-color("textPrimary");
} }
.scroll-row .scroll-row
{ {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
} }

View File

@ -3,16 +3,16 @@
import 'zone.js/dist/zone-testing'; import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing'; import { getTestBed } from '@angular/core/testing';
import { import {
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
declare const require: any; declare const require: any;
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting() platformBrowserDynamicTesting()
); );
// Then we find all the tests. // Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/); const context = require.context('./', true, /\.spec\.ts$/);

View File

@ -3,157 +3,157 @@ Object.defineProperty(exports, "__esModule", { value: true });
var detect_browser_1 = require("detect-browser"); var detect_browser_1 = require("detect-browser");
var method; var method;
(function (method) { (function (method) {
method["direct"] = "Direct Play"; method["direct"] = "Direct Play";
method["transmux"] = "Transmux"; method["transmux"] = "Transmux";
method["transcode"] = "Transcode"; method["transcode"] = "Transcode";
})(method = exports.method || (exports.method = {})); })(method = exports.method || (exports.method = {}));
; ;
var SupportList = /** @class */ (function () { var SupportList = /** @class */ (function () {
function SupportList() { function SupportList() {
} }
return SupportList; return SupportList;
}()); }());
exports.SupportList = SupportList; exports.SupportList = SupportList;
function getPlaybackMethod(player, item) { function getPlaybackMethod(player, item) {
var supportList = getWhatIsSupported(player, item); var supportList = getWhatIsSupported(player, item);
if (supportList.container) { if (supportList.container) {
if (supportList.videoCodec && supportList.audioCodec) if (supportList.videoCodec && supportList.audioCodec)
return method.direct; return method.direct;
return method.transcode; return method.transcode;
} }
if (supportList.videoCodec && supportList.audioCodec) if (supportList.videoCodec && supportList.audioCodec)
return method.transmux; return method.transmux;
return method.transcode; return method.transcode;
} }
exports.getPlaybackMethod = getPlaybackMethod; exports.getPlaybackMethod = getPlaybackMethod;
function getWhatIsSupported(player, item) { function getWhatIsSupported(player, item) {
var supportList = new SupportList(); var supportList = new SupportList();
var browser = detect_browser_1.detect(); var browser = detect_browser_1.detect();
if (!browser) { if (!browser) {
supportList.container = false; supportList.container = false;
supportList.videoCodec = false; supportList.videoCodec = false;
supportList.audioCodec = false; supportList.audioCodec = false;
} }
else { else {
supportList.container = containerIsSupported(player, item.container, browser.name) && item.audios.length <= 1; supportList.container = containerIsSupported(player, item.container, browser.name) && item.audios.length <= 1;
supportList.videoCodec = videoCodecIsSupported(player, item.video.codec, browser.name); supportList.videoCodec = videoCodecIsSupported(player, item.video.codec, browser.name);
supportList.audioCodec = audioCodecIsSupported(player, item.audios.map(function (value) { return value.codec; }), browser.name); supportList.audioCodec = audioCodecIsSupported(player, item.audios.map(function (value) { return value.codec; }), browser.name);
} }
return (supportList); return (supportList);
} }
exports.getWhatIsSupported = getWhatIsSupported; exports.getWhatIsSupported = getWhatIsSupported;
function containerIsSupported(player, container, browser) { function containerIsSupported(player, container, browser) {
var supported = false; var supported = false;
switch (container) { switch (container) {
case "asf": case "asf":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
//videoAudioCodecs = []; //videoAudioCodecs = [];
break; break;
case "avi": case "avi":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
break; break;
case "mpg": case "mpg":
case "mpeg": case "mpeg":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
break; break;
case "flv": case "flv":
supported = browser == "tizen" || browser == "orsay"; supported = browser == "tizen" || browser == "orsay";
break; break;
case "3gp": case "3gp":
case "mts": case "mts":
case "trp": case "trp":
case "vob": case "vob":
case "vro": case "vro":
supported = browser == "tizen" || browser == "orsay"; supported = browser == "tizen" || browser == "orsay";
break; break;
case "mov": case "mov":
supported = browser == "tizen" || browser == "orsay" || browser == "edge" || browser == "chrome"; supported = browser == "tizen" || browser == "orsay" || browser == "edge" || browser == "chrome";
break; break;
case "m2ts": case "m2ts":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
break; break;
case "wmv": case "wmv":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
//videoAudioCodecs = []; //videoAudioCodecs = [];
break; break;
case "ts": case "ts":
supported = browser == "tizen" || browser == "orsay" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "edge";
break; break;
case "mp4": case "mp4":
case "m4v": case "m4v":
supported = true; supported = true;
break; break;
case "mkv": case "mkv":
supported = browser == "tizen" || browser == "orsay" || browser == "chrome" || browser == "edge"; supported = browser == "tizen" || browser == "orsay" || browser == "chrome" || browser == "edge";
if (supported) if (supported)
break; break;
if (player.canPlayType("video/x-matroska") || player.canPlayType("video/mkv")) if (player.canPlayType("video/x-matroska") || player.canPlayType("video/mkv"))
supported = true; supported = true;
break; break;
default: default:
break; break;
} }
return supported; return supported;
} }
//SHOULD CHECK FOR DEPTH (8bits ok but 10bits unsuported for almost every browsers) //SHOULD CHECK FOR DEPTH (8bits ok but 10bits unsuported for almost every browsers)
function videoCodecIsSupported(player, codec, browser) { function videoCodecIsSupported(player, codec, browser) {
switch (codec) { switch (codec) {
case "h264": case "h264":
return !!player.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); //The !! is used to parse the string as a bool return !!player.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); //The !! is used to parse the string as a bool
case "h265": case "h265":
case "hevc": case "hevc":
if (browser == "tizen" || browser == "orsay" || browser == "xboxOne" || browser == "ios") if (browser == "tizen" || browser == "orsay" || browser == "xboxOne" || browser == "ios")
return true; return true;
//SHOULD SUPPORT CHROMECAST ULTRA //SHOULD SUPPORT CHROMECAST ULTRA
// if (browser.chromecast) // if (browser.chromecast)
// { // {
// var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; // var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
// if (isChromecastUltra) // if (isChromecastUltra)
// { // {
// return true; // return true;
// } // }
// } // }
return !!player.canPlayType('video/hevc; codecs="hevc, aac"'); return !!player.canPlayType('video/hevc; codecs="hevc, aac"');
case "mpeg2video": case "mpeg2video":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
case "vc1": case "vc1":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
case "msmpeg4v2": case "msmpeg4v2":
return browser == "orsay" || browser == "tizen"; return browser == "orsay" || browser == "tizen";
case "vp8": case "vp8":
return !!player.canPlayType('video/webm; codecs="vp8'); return !!player.canPlayType('video/webm; codecs="vp8');
case "vp9": case "vp9":
return !!player.canPlayType('video/webm; codecs="vp9"'); return !!player.canPlayType('video/webm; codecs="vp9"');
case "vorbis": case "vorbis":
return browser == "orsay" || browser == "tizen" || !!player.canPlayType('video/webm; codecs="vp8'); return browser == "orsay" || browser == "tizen" || !!player.canPlayType('video/webm; codecs="vp8');
default: default:
return false; return false;
} }
} }
//SHOULD CHECK FOR NUMBER OF AUDIO CHANNEL (2 ok but 5 not in some browsers) //SHOULD CHECK FOR NUMBER OF AUDIO CHANNEL (2 ok but 5 not in some browsers)
function audioCodecIsSupported(player, codecs, browser) { function audioCodecIsSupported(player, codecs, browser) {
for (var _i = 0, codecs_1 = codecs; _i < codecs_1.length; _i++) { for (var _i = 0, codecs_1 = codecs; _i < codecs_1.length; _i++) {
var codec = codecs_1[_i]; var codec = codecs_1[_i];
switch (codec) { switch (codec) {
case "mp3": case "mp3":
return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"') || return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"') ||
!!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"'); !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"');
case "aac": case "aac":
return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"'); return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"');
case "mp2": case "mp2":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
case "pcm_s16le": case "pcm_s16le":
case "pcm_s24le": case "pcm_s24le":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
case "aac_latm": case "aac_latm":
return browser == "orsay" || browser == "tizen"; return browser == "orsay" || browser == "tizen";
case "opus": case "opus":
return !!player.canPlayType('audio/ogg; codecs="opus"'); return !!player.canPlayType('audio/ogg; codecs="opus"');
case "flac": case "flac":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
default: default:
return false; return false;
} }
} }
} }
//# sourceMappingURL=playbackMethodDetector.js.map //# sourceMappingURL=playbackMethodDetector.js.map

View File

@ -121,16 +121,16 @@ function videoCodecIsSupported(player: HTMLVideoElement, codec: string, browser:
case "hevc": case "hevc":
if (browser == "tizen" || browser == "orsay" || browser == "xboxOne" || browser == "ios") if (browser == "tizen" || browser == "orsay" || browser == "xboxOne" || browser == "ios")
return true; return true;
//SHOULD SUPPORT CHROMECAST ULTRA //SHOULD SUPPORT CHROMECAST ULTRA
// if (browser.chromecast) // if (browser.chromecast)
// { // {
// var isChromecastUltra = userAgent.indexOf('aarch64') !== -1; // var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
// if (isChromecastUltra) // if (isChromecastUltra)
// { // {
// return true; // return true;
// } // }
// } // }
return !!player.canPlayType('video/hevc; codecs="hevc, aac"'); return !!player.canPlayType('video/hevc; codecs="hevc, aac"');
case "mpeg2video": case "mpeg2video":
return browser == "orsay" || browser == "tizen" || browser == "edge"; return browser == "orsay" || browser == "tizen" || browser == "edge";
@ -145,7 +145,7 @@ function videoCodecIsSupported(player: HTMLVideoElement, codec: string, browser:
case "vorbis": case "vorbis":
return browser == "orsay" || browser == "tizen" || !!player.canPlayType('video/webm; codecs="vp8'); return browser == "orsay" || browser == "tizen" || !!player.canPlayType('video/webm; codecs="vp8');
default: default:
return false; return false;
} }
} }
@ -175,5 +175,5 @@ function audioCodecIsSupported(player: HTMLVideoElement, codecs: string[], brows
default: default:
return false; return false;
} }
} }
} }