Removing the web app files

This commit is contained in:
Zoe Roux 2020-01-12 21:04:46 +01:00
parent 04beaf4c49
commit b96b79f02d
111 changed files with 65 additions and 22578 deletions

View File

@ -1,51 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo", "Kyoo\Kyoo.csproj", "{0F8275B6-C7DD-42DF-A168-755C81B1C329}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unit Tests", "Unit Tests\Unit Tests.csproj", "{CC8144B5-8868-4EA7-B171-4E47746ED003}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|x64.ActiveCfg = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|x64.Build.0 = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|x86.ActiveCfg = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|x86.Build.0 = Debug|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.Build.0 = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|x64.ActiveCfg = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|x64.Build.0 = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|x86.ActiveCfg = Release|Any CPU
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|x86.Build.0 = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|x64.ActiveCfg = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|x64.Build.0 = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|x86.ActiveCfg = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Debug|x86.Build.0 = Debug|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|Any CPU.Build.0 = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|x64.ActiveCfg = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|x64.Build.0 = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|x86.ActiveCfg = Release|Any CPU
{CC8144B5-8868-4EA7-B171-4E47746ED003}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2940DD02-D039-437D-B47B-169D9B83B16A}
EndGlobalSection
EndGlobal

View File

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>

View File

@ -1,13 +0,0 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -1,46 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@ -1,27 +0,0 @@
# Kyoo
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -1,133 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Kyoo": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [
"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
"./node_modules/hls.js/dist/hls.js",
"./src/libraries/subtitles.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "3mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "Kyoo:build"
},
"configurations": {
"production": {
"browserTarget": "Kyoo:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "Kyoo:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.sass"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "Kyoo:serve"
},
"configurations": {
"production": {
"devServerTarget": "Kyoo:serve:production"
}
}
}
}
}},
"defaultProject": "Kyoo"
}

View File

@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -1,32 +0,0 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -1,23 +0,0 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to Kyoo!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

View File

@ -1,13 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/Kyoo'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
{
"name": "kyoo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/common": "^8.2.14",
"@angular/compiler": "^8.2.14",
"@angular/core": "^8.2.14",
"@angular/forms": "^8.2.14",
"@angular/material": "^8.2.3",
"@angular/platform-browser": "^8.2.14",
"@angular/platform-browser-dynamic": "^8.2.14",
"@angular/router": "^8.2.14",
"bootstrap": "^4.4.1",
"detect-browser": "^4.8.0",
"hammerjs": "^2.0.8",
"hls.js": "^0.12.4",
"jquery": "^3.4.1",
"popper.js": "^1.16.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.803.21",
"@angular/cli": "^8.3.21",
"@angular/compiler-cli": "^8.2.14",
"@angular/language-service": "^8.2.14",
"@types/bootstrap": "^4.3.1",
"@types/hls.js": "^0.12.5",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "^2.0.8",
"@types/jquery": "^3.3.31",
"@types/node": "~8.9.4",
"@types/video.js": "^7.3.3",
"codelyzer": "^5.2.1",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.5.1",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}

View File

@ -1,42 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BrowseComponent } from './browse/browse.component';
import { CollectionComponent } from "./collection/collection.component";
import { NotFoundComponent } from './not-found/not-found.component';
import { PlayerComponent } from "./player/player.component";
import { SearchComponent } from "./search/search.component";
import { CollectionResolverService } from "./services/collection-resolver.service";
import { LibraryResolverService } from './services/library-resolver.service';
import { PeopleResolverService } from "./services/people-resolver.service";
import { SearchResolverService } from "./services/search-resolver.service";
import { ShowResolverService } from './services/show-resolver.service';
import { StreamResolverService } from "./services/stream-resolver.service";
import { ShowDetailsComponent } from './show-details/show-details.component';
const routes: Routes = [
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
{ path: "collection/:collection-slug", component: CollectionComponent, resolve: { collection: CollectionResolverService } },
{ path: "people/:people-slug", component: CollectionComponent, resolve: { collection: PeopleResolverService } },
{ path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService } },
{ path: "search/:query", component: SearchComponent, resolve: { items: SearchResolverService } },
{ path: "**", component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes,
{
scrollPositionRestoration: "enabled"
})],
exports: [RouterModule],
providers: [
LibraryResolverService,
ShowResolverService,
CollectionResolverService,
PeopleResolverService,
StreamResolverService,
SearchResolverService
]
})
export class AppRoutingModule { }

View File

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

View File

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

View File

@ -1,76 +0,0 @@
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Event, Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
import * as $ from "jquery";
import { Location } from "@angular/common";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent
{
libraries: Library[];
isLoading: boolean = false;
constructor(http: HttpClient, private router: Router, private location: Location)
{
http.get<Library[]>("api/libraries").subscribe(result =>
{
this.libraries = result;
}, error => console.error(error));
this.router.events.subscribe((event: Event) =>
{
switch (true)
{
case event instanceof NavigationStart:
this.isLoading = true;
break;
case event instanceof NavigationEnd:
case event instanceof NavigationCancel:
case event instanceof NavigationError:
this.isLoading = false;
break;
default:
this.isLoading = false;
break;
}
});
if (!navigator.userAgent.match(/Mobi/))
document.body.classList.add("hoverEnabled");
}
openSearch()
{
let input: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
input.value = "";
input.focus();
}
onUpdateValue(event)
{
let query: string = event.target.value;
if (query != "")
{
event.target.classList.add("searching");
this.router.navigate(["/search/" + query], { replaceUrl: this.router.url.startsWith("/search/") });
}
else
{
event.target.classList.remove("searching");
this.location.back();
}
}
}
interface Library
{
id: number;
slug: string;
name: string;
}

View File

@ -1,60 +0,0 @@
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatRippleModule } from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatSliderModule } from '@angular/material/slider';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowseComponent } from './browse/browse.component';
import { CollectionComponent } from './collection/collection.component';
import { EpisodesListComponent } from './episodes-list/episodes-list.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { PeopleListComponent } from './people-list/people-list.component';
import { PlayerComponent } from './player/player.component';
import { SearchComponent } from './search/search.component';
import { ShowDetailsComponent } from './show-details/show-details.component';
import { ShowsListComponent } from './shows-list/shows-list.component';
@NgModule({
declarations: [
AppComponent,
NotFoundComponent,
BrowseComponent,
ShowDetailsComponent,
EpisodesListComponent,
PlayerComponent,
CollectionComponent,
SearchComponent,
PeopleListComponent,
ShowsListComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
BrowserAnimationsModule,
MatSnackBarModule,
MatProgressBarModule,
MatButtonModule,
MatIconModule,
MatSelectModule,
MatMenuModule,
MatSliderModule,
MatTooltipModule,
MatRippleModule,
MatCardModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

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

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowseComponent } from './browse.component';
describe('BrowseComponent', () => {
let component: BrowseComponent;
let fixture: ComponentFixture<BrowseComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BrowseComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BrowseComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@ -1,14 +0,0 @@
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-md-4 col-lg-3 col-xl-2 collection-info">
<div [style.background-image]="getThumb()"></div>
</div>
<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>
<h5 class="date" *ngIf="collection.endYear; else elseBlock">{{collection.startYear}} - {{collection.endYear}}</h5>
<ng-template #elseBlock><h5 class="date">{{collection.startYear}}</h5></ng-template>
<hr />
<app-browse [shows]="collection.shows"></app-browse>
</div>
</div>
</div>

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CollectionComponent } from './collection.component';
describe('CollectionComponent', () => {
let component: CollectionComponent;
let fixture: ComponentFixture<CollectionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CollectionComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,27 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { Collection } from "../../models/collection";
import { ActivatedRoute } from "@angular/router";
import { DomSanitizer } from "@angular/platform-browser";
@Component({
selector: 'app-collection',
templateUrl: './collection.component.html',
styleUrls: ['./collection.component.scss']
})
export class CollectionComponent
{
collection: Collection;
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer)
{
this.route.data.subscribe((data) =>
{
this.collection = data.collection;
});
}
getThumb()
{
return this.sanitizer.bypassSecurityTrustStyle("url(" + this.collection.poster + ")");
}
}

View File

@ -1,21 +0,0 @@
<div class="root">
<div class="episodes" #scrollView (scroll)="onScroll()">
<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)">
<button mat-icon-button class="playBtn"><i class="material-icons playIcon">play_circle_outline</i></button>
</div>
<ng-container *ngIf="displayShowTitle; else noTitle;">
<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>
<p class="subtitle">{{episode.title}}</p>
</ng-container>
<ng-template #noTitle>
<h6 *ngIf="episode.seasonNumber != 0; else elseBlock;" class="title">S{{episode.seasonNumber}}:E{{episode.episodeNumber}} - {{episode.title}}</h6>
<ng-template #elseBlock><h6 class="title">{{episode.title}}</h6></ng-template>
<p class="overview">{{episode.overview}}</p>
</ng-template>
</a>
</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 rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div>

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EpisodesListComponent } from './episodes-list.component';
describe('EpisodesListComponent', () => {
let component: EpisodesListComponent;
let fixture: ComponentFixture<EpisodesListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EpisodesListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EpisodesListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,60 +0,0 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { MatButton } from "@angular/material/button";
import { DomSanitizer } from "@angular/platform-browser";
import { Episode } from "../../models/episode";
@Component({
selector: 'app-episodes-list',
templateUrl: './episodes-list.component.html',
styleUrls: ['./episodes-list.component.scss']
})
export class EpisodesListComponent
{
@Input() displayShowTitle: boolean = false;
@Input() episodes: Episode[];
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
@ViewChild("episodeDom", { static: false }) private episode: ElementRef;
constructor(private sanitizer: DomSanitizer) { }
scrollLeft()
{
let scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
}
scrollRight()
{
let scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
}
roundScroll(offset: number): number
{
let episodeSize: number = this.episode.nativeElement.scrollWidth;
offset = Math.round(offset / episodeSize) * episodeSize;
if (offset == 0)
offset = episodeSize;
return offset;
}
onScroll()
{
if (this.scrollView.nativeElement.scrollLeft <= 0)
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
else
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
else
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
}
sanitize(url: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
}
}

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NotFoundComponent } from './not-found.component';
describe('NotFoundComponent', () => {
let component: NotFoundComponent;
let fixture: ComponentFixture<NotFoundComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NotFoundComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NotFoundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@ -1,11 +0,0 @@
<div class="scroll-row mb-5">
<div class="people-container" #scrollView (scroll)="onScroll()">
<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>
<h6 class="name">{{people.name}}</h6>
<p class="role">{{people.role}}</p>
</a>
</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 rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div>

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PeopleListComponent } from './people-list.component';
describe('PeopleListComponent', () => {
let component: PeopleListComponent;
let fixture: ComponentFixture<PeopleListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PeopleListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PeopleListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,48 +0,0 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { MatButton } from "@angular/material/button";
import { DomSanitizer } from "@angular/platform-browser";
import { People } from "../../models/people";
@Component({
selector: 'app-people-list',
templateUrl: './people-list.component.html',
styleUrls: ['./people-list.component.scss']
})
export class PeopleListComponent
{
@Input() people: People[];
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
constructor(private sanitizer: DomSanitizer) { }
scrollLeft()
{
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
}
scrollRight()
{
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
}
onScroll()
{
if (this.scrollView.nativeElement.scrollLeft <= 0)
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
else
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
else
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
}
getPeopleIcon(slug: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(/peopleimg/" + slug + ")");
}
}

View File

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

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PlayerComponent } from './player.component';
describe('PlayerComponent', () => {
let component: PlayerComponent;
let fixture: ComponentFixture<PlayerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PlayerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PlayerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,644 +0,0 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatSnackBar } from "@angular/material/snack-bar";
import { DomSanitizer, Title } from "@angular/platform-browser";
import { ActivatedRoute, Event, NavigationCancel, NavigationEnd, NavigationStart, Router } from "@angular/router";
import { Track, WatchItem } from "../../models/watch-item";
import { Location } from "@angular/common";
import * as Hls from "hls.js"
import { getPlaybackMethod, method, getWhatIsSupported, SupportList } from "../../videoSupport/playbackMethodDetector";
declare var SubtitleManager: any;
@Component({
selector: 'app-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class PlayerComponent implements OnInit
{
item: WatchItem;
volume: number = 100;
seeking: boolean = false;
videoHider;
controllerHovered: boolean = false;
selectedSubtitle: Track;
hours: number;
minutes: number = 0;
seconds: number = 0;
maxHours: number;
maxMinutes: number;
maxSeconds: number;
playIcon: string = "pause"; //Icon used by the play btn.
volumeIcon: string = "volume_up"; //Icon used by the volume btn.
fullscreenIcon: string = "fullscreen"; //Icon used by the fullscreen btn.
playTooltip: string = "Pause"; //Text used in the play tooltip
fullscreenTooltip: string = "Fullscreen"; //Text used in the fullscreen tooltip
playMethod: method = method.direct;
methodType = method;
displayStats: boolean = false;
supportList: SupportList;
private player: HTMLVideoElement;
private hlsPlayer: Hls = new Hls();
private thumb: HTMLElement;
private progress: HTMLElement;
private buffered: HTMLElement;
constructor(private route: ActivatedRoute, private sanitizer: DomSanitizer, private snackBar: MatSnackBar, private title: Title, private router: Router, private location: Location) { }
ngOnInit()
{
document.getElementById("nav").classList.add("d-none");
this.route.data.subscribe((data) =>
{
this.item = data.item;
this.item.duration = 20 * 60;
if (this.player)
{
this.player.load();
this.initPlayBtn();
}
this.setDuration(this.item.duration);
this.title.setTitle(this.item.showTitle + " S" + this.item.seasonNumber + ":E" + this.item.episodeNumber + " - Kyoo");
if (navigator.userAgent.match(/Mobi/) && document.fullscreenElement == null)
{
this.fullscreen();
screen.orientation.lock("landscape");
$("#fullscreen").addClass("d-none");
$("#volume").addClass("d-none");
}
setTimeout(() =>
{
if (this.player)
this.init();
});
});
}
ngAfterViewInit()
{
this.player = document.getElementById("player") as HTMLVideoElement;
this.thumb = document.getElementById("thumb") as HTMLElement;
this.progress = document.getElementById("progress") as HTMLElement;
this.buffered = document.getElementById("buffered") as HTMLElement;
this.player.controls = false;
this.player.onplay = () =>
{
this.initPlayBtn();
}
this.player.onpause = () =>
{
this.playIcon = "play_arrow"
this.playTooltip = "Play";
}
this.player.ontimeupdate = () =>
{
if (!this.seeking)
this.updateTime(this.player.currentTime);
};
this.player.onprogress = () =>
{
if (this.player.buffered.length > 0)
this.buffered.style.width = (this.player.buffered.end(this.player.buffered.length - 1) / this.item.duration * 100) + "%";
if (this.player.duration != undefined && this.player.duration != Infinity)
this.setDuration(this.player.duration);
};
let loadIndicator: HTMLElement = document.getElementById("loadIndicator") as HTMLElement;
this.player.onwaiting = () =>
{
loadIndicator.classList.remove("d-none");
}
this.player.oncanplay = () =>
{
loadIndicator.classList.add("d-none");
}
this.player.onended = () =>
{
this.next();
}
this.player.onerror = () =>
{
if (this.playMethod == method.transcode)
{
this.snackBar.open("This episode can't be played.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 10000 });
}
else
{
if (this.playMethod == method.direct)
this.playMethod = method.transmux;
else
this.playMethod = method.transcode;
this.selectPlayMethod(this.playMethod);
}
}
let progressBar: HTMLElement = document.getElementById("progress-bar") as HTMLElement;
$(progressBar).click((event) =>
{
event.preventDefault();
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.player.currentTime = time;
this.updateTime(time);
});
if (!navigator.userAgent.match(/Mobi/))
{
$(document).mousemove((event) =>
{
if (this.seeking)
{
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.updateTime(time);
}
else
{
document.getElementById("hover").classList.remove("idle");
document.documentElement.style.cursor = "";
clearTimeout(this.videoHider);
this.videoHider = setTimeout((ev: MouseEvent) =>
{
if (!this.player.paused && !this.controllerHovered)
{
document.getElementById("hover").classList.add("idle");
document.documentElement.style.cursor = "none";
}
}, 2000);
}
});
$(progressBar).mousedown((event) =>
{
event.preventDefault();
this.seeking = true;
progressBar.classList.add("seeking");
this.player.pause();
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.updateTime(time);
});
$(document).mouseup((event) =>
{
if (this.seeking)
{
event.preventDefault();
this.seeking = false;
progressBar.classList.remove("seeking");
let time: number = this.getTimeFromSeekbar(progressBar, event.pageX);
this.player.currentTime = time;
this.player.play();
}
});
$("#controller").mouseenter(() => { this.controllerHovered = true; });
$("#controller").mouseleave(() => { this.controllerHovered = false; });
}
else
{
$(progressBar).on("touchstart", (event) =>
{
event.preventDefault();
this.seeking = true;
progressBar.classList.add("seeking");
this.player.pause();
let time: number = this.getTimeFromSeekbar(progressBar, event.changedTouches[0].pageX);
this.updateTime(time);
});
$(document).on("touchend", (event) =>
{
if (this.seeking)
{
event.preventDefault();
this.seeking = false;
progressBar.classList.remove("seeking");
let time: number = this.getTimeFromSeekbar(progressBar, event.changedTouches[0].pageX);
this.player.currentTime = time;
this.player.play();
}
});
$(document).on("touchmove", (event) =>
{
if (this.seeking)
{
let time: number = this.getTimeFromSeekbar(progressBar, event.changedTouches[0].pageX);
this.updateTime(time);
}
});
}
//Initialize the timout at the document initialization.
this.videoHider = setTimeout(() =>
{
if (!this.player.paused)
{
document.getElementById("hover").classList.add("idle");
document.documentElement.style.cursor = "none";
}
}, 1000);
document.addEventListener("fullscreenchange", () =>
{
if (navigator.userAgent.match(/Mobi/))
{
if (document.fullscreenElement == null && this.router.url.startsWith("/watch"))
this.back();
}
else
{
if (document.fullscreenElement != null)
{
this.fullscreenIcon = "fullscreen_exit";
this.fullscreenTooltip = "Exit fullscreen";
}
else
{
this.fullscreenIcon = "fullscreen";
this.fullscreenTooltip = "Fullscreen";
}
}
});
$(window).keydown((e) =>
{
switch (e.keyCode)
{
case 32: //space
this.tooglePlayback();
break;
case 38: //Key up
this.changeVolume(this.volume + 5);
this.snackBar.open(this.volume + "%", null, { verticalPosition: "top", horizontalPosition: "right", duration: 300, panelClass: "volume" });
break;
case 40: //Key down
this.changeVolume(this.volume - 5);
this.snackBar.open(this.volume + "%", null, { verticalPosition: "top", horizontalPosition: "right", duration: 300, panelClass: "volume" });
break;
case 86: //V key
let subtitleIndex: number = this.item.subtitles.indexOf(this.selectedSubtitle);
let nextSub: Track;
if (subtitleIndex + 1 <= this.item.subtitles.length)
nextSub = this.item.subtitles[subtitleIndex + 1];
else
nextSub = this.item.subtitles[0];
this.selectSubtitle(nextSub);
break;
case 70: //F key
this.fullscreen();
break;
case 77: //M key
this.toogleMute();
if (this.player.muted)
this.snackBar.open("Sound muted.", null, { verticalPosition: "top", horizontalPosition: "right", duration: 750, panelClass: "info-panel" });
else
this.snackBar.open("Sound unmuted.", null, { verticalPosition: "top", horizontalPosition: "right", duration: 750, panelClass: "info-panel" });
break;
case 78: //N key
this.next();
break;
case 80: //P key
this.previous();
break;
default:
break;
}
});
this.router.events.subscribe((event: Event) =>
{
switch (true)
{
case event instanceof NavigationStart:
{
loadIndicator.classList.remove("d-none");
break;
}
case event instanceof NavigationEnd:
case event instanceof NavigationCancel:
{
loadIndicator.classList.add("d-none");
break;
}
default:
break;
}
});
setTimeout(() =>
{
this.init();
});
}
init()
{
let queryMethod: string = this.route.snapshot.queryParams["method"];
if (queryMethod)
this.playMethod = method[queryMethod];
else
this.playMethod = getPlaybackMethod(this.player, this.item);
this.selectPlayMethod(this.playMethod);
let sub: string = this.route.snapshot.queryParams["sub"];
if (sub != null)
{
let languageCode: string = sub.substring(0, 3);
let forced: boolean = sub.length > 3 && sub.substring(4) == "for";
this.selectSubtitle(this.item.subtitles.find(x => x.language == languageCode && x.isForced == forced), false);
}
this.supportList = getWhatIsSupported(this.player, this.item);
setTimeout(() =>
{
this.snackBar.open("Playing: " + this.item.showTitle + " S" + this.item.seasonNumber + ":E" + this.item.episodeNumber, null, { verticalPosition: "top", horizontalPosition: "right", duration: 2000, panelClass: "info-panel" });
}, 750);
}
selectPlayMethod(playMethod: method)
{
this.playMethod = playMethod;
if (this.playMethod == method.direct)
{
this.player.src = "/video/" + this.item.link;
}
else if (this.playMethod == method.transmux)
{
this.hlsPlayer.loadSource("/video/transmux/" + this.item.link + "/");
this.hlsPlayer.attachMedia(this.player);
this.hlsPlayer.on(Hls.Events.MANIFEST_LOADED, () =>
{
this.player.play();
});
}
else
{
this.hlsPlayer.loadSource("/video/transcode/" + this.item.link + "/");
this.hlsPlayer.attachMedia(this.player);
this.hlsPlayer.on(Hls.Events.MANIFEST_LOADED, () =>
{
this.player.play();
});
}
}
back()
{
this.location.back();
}
next()
{
if (this.item.nextEpisode != null)
this.router.navigate(["/watch/" + this.item.nextEpisode.link], { queryParamsHandling: "merge", replaceUrl: true });
}
previous()
{
if (this.item.previousEpisode != null)
this.router.navigate(["/watch/" + this.item.previousEpisode], { queryParamsHandling: "merge", replaceUrl: true });
}
getTimeFromSeekbar(progressBar: HTMLElement, pageX: number)
{
return Math.max(0, Math.min((pageX - progressBar.offsetLeft) / progressBar.clientWidth, 1)) * this.item.duration;
}
setDuration(duration: number)
{
this.maxSeconds = Math.round(duration % 60);
this.maxMinutes = Math.round(duration / 60 % 60);
this.maxHours = Math.round(duration / 3600);
this.item.duration = duration;
}
updateTime(time: number)
{
this.hours = Math.round(time / 60 % 60);
this.seconds = Math.round(time % 60);
this.minutes = Math.round(time / 60);
this.thumb.style.transform = "translateX(" + (time / this.item.duration * 100) + "%)";
this.progress.style.width = (time / this.item.duration * 100) + "%";
}
ngOnDestroy()
{
if (document.fullscreen)
document.exitFullscreen();
document.getElementById("nav").classList.remove("d-none");
this.title.setTitle("Kyoo");
$(document).unbind();
$(window).unbind();
}
videoClicked()
{
if (!navigator.userAgent.match(/Mobi/))
this.tooglePlayback();
else
{
if ($("#hover").hasClass("idle"))
{
$("#hover").removeClass("idle");
clearTimeout(this.videoHider);
this.videoHider = setTimeout((ev: MouseEvent) =>
{
if (!this.player.paused)
{
document.getElementById("hover").classList.add("idle");
}
}, 1000);
}
else
{
$("#hover").addClass("idle");
clearTimeout(this.videoHider);
}
}
}
tooglePlayback()
{
if (this.player.paused)
this.player.play();
else
this.player.pause();
}
toogleMute()
{
if (this.player.muted)
this.player.muted = false;
else
this.player.muted = true;
this.updateVolumeBtn()
}
initPlayBtn()
{
this.playIcon = "pause";
this.playTooltip = "Pause";
}
fullscreen()
{
if (document.fullscreenElement == null)
document.body.requestFullscreen();
else
document.exitFullscreen();
}
//Value from 0 to 100
changeVolume(value: number)
{
value = Math.max(0, Math.min(value, 100));
this.player.muted = false;
this.player.volume = value / 100;
this.volume = value;
this.updateVolumeBtn();
}
updateVolumeBtn()
{
if (this.player.muted)
{
this.volumeIcon = "volume_off"
}
else
{
if (this.volume == 0)
this.volumeIcon = "volume_off";
else if (this.volume < 25)
this.volumeIcon = "volume_mute";
else if (this.volume < 65)
this.volumeIcon = "volume_down";
else
this.volumeIcon = "volume_up";
}
}
selectSubtitle(subtitle: Track, changeUrl: boolean = true)
{
if (changeUrl)
{
let subSlug: string;
if (subtitle != null)
{
subSlug = subtitle.language;
if (subtitle.isForced)
subSlug += "-for";
}
this.router.navigate([], { relativeTo: this.route, queryParams: { sub: subSlug }, replaceUrl: true, queryParamsHandling: "merge" });
}
this.selectedSubtitle = subtitle;
if (subtitle == null)
{
this.snackBar.open("Subtitle removed.", null, { verticalPosition: "top", horizontalPosition: "right", duration: 750, panelClass: "info-panel" });
SubtitleManager.remove(this.player);
this.removeHtmlTrack();
}
else
{
this.snackBar.open(subtitle.displayName + " subtitle loaded.", null, { verticalPosition: "top", horizontalPosition: "right", duration: 750, panelClass: "info-panel" });
this.removeHtmlTrack();
if (subtitle.codec == "ass")
SubtitleManager.add(this.player, subtitle.link, true);
else if (subtitle.codec == "subrip")
{
SubtitleManager.remove(this.player);
let track = document.createElement("track");
track.kind = "subtitles";
track.label = subtitle.displayName;
track.srclang = subtitle.language;
track.src = subtitle.link.replace(".srt", ".vtt");
track.classList.add("subtitle_container");
track.default = true;
track.onload = () =>
{
this.player.textTracks[0].mode = "showing";
};
this.player.appendChild(track);
}
}
}
getSupportedFeature(feature: string) : string
{
if (!this.supportList)
return "help";
switch (feature)
{
case "container":
return this.supportList.container ? "check_circle" : "cancel";
case "video":
return this.supportList.videoCodec ? "check_circle" : "cancel";
case "audio":
return this.supportList.audioCodec ? "check_circle" : "cancel";
default:
return "help";
}
}
removeHtmlTrack()
{
let elements = this.player.getElementsByTagName("track");
if (elements.length > 0)
elements.item(0).remove();
}
getThumb(url: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(" + url + ")");
}
}

View File

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

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,39 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { SearchResut } from "../../models/search-result";
import { Title } from "@angular/platform-browser";
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit, OnDestroy
{
items: SearchResut;
constructor(private route: ActivatedRoute, private title: Title) { }
ngOnInit()
{
this.route.data.subscribe((data) =>
{
this.items = data.items;
this.title.setTitle(this.items.query + " - Kyoo");
});
}
ngAfterViewInit()
{
let searchBar: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
searchBar.classList.add("searching");
searchBar.value = this.items.query;
}
ngOnDestroy()
{
let searchBar: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
searchBar.classList.remove("searching");
searchBar.value = "";
}
}

View File

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

View File

@ -1,45 +0,0 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { EMPTY, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Show } from "../../models/show";
@Injectable()
export class LibraryResolverService implements Resolve<Show[]>
{
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Show[] | Observable<Show[]> | Promise<Show[]>
{
let slug: string = route.paramMap.get("library-slug");
if (slug == null)
{
return this.http.get<Show[]>("api/shows").pipe(catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
this.snackBar.open("An unknow error occured.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
return EMPTY;
}));
}
else
{
return this.http.get<Show[]>("api/libraries/" + slug).pipe(catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
if (error.status == 404)
{
this.snackBar.open("Library \"" + 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 });
}
return EMPTY;
}));
}
}
}

View File

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

View File

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

View File

@ -1,31 +0,0 @@
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { EMPTY, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Show } from "../../models/show";
@Injectable()
export class ShowResolverService implements Resolve<Show>
{
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show>
{
let slug: string = route.paramMap.get("show-slug");
return this.http.get<Show>("api/shows/" + slug).pipe(catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
if (error.status == 404)
{
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 });
}
return EMPTY;
}));
}
}

View File

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

View File

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

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ShowDetailsComponent } from './show-details.component';
describe('ShowDetailsComponent', () => {
let component: ShowDetailsComponent;
let fixture: ComponentFixture<ShowDetailsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShowDetailsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShowDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@ -1,12 +0,0 @@
<div class="scroll-row mb-5">
<div class="shows-container" #scrollView (scroll)="onScroll()">
<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>
<p class="title">{{show.title}}</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>
</a>
</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 rightBtn" #rightBtn (click)="scrollRight()"><mat-icon>arrow_right</mat-icon></button>
</div>

View File

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

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ShowsListComponent } from './shows-list.component';
describe('ShowsListComponent', () => {
let component: ShowsListComponent;
let fixture: ComponentFixture<ShowsListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ShowsListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ShowsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,48 +0,0 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { MatButton } from "@angular/material/button";
import { DomSanitizer } from "@angular/platform-browser";
import { Show } from "../../models/show";
@Component({
selector: 'app-shows-list',
templateUrl: './shows-list.component.html',
styleUrls: ['./shows-list.component.scss']
})
export class ShowsListComponent
{
@Input() shows: Show[];
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
@ViewChild("leftBtn", { static: false }) private leftBtn: MatButton;
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
constructor(private sanitizer: DomSanitizer) { }
scrollLeft()
{
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
}
scrollRight()
{
let scroll: number = this.scrollView.nativeElement.offsetWidth * 0.80;
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
}
onScroll()
{
if (this.scrollView.nativeElement.scrollLeft <= 0)
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
else
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
else
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
}
getThumb(slug: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
}
}

View File

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

View File

@ -1,16 +0,0 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

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

View File

@ -1,23 +0,0 @@
.subtitle_container {
line-height: normal;
position: absolute;
pointer-events: none;
transform-origin: 0 0 0;
top: 0;
left: 0;
}
.subtitle_container text, .subtitle_container path {
dominant-baseline: text-before-edge;
text-anchor: start;
transform-box: view-box;
paint-order: stroke;
position: absolute;
top: 0;
left: 0;
}
.subtitle_container tspan {
white-space: pre;
}
.subtitle_container mask path {
fill: white;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import "hammerjs"
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +0,0 @@
import { Show } from "./show";
import { Episode } from "./episode";
import { People } from "./people";
import { Studio } from "./studio";
import { Genre } from "./genre";
export interface SearchResut
{
query: string;
shows: Show[];
episodes: Episode[];
people: People[];
genrwes: Genre[];
studios: Studio[];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,63 +0,0 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 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
* file.
*
* 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),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* 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).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (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
*
* 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
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

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

View File

@ -1,20 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

Some files were not shown because too many files have changed in this diff Show More