mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-07 10:14:13 -04:00
Fixing coding style
This commit is contained in:
parent
d36e332bcb
commit
1dbbc0a67a
@ -1,10 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from "@angular/core";
|
||||||
import { RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
import { RouteReuseStrategy, RouterModule, Routes } from "@angular/router";
|
||||||
import { ItemsGridComponent } from './components/items-grid/items-grid.component';
|
import { ItemsGridComponent } from "./components/items-grid/items-grid.component";
|
||||||
import { CustomRouteReuseStrategy } from "./misc/custom-route-reuse-strategy";
|
import { CustomRouteReuseStrategy } from "./misc/custom-route-reuse-strategy";
|
||||||
import { NotFoundComponent } from './pages/not-found/not-found.component';
|
import { NotFoundComponent } from "./pages/not-found/not-found.component";
|
||||||
import { PageResolver } from './services/page-resolver.service';
|
import { PageResolver } from "./services/page-resolver.service";
|
||||||
import { ShowDetailsComponent } from './pages/show-details/show-details.component';
|
import { ShowDetailsComponent } from "./pages/show-details/show-details.component";
|
||||||
import { AuthGuard } from "./auth/misc/authenticated-guard.service";
|
import { AuthGuard } from "./auth/misc/authenticated-guard.service";
|
||||||
import { LibraryItem } from "./models/resources/library-item";
|
import { LibraryItem } from "./models/resources/library-item";
|
||||||
import {
|
import {
|
||||||
@ -92,7 +92,7 @@ const routes: Routes = [
|
|||||||
|
|
||||||
// TODO implement an home page.
|
// TODO implement an home page.
|
||||||
|
|
||||||
{path: "", pathMatch: 'full', redirectTo: "/browse"},
|
{path: "", pathMatch: "full", redirectTo: "/browse"},
|
||||||
{path: "**", component: NotFoundComponent}
|
{path: "**", component: NotFoundComponent}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<ul class="navbar-nav flex-row flex-nowrap ml-auto">
|
<ul class="navbar-nav flex-row flex-nowrap ml-auto">
|
||||||
<li class="nav-item icon searchbar">
|
<li class="nav-item icon searchbar">
|
||||||
<mat-icon matTooltipPosition="below" matTooltip="Search" (click)="openSearch()">search</mat-icon>
|
<mat-icon matTooltipPosition="below" matTooltip="Search" (click)="openSearch()">search</mat-icon>
|
||||||
<input placeholder="Search" id="search" type="search" (input)="onUpdateValue($event)"/>
|
<input placeholder="Search" id="search" type="search" (input)="onUpdateValue($any($event))"/>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item" *ngIf="!this.isAuthenticated else accountDrop">
|
<li class="nav-item" *ngIf="!this.isAuthenticated else accountDrop">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {Component} from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
Router,
|
Router,
|
||||||
@ -7,25 +7,27 @@ import {
|
|||||||
NavigationCancel,
|
NavigationCancel,
|
||||||
NavigationError
|
NavigationError
|
||||||
} from "@angular/router";
|
} from "@angular/router";
|
||||||
import {Location} from "@angular/common";
|
import { Location } from "@angular/common";
|
||||||
import {MatDialog} from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import {AccountComponent} from "./auth/account/account.component";
|
import { AccountComponent } from "./auth/account/account.component";
|
||||||
import {AuthService} from "./auth/auth.service";
|
import { AuthService } from "./auth/auth.service";
|
||||||
import {Library} from "./models/resources/library";
|
import { Library } from "./models/resources/library";
|
||||||
import {LibraryService} from "./services/api.service";
|
import { LibraryService } from "./services/api.service";
|
||||||
|
// noinspection ES6UnusedImports
|
||||||
import * as $ from "jquery";
|
import * as $ from "jquery";
|
||||||
|
import ChangeEvent = JQuery.ChangeEvent;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: "app-root",
|
||||||
templateUrl: './app.component.html',
|
templateUrl: "./app.component.html",
|
||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ["./app.component.scss"]
|
||||||
})
|
})
|
||||||
export class AppComponent
|
export class AppComponent
|
||||||
{
|
{
|
||||||
|
static isMobile: boolean = false;
|
||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
isLoading: boolean = false;
|
isLoading: boolean = false;
|
||||||
|
|
||||||
static isMobile: boolean = false;
|
|
||||||
|
|
||||||
constructor(private libraryService: LibraryService,
|
constructor(private libraryService: LibraryService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@ -61,18 +63,23 @@ export class AppComponent
|
|||||||
document.body.classList.add("hoverEnabled");
|
document.body.classList.add("hoverEnabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
openSearch()
|
get isAuthenticated(): boolean
|
||||||
{
|
{
|
||||||
let input: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
|
return this.authManager.isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
openSearch(): void
|
||||||
|
{
|
||||||
|
const input: HTMLInputElement = document.getElementById("search") as HTMLInputElement;
|
||||||
|
|
||||||
input.value = "";
|
input.value = "";
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateValue(event)
|
onUpdateValue(event: ChangeEvent<HTMLInputElement>): void
|
||||||
{
|
{
|
||||||
let query: string = event.target.value;
|
const query: string = event.target.value;
|
||||||
if (query != "")
|
if (query !== "")
|
||||||
{
|
{
|
||||||
event.target.classList.add("searching");
|
event.target.classList.add("searching");
|
||||||
this.router.navigate(["/search", query], {
|
this.router.navigate(["/search", query], {
|
||||||
@ -85,14 +92,9 @@ export class AppComponent
|
|||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openAccountDialog()
|
openAccountDialog(): void
|
||||||
{
|
{
|
||||||
this.dialog.open(AccountComponent, {width: "500px", data: this.authManager.account});
|
this.dialog.open(AccountComponent, {width: "500px", data: this.authManager.account});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAuthenticated(): boolean
|
|
||||||
{
|
|
||||||
return this.authManager.isAuthenticated;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from "@angular/common/http";
|
||||||
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from "@angular/material/button";
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from "@angular/material/card";
|
||||||
import { MatRippleModule } from '@angular/material/core';
|
import { MatRippleModule } from "@angular/material/core";
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from "@angular/material/icon";
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from "@angular/material/menu";
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
import { MatProgressBarModule } from "@angular/material/progress-bar";
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from "@angular/material/select";
|
||||||
import { MatSliderModule } from '@angular/material/slider';
|
import { MatSliderModule } from "@angular/material/slider";
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from "@angular/material/snack-bar";
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||||
import { BrowserModule, HammerModule } from "@angular/platform-browser";
|
import { BrowserModule, HammerModule } from "@angular/platform-browser";
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from "./app-routing.module";
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from "./app.component";
|
||||||
import { ItemsGridComponent } from './components/items-grid/items-grid.component';
|
import { ItemsGridComponent } from "./components/items-grid/items-grid.component";
|
||||||
import { CollectionComponent } from './pages/collection/collection.component';
|
import { CollectionComponent } from "./pages/collection/collection.component";
|
||||||
import { EpisodesListComponent } from './components/episodes-list/episodes-list.component';
|
import { EpisodesListComponent } from "./components/episodes-list/episodes-list.component";
|
||||||
import { NotFoundComponent } from './pages/not-found/not-found.component';
|
import { NotFoundComponent } from "./pages/not-found/not-found.component";
|
||||||
import { PeopleListComponent } from './components/people-list/people-list.component';
|
import { PeopleListComponent } from "./components/people-list/people-list.component";
|
||||||
import {
|
import {
|
||||||
BufferToWidthPipe,
|
BufferToWidthPipe,
|
||||||
FormatTimePipe,
|
FormatTimePipe,
|
||||||
PlayerComponent, SupportedButtonPipe,
|
PlayerComponent, SupportedButtonPipe,
|
||||||
VolumeToButtonPipe
|
VolumeToButtonPipe
|
||||||
} from "./pages/player/player.component";
|
} from "./pages/player/player.component";
|
||||||
import { SearchComponent } from './pages/search/search.component';
|
import { SearchComponent } from "./pages/search/search.component";
|
||||||
import { ShowDetailsComponent } from './pages/show-details/show-details.component';
|
import { ShowDetailsComponent } from "./pages/show-details/show-details.component";
|
||||||
import { FormsModule , ReactiveFormsModule} from "@angular/forms";
|
import { FormsModule , ReactiveFormsModule } from "@angular/forms";
|
||||||
import { MatInputModule } from "@angular/material/input";
|
import { MatInputModule } from "@angular/material/input";
|
||||||
import { MatFormFieldModule } from "@angular/material/form-field";
|
import { MatFormFieldModule } from "@angular/material/form-field";
|
||||||
import { MatTabsModule } from "@angular/material/tabs";
|
import { MatTabsModule } from "@angular/material/tabs";
|
||||||
import { PasswordValidator } from "./misc/password-validator";
|
import { PasswordValidator } from "./misc/password-validator";
|
||||||
import { MatCheckboxModule } from "@angular/material/checkbox";
|
import { MatCheckboxModule } from "@angular/material/checkbox";
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from "@angular/material/dialog";
|
||||||
import { FallbackDirective } from "./misc/fallback.directive";
|
import { FallbackDirective } from "./misc/fallback.directive";
|
||||||
import { AuthModule } from "./auth/auth.module";
|
import { AuthModule } from "./auth/auth.module";
|
||||||
import { AuthRoutingModule } from "./auth/auth-routing.module";
|
import { AuthRoutingModule } from "./auth/auth-routing.module";
|
||||||
import { TrailerDialogComponent } from './pages/trailer-dialog/trailer-dialog.component';
|
import { TrailerDialogComponent } from "./pages/trailer-dialog/trailer-dialog.component";
|
||||||
import { ItemsListComponent } from "./components/items-list/items-list.component";
|
import { ItemsListComponent } from "./components/items-list/items-list.component";
|
||||||
import { MetadataEditComponent } from './pages/metadata-edit/metadata-edit.component';
|
import { MetadataEditComponent } from "./pages/metadata-edit/metadata-edit.component";
|
||||||
import { MatChipsModule } from "@angular/material/chips";
|
import { MatChipsModule } from "@angular/material/chips";
|
||||||
import { MatAutocompleteModule } from "@angular/material/autocomplete";
|
import { MatAutocompleteModule } from "@angular/material/autocomplete";
|
||||||
import { MatExpansionModule } from "@angular/material/expansion";
|
import { MatExpansionModule } from "@angular/material/expansion";
|
||||||
@ -47,7 +47,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
|||||||
import { ShowGridComponent } from "./components/show-grid/show-grid.component";
|
import { ShowGridComponent } from "./components/show-grid/show-grid.component";
|
||||||
import { MatBadgeModule } from "@angular/material/badge";
|
import { MatBadgeModule } from "@angular/material/badge";
|
||||||
import { StartupService } from "./services/startup.service";
|
import { StartupService } from "./services/startup.service";
|
||||||
import { LongPressDirective } from './misc/long-press.directive';
|
import { LongPressDirective } from "./misc/long-press.directive";
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -105,6 +105,9 @@ import { LongPressDirective } from './misc/long-press.directive';
|
|||||||
HammerModule
|
HammerModule
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
exports: [
|
||||||
|
FallbackDirective
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
StartupService,
|
StartupService,
|
||||||
{
|
{
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
import {Component, ElementRef, Inject, ViewChild} from '@angular/core';
|
import { Component, ElementRef, Inject, ViewChild } from "@angular/core";
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
import {Account} from "../../models/account";
|
import { Account } from "../../models/account";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: "app-account",
|
||||||
templateUrl: './account.component.html',
|
templateUrl: "./account.component.html",
|
||||||
styleUrls: ['./account.component.scss']
|
styleUrls: ["./account.component.scss"]
|
||||||
})
|
})
|
||||||
export class AccountComponent
|
export class AccountComponent
|
||||||
{
|
{
|
||||||
selectedPicture: File;
|
selectedPicture: File;
|
||||||
@ViewChild("accountImg") accountImg: ElementRef;
|
@ViewChild("accountImg") accountImg: ElementRef;
|
||||||
|
|
||||||
constructor(public dialogRef: MatDialogRef<AccountComponent>,
|
constructor(public dialogRef: MatDialogRef<AccountComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public account: Account,
|
@Inject(MAT_DIALOG_DATA) public account: Account,
|
||||||
private http: HttpClient) {}
|
private http: HttpClient) {}
|
||||||
|
|
||||||
finish()
|
finish(): void
|
||||||
{
|
{
|
||||||
let data = new FormData();
|
const data: FormData = new FormData();
|
||||||
data.append("email", this.account.email);
|
data.append("email", this.account.email);
|
||||||
data.append("username", this.account.username);
|
data.append("username", this.account.username);
|
||||||
data.append("picture", this.selectedPicture);
|
data.append("picture", this.selectedPicture);
|
||||||
|
|
||||||
this.http.post("api/account/update", data).subscribe(() =>
|
this.http.post("api/account/update", data).subscribe(() =>
|
||||||
{
|
{
|
||||||
this.dialogRef.close(this.account);
|
this.dialogRef.close(this.account);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel(): void
|
||||||
{
|
{
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPictureSelected(event: any)
|
onPictureSelected(event: any): void
|
||||||
{
|
{
|
||||||
this.selectedPicture = event.target.files[0];
|
this.selectedPicture = event.target.files[0];
|
||||||
const reader = new FileReader();
|
const reader: FileReader = new FileReader();
|
||||||
reader.onloadend = () =>
|
reader.onloadend = () =>
|
||||||
{
|
{
|
||||||
this.accountImg.nativeElement.src = reader.result;
|
this.accountImg.nativeElement.src = reader.result;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {NgModule} from '@angular/core';
|
import { NgModule } from "@angular/core";
|
||||||
import {RouterModule, Routes} from "@angular/router";
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
import {UnauthorizedComponent} from "./unauthorized/unauthorized.component";
|
import { UnauthorizedComponent } from "./unauthorized/unauthorized.component";
|
||||||
import {LogoutComponent} from "./logout/logout.component";
|
import { LogoutComponent } from "./logout/logout.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "logout", component: LogoutComponent},
|
{path: "logout", component: LogoutComponent},
|
||||||
|
@ -25,7 +25,7 @@ import { AuthGuard } from "./misc/authenticated-guard.service";
|
|||||||
import { AuthorizerInterceptor } from "./misc/authorizer-interceptor.service";
|
import { AuthorizerInterceptor } from "./misc/authorizer-interceptor.service";
|
||||||
import { UnauthorizedComponent } from "./unauthorized/unauthorized.component";
|
import { UnauthorizedComponent } from "./unauthorized/unauthorized.component";
|
||||||
|
|
||||||
export function loadConfig(oidcConfigService: OidcConfigService)
|
export function loadConfig(oidcConfigService: OidcConfigService): () => Promise<any>
|
||||||
{
|
{
|
||||||
return () => oidcConfigService.withConfig({
|
return () => oidcConfigService.withConfig({
|
||||||
stsServer: window.location.origin,
|
stsServer: window.location.origin,
|
||||||
@ -91,7 +91,7 @@ export function loadConfig(oidcConfigService: OidcConfigService)
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AuthModule
|
export class AuthModule
|
||||||
{
|
{
|
||||||
constructor(http: HttpClient)
|
constructor(http: HttpClient)
|
||||||
{
|
{
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import { HttpClient } from "@angular/common/http";
|
import { Injectable } from "@angular/core";
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { OidcSecurityService } from "angular-auth-oidc-client";
|
import { OidcSecurityService } from "angular-auth-oidc-client";
|
||||||
import { Account } from "../models/account";
|
import { Account } from "../models/account";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class AuthService
|
export class AuthService
|
||||||
{
|
{
|
||||||
isAuthenticated: boolean = false;
|
isAuthenticated: boolean = false;
|
||||||
account: Account = null;
|
account: Account = null;
|
||||||
|
|
||||||
constructor(private oidcSecurityService: OidcSecurityService,
|
constructor(private oidcSecurityService: OidcSecurityService)
|
||||||
private http: HttpClient)
|
|
||||||
{
|
{
|
||||||
this.oidcSecurityService.checkAuth()
|
this.oidcSecurityService.checkAuth()
|
||||||
.subscribe((auth: boolean) => this.isAuthenticated = auth);
|
.subscribe((auth: boolean) => this.isAuthenticated = auth);
|
||||||
@ -24,17 +22,17 @@ export class AuthService
|
|||||||
email: x.email,
|
email: x.email,
|
||||||
username: x.username,
|
username: x.username,
|
||||||
picture: x.picture,
|
picture: x.picture,
|
||||||
permissions: x.permissions.split(',')
|
permissions: x.permissions.split(",")
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
login()
|
login(): void
|
||||||
{
|
{
|
||||||
this.oidcSecurityService.authorize();
|
this.oidcSecurityService.authorize();
|
||||||
}
|
}
|
||||||
|
|
||||||
logout()
|
logout(): void
|
||||||
{
|
{
|
||||||
// this.http.get("api/account/logout").subscribe(() =>
|
// this.http.get("api/account/logout").subscribe(() =>
|
||||||
// {
|
// {
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import {Injector, Pipe, PipeTransform} from '@angular/core';
|
import { Injector, Pipe, PipeTransform } from "@angular/core";
|
||||||
import {HttpClient, HttpHeaders} from "@angular/common/http";
|
import { HttpClient, HttpHeaders } from "@angular/common/http";
|
||||||
import {OidcSecurityService} from "angular-auth-oidc-client";
|
import { OidcSecurityService } from "angular-auth-oidc-client";
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'auth'
|
name: "auth"
|
||||||
})
|
})
|
||||||
export class AuthPipe implements PipeTransform
|
export class AuthPipe implements PipeTransform
|
||||||
{
|
{
|
||||||
private oidcSecurity: OidcSecurityService;
|
private oidcSecurity: OidcSecurityService;
|
||||||
|
|
||||||
constructor(private injector: Injector, private http: HttpClient) {}
|
constructor(private injector: Injector, private http: HttpClient) {}
|
||||||
|
|
||||||
async transform(uri: string): Promise<string>
|
async transform(uri: string): Promise<string>
|
||||||
{
|
{
|
||||||
if (this.oidcSecurity === undefined)
|
if (this.oidcSecurity === undefined)
|
||||||
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
||||||
let token = this.oidcSecurity.getToken();
|
const token: string = this.oidcSecurity.getToken();
|
||||||
if (!token)
|
if (!token)
|
||||||
return uri;
|
return uri;
|
||||||
const headers = new HttpHeaders({"Authorization": "Bearer " + token});
|
const headers: HttpHeaders = new HttpHeaders({Authorization: "Bearer " + token});
|
||||||
const img = await this.http.get(uri, {headers, responseType: 'blob'}).toPromise();
|
const img: Blob = await this.http.get(uri, {headers, responseType: "blob"}).toPromise();
|
||||||
const reader = new FileReader();
|
const reader: FileReader = new FileReader();
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
reader.onloadend = () => resolve(reader.result as string);
|
reader.onloadend = () => resolve(reader.result as string);
|
||||||
reader.readAsDataURL(img);
|
reader.readAsDataURL(img);
|
||||||
|
@ -1,31 +1,23 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {
|
import { CanActivate, CanLoad, Router } from "@angular/router";
|
||||||
CanActivate,
|
import { Observable } from "rxjs";
|
||||||
CanLoad,
|
import { AuthService } from "../auth.service";
|
||||||
Route,
|
|
||||||
UrlSegment,
|
|
||||||
ActivatedRouteSnapshot,
|
|
||||||
RouterStateSnapshot,
|
|
||||||
Router
|
|
||||||
} from '@angular/router';
|
|
||||||
import {Observable} from 'rxjs';
|
|
||||||
import {AuthService} from "../auth.service";
|
|
||||||
|
|
||||||
@Injectable({providedIn: "root"})
|
@Injectable({providedIn: "root"})
|
||||||
export class AuthGuard
|
export class AuthGuard
|
||||||
{
|
{
|
||||||
public static guards: any[] = [];
|
public static guards: any[] = [];
|
||||||
public static defaultPermissions: string[];
|
public static defaultPermissions: string[];
|
||||||
public static permissionsObservable: Observable<string[]>;
|
public static permissionsObservable: Observable<string[]>;
|
||||||
|
|
||||||
static forPermissions(...permissions: string[])
|
static forPermissions(...permissions: string[]): any
|
||||||
{
|
{
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class AuthenticatedGuard implements CanActivate, CanLoad
|
class AuthenticatedGuard implements CanActivate, CanLoad
|
||||||
{
|
{
|
||||||
constructor(private router: Router, private authManager: AuthService) {}
|
constructor(private router: Router, private authManager: AuthService) {}
|
||||||
|
|
||||||
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean>
|
async canActivate(): Promise<boolean>
|
||||||
{
|
{
|
||||||
if (!await this.checkPermissions())
|
if (!await this.checkPermissions())
|
||||||
{
|
{
|
||||||
@ -34,8 +26,8 @@ export class AuthGuard
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async canLoad(route: Route, segments: UrlSegment[]): Promise<boolean>
|
async canLoad(): Promise<boolean>
|
||||||
{
|
{
|
||||||
if (!await this.checkPermissions())
|
if (!await this.checkPermissions())
|
||||||
{
|
{
|
||||||
@ -44,24 +36,24 @@ export class AuthGuard
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkPermissions(): Promise<boolean>
|
async checkPermissions(): Promise<boolean>
|
||||||
{
|
{
|
||||||
if (this.authManager.isAuthenticated)
|
if (this.authManager.isAuthenticated)
|
||||||
{
|
{
|
||||||
const perms: string[] = this.authManager.account.permissions;
|
const perms: string[] = this.authManager.account.permissions;
|
||||||
for (let perm of permissions) {
|
for (const perm of permissions) {
|
||||||
if (!perms.includes(perm))
|
if (!perms.includes(perm))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (AuthGuard.defaultPermissions == undefined)
|
if (!AuthGuard.defaultPermissions)
|
||||||
await AuthGuard.permissionsObservable.toPromise()
|
await AuthGuard.permissionsObservable.toPromise();
|
||||||
|
|
||||||
for (let perm of permissions)
|
for (const perm of permissions)
|
||||||
if (!AuthGuard.defaultPermissions.includes(perm))
|
if (!AuthGuard.defaultPermissions.includes(perm))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import {Injectable, Injector} from '@angular/core';
|
import { Injectable, Injector } from "@angular/core";
|
||||||
import {
|
import {
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpHandler,
|
HttpHandler,
|
||||||
HttpEvent,
|
HttpEvent,
|
||||||
HttpInterceptor
|
HttpInterceptor
|
||||||
} from '@angular/common/http';
|
} from "@angular/common/http";
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from "rxjs";
|
||||||
import {OidcSecurityService} from "angular-auth-oidc-client";
|
import { OidcSecurityService } from "angular-auth-oidc-client";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthorizerInterceptor implements HttpInterceptor
|
export class AuthorizerInterceptor implements HttpInterceptor
|
||||||
{
|
{
|
||||||
private oidcSecurity: OidcSecurityService;
|
private oidcSecurity: OidcSecurityService;
|
||||||
|
|
||||||
|
|
||||||
constructor(private injector: Injector) {}
|
constructor(private injector: Injector) {}
|
||||||
|
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
||||||
{
|
{
|
||||||
if (this.oidcSecurity === undefined)
|
if (this.oidcSecurity === undefined)
|
||||||
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
||||||
let token = this.oidcSecurity.getToken();
|
const token: string = this.oidcSecurity.getToken();
|
||||||
if (token)
|
if (token)
|
||||||
request = request.clone({setHeaders: {Authorization: "Bearer " + token}});
|
request = request.clone({setHeaders: {Authorization: "Bearer " + token}});
|
||||||
return next.handle(request);
|
return next.handle(request);
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {AuthService} from "../auth.service";
|
import { AuthService } from "../auth.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-unauthorized',
|
selector: "app-unauthorized",
|
||||||
templateUrl: './unauthorized.component.html',
|
templateUrl: "./unauthorized.component.html",
|
||||||
styleUrls: ['./unauthorized.component.scss']
|
styleUrls: ["./unauthorized.component.scss"]
|
||||||
})
|
})
|
||||||
export class UnauthorizedComponent
|
export class UnauthorizedComponent
|
||||||
{
|
{
|
||||||
constructor(private authManager: AuthService) { }
|
constructor(private authManager: AuthService) { }
|
||||||
|
|
||||||
isLoggedIn() : boolean
|
isLoggedIn(): boolean
|
||||||
{
|
{
|
||||||
return this.authManager.isAuthenticated;
|
return this.authManager.isAuthenticated;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export class EpisodesListComponent extends HorizontalScroller
|
|||||||
|
|
||||||
openMenu(index: number): void
|
openMenu(index: number): void
|
||||||
{
|
{
|
||||||
const menu = this.menus.find((x, i) => i === index);
|
const menu: MatMenuTrigger = this.menus.find((x, i) => i === index);
|
||||||
menu.focus();
|
menu.focus();
|
||||||
menu.openMenu();
|
menu.openMenu();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, Input, OnInit } from "@angular/core";
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
import { FormControl } from "@angular/forms";
|
import { FormControl } from "@angular/forms";
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params, Router } from "@angular/router";
|
import { ActivatedRoute, ActivatedRouteSnapshot, Params, Router } from "@angular/router";
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||||
import { Genre } from "../../models/resources/genre";
|
import { Genre } from "../../models/resources/genre";
|
||||||
import { LibraryItem } from "../../models/resources/library-item";
|
import { LibraryItem } from "../../models/resources/library-item";
|
||||||
import { Page } from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
@ -14,36 +14,16 @@ import { Studio } from "../../models/resources/studio";
|
|||||||
import { ItemsUtils } from "../../misc/items-utils";
|
import { ItemsUtils } from "../../misc/items-utils";
|
||||||
import { PeopleService, StudioService } from "../../services/api.service";
|
import { PeopleService, StudioService } from "../../services/api.service";
|
||||||
import { PreLoaderService } from "../../services/pre-loader.service";
|
import { PreLoaderService } from "../../services/pre-loader.service";
|
||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs";
|
||||||
import { catchError, filter, map, mergeAll } from "rxjs/operators";
|
import { catchError, filter, map, mergeAll } from "rxjs/operators";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-items-grid',
|
selector: "app-items-grid",
|
||||||
templateUrl: './items-grid.component.html',
|
templateUrl: "./items-grid.component.html",
|
||||||
styleUrls: ['./items-grid.component.scss']
|
styleUrls: ["./items-grid.component.scss"]
|
||||||
})
|
})
|
||||||
export class ItemsGridComponent implements OnInit
|
export class ItemsGridComponent implements OnInit
|
||||||
{
|
{
|
||||||
@Input() page: Page<LibraryItem | Show | ShowRole | Collection>;
|
|
||||||
@Input() sortEnabled: boolean = true;
|
|
||||||
|
|
||||||
complexFiltersEnabled: boolean;
|
|
||||||
|
|
||||||
sortType: string = "title";
|
|
||||||
sortKeys: string[] = ["title", "start year", "end year"]
|
|
||||||
sortUp: boolean = true;
|
|
||||||
|
|
||||||
public static readonly showOnlyFilters: string[] = ["genres", "studio", "people"]
|
|
||||||
public static readonly filters: string[] = [].concat(...ItemsGridComponent.showOnlyFilters)
|
|
||||||
filters: {genres: Genre[], studio: Studio, people: People[]} = {genres: [], studio: null, people: []};
|
|
||||||
|
|
||||||
genres: Genre[] = [];
|
|
||||||
|
|
||||||
studioForm: FormControl = new FormControl();
|
|
||||||
filteredStudios: Observable<Studio[]>;
|
|
||||||
|
|
||||||
peopleForm: FormControl = new FormControl();
|
|
||||||
filteredPeople: Observable<People[]>;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
@ -70,57 +50,103 @@ export class ItemsGridComponent implements OnInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGenresFilterFromQuery(query: Params)
|
public static readonly showOnlyFilters: string[] = ["genres", "studio", "people"];
|
||||||
|
public static readonly filters: string[] = [].concat(...ItemsGridComponent.showOnlyFilters);
|
||||||
|
@Input() page: Page<LibraryItem | Show | ShowRole | Collection>;
|
||||||
|
@Input() sortEnabled: boolean = true;
|
||||||
|
|
||||||
|
complexFiltersEnabled: boolean;
|
||||||
|
|
||||||
|
sortType: string = "title";
|
||||||
|
sortKeys: string[] = ["title", "start year", "end year"];
|
||||||
|
sortUp: boolean = true;
|
||||||
|
filters: {genres: Genre[], studio: Studio, people: People[]} = {genres: [], studio: null, people: []};
|
||||||
|
|
||||||
|
genres: Genre[] = [];
|
||||||
|
|
||||||
|
studioForm: FormControl = new FormControl();
|
||||||
|
filteredStudios: Observable<Studio[]>;
|
||||||
|
|
||||||
|
peopleForm: FormControl = new FormControl();
|
||||||
|
filteredPeople: Observable<People[]>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* /browse -> /api/items | /api/shows
|
||||||
|
* /browse/:library -> /api/library/:slug/items | /api/library/:slug/shows
|
||||||
|
* /genre/:slug -> /api/shows
|
||||||
|
* /studio/:slug -> /api/shows
|
||||||
|
*
|
||||||
|
* /collection/:slug -> /api/collection/:slug/shows |> /api/collections/:slug/shows
|
||||||
|
* /people/:slug -> /api/people/:slug/roles |> /api/people/:slug/roles
|
||||||
|
*/
|
||||||
|
|
||||||
|
static routeMapper(route: ActivatedRouteSnapshot, endpoint: string, query: [string, string][]): string
|
||||||
|
{
|
||||||
|
const queryParams: [string, string][] = Object.entries(route.queryParams)
|
||||||
|
.filter(x => ItemsGridComponent.filters.includes(x[0]) || x[0] === "sortBy");
|
||||||
|
if (query)
|
||||||
|
queryParams.push(...query);
|
||||||
|
|
||||||
|
if (queryParams.some(x => ItemsGridComponent.showOnlyFilters.includes(x[0])))
|
||||||
|
endpoint = endpoint.replace(/items?$/, "show");
|
||||||
|
|
||||||
|
const params: string = queryParams.length > 0
|
||||||
|
? "?" + queryParams.map(x => `${x[0]}=${x[1]}`).join("&")
|
||||||
|
: "";
|
||||||
|
return `api/${endpoint}${params}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGenresFilterFromQuery(query: Params): void
|
||||||
{
|
{
|
||||||
let selectedGenres: string[] = [];
|
let selectedGenres: string[] = [];
|
||||||
if (query.genres?.startsWith("ctn:"))
|
if (query.genres?.startsWith("ctn:"))
|
||||||
selectedGenres = query.genres.substr(4).split(',');
|
selectedGenres = query.genres.substr(4).split(",");
|
||||||
else if (query.genres != null)
|
else if (query.genres != null)
|
||||||
selectedGenres = query.genres.split(',');
|
selectedGenres = query.genres.split(",");
|
||||||
if (this.router.url.startsWith("/genre"))
|
if (this.router.url.startsWith("/genre"))
|
||||||
selectedGenres.push(this.route.snapshot.params.slug);
|
selectedGenres.push(this.route.snapshot.params.slug);
|
||||||
|
|
||||||
this.filters.genres = this.genres.filter(x => selectedGenres.includes(x.slug));
|
this.filters.genres = this.genres.filter(x => selectedGenres.includes(x.slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStudioFilterFromQuery(query: Params)
|
updateStudioFilterFromQuery(query: Params): void
|
||||||
{
|
{
|
||||||
const slug: string = this.router.url.startsWith("/studio") ? this.route.snapshot.params.slug : query.studio;
|
const slug: string = this.router.url.startsWith("/studio") ? this.route.snapshot.params.slug : query.studio;
|
||||||
|
|
||||||
if (slug && this.filters.studio?.slug != slug)
|
if (slug && this.filters.studio?.slug !== slug)
|
||||||
{
|
{
|
||||||
this.filters.studio = {id: 0, slug: slug, name: slug};
|
this.filters.studio = {id: 0, slug, name: slug};
|
||||||
this.studioApi.get(slug).subscribe(x => this.filters.studio = x);
|
this.studioApi.get(slug).subscribe(x => this.filters.studio = x);
|
||||||
}
|
}
|
||||||
else if (!slug)
|
else if (!slug)
|
||||||
this.filters.studio = null;
|
this.filters.studio = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePeopleFilterFromQuery(query: Params)
|
updatePeopleFilterFromQuery(query: Params): void
|
||||||
{
|
{
|
||||||
let slugs: string[] = [];
|
let slugs: string[] = [];
|
||||||
if (query.people != null)
|
if (query.people != null)
|
||||||
{
|
{
|
||||||
if (query.people.startsWith("ctn:"))
|
if (query.people.startsWith("ctn:"))
|
||||||
slugs = query.people.substr(4).split(',');
|
slugs = query.people.substr(4).split(",");
|
||||||
else
|
else
|
||||||
slugs = query.people.split(',');
|
slugs = query.people.split(",");
|
||||||
}
|
}
|
||||||
else if (this.route.snapshot.params.slug && this.router.url.startsWith("/people"))
|
else if (this.route.snapshot.params.slug && this.router.url.startsWith("/people"))
|
||||||
slugs = [this.route.snapshot.params.slug];
|
slugs = [this.route.snapshot.params.slug];
|
||||||
|
|
||||||
this.filters.people = slugs.map(x => ({slug: x, name: x} as People));
|
this.filters.people = slugs.map(x => ({slug: x, name: x} as People));
|
||||||
for (let slug of slugs)
|
for (const slug of slugs)
|
||||||
{
|
{
|
||||||
this.peopleApi.get(slug).subscribe(x =>
|
this.peopleApi.get(slug).subscribe(x =>
|
||||||
{
|
{
|
||||||
let i: number = this.filters.people.findIndex(x => x.slug == slug);
|
const i: number = this.filters.people.findIndex(y => y.slug === slug);
|
||||||
this.filters.people[i] = x
|
this.filters.people[i] = x;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.filteredStudios = this.studioForm.valueChanges
|
this.filteredStudios = this.studioForm.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -149,38 +175,12 @@ export class ItemsGridComponent implements OnInit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldDisplayNoneStudio()
|
shouldDisplayNoneStudio(): boolean
|
||||||
{
|
{
|
||||||
return this.studioForm.value == '' || typeof this.studioForm.value != "string";
|
return this.studioForm.value === "" || typeof this.studioForm.value !== "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
getFilterCount(): number
|
||||||
* /browse -> /api/items | /api/shows
|
|
||||||
* /browse/:library -> /api/library/:slug/items | /api/library/:slug/shows
|
|
||||||
* /genre/:slug -> /api/shows
|
|
||||||
* /studio/:slug -> /api/shows
|
|
||||||
*
|
|
||||||
* /collection/:slug -> /api/collection/:slug/shows |> /api/collections/:slug/shows
|
|
||||||
* /people/:slug -> /api/people/:slug/roles |> /api/people/:slug/roles
|
|
||||||
*/
|
|
||||||
|
|
||||||
static routeMapper(route: ActivatedRouteSnapshot, endpoint: string, query: [string, string][]): string
|
|
||||||
{
|
|
||||||
let queryParams: [string, string][] = Object.entries(route.queryParams)
|
|
||||||
.filter(x => ItemsGridComponent.filters.includes(x[0]) || x[0] == "sortBy");
|
|
||||||
if (query)
|
|
||||||
queryParams.push(...query)
|
|
||||||
|
|
||||||
if (queryParams.some(x => ItemsGridComponent.showOnlyFilters.includes(x[0])))
|
|
||||||
endpoint = endpoint.replace(/items?$/, "show");
|
|
||||||
|
|
||||||
let params: string = queryParams.length > 0
|
|
||||||
? '?' + queryParams.map(x => `${x[0]}=${x[1]}`).join('&')
|
|
||||||
: "";
|
|
||||||
return `api/${endpoint}${params}`
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilterCount()
|
|
||||||
{
|
{
|
||||||
let count: number = this.filters.genres.length + this.filters.people.length;
|
let count: number = this.filters.genres.length + this.filters.people.length;
|
||||||
if (this.filters.studio != null)
|
if (this.filters.studio != null)
|
||||||
@ -188,39 +188,39 @@ export class ItemsGridComponent implements OnInit
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilter(category: string, filter: IResource, isArray: boolean = true, toggle: boolean = false)
|
addFilter(category: string, resource: IResource, isArray: boolean = true, toggle: boolean = false): void
|
||||||
{
|
{
|
||||||
if (isArray)
|
if (isArray)
|
||||||
{
|
{
|
||||||
if (this.filters[category].includes(filter) || this.filters[category].some(x => x.slug == filter.slug))
|
if (this.filters[category].includes(resource) || this.filters[category].some(x => x.slug === resource.slug))
|
||||||
this.filters[category].splice(this.filters[category].indexOf(filter), 1);
|
this.filters[category].splice(this.filters[category].indexOf(resource), 1);
|
||||||
else
|
else
|
||||||
this.filters[category].push(filter);
|
this.filters[category].push(resource);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (filter && (this.filters[category] == filter || this.filters[category]?.slug == filter.slug))
|
if (resource && (this.filters[category] === resource || this.filters[category]?.slug === resource.slug))
|
||||||
{
|
{
|
||||||
if (!toggle)
|
if (!toggle)
|
||||||
return;
|
return;
|
||||||
this.filters[category] = null;
|
this.filters[category] = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.filters[category] = filter;
|
this.filters[category] = resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
let param: string = null;
|
let param: string = null;
|
||||||
if (isArray && this.filters[category].length > 0)
|
if (isArray && this.filters[category].length > 0)
|
||||||
param = `${this.filters[category].map(x => x.slug).join(',')}`;
|
param = `${this.filters[category].map(x => x.slug).join(",")}`;
|
||||||
else if (!isArray && this.filters[category] != null)
|
else if (!isArray && this.filters[category] != null)
|
||||||
param = filter.slug;
|
param = resource.slug;
|
||||||
|
|
||||||
if (/\/browse($|\?)/.test(this.router.url)
|
if (/\/browse($|\?)/.test(this.router.url)
|
||||||
|| this.router.url.startsWith("/genre")
|
|| this.router.url.startsWith("/genre")
|
||||||
|| this.router.url.startsWith("/studio")
|
|| this.router.url.startsWith("/studio")
|
||||||
|| this.router.url.startsWith("/people"))
|
|| this.router.url.startsWith("/people"))
|
||||||
{
|
{
|
||||||
if (this.filters.genres.length == 1 && this.getFilterCount() == 1)
|
if (this.filters.genres.length === 1 && this.getFilterCount() === 1)
|
||||||
{
|
{
|
||||||
this.router.navigate(["genre", this.filters.genres[0].slug], {
|
this.router.navigate(["genre", this.filters.genres[0].slug], {
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
@ -228,7 +228,7 @@ export class ItemsGridComponent implements OnInit
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.filters.studio != null && this.getFilterCount() == 1)
|
if (this.filters.studio != null && this.getFilterCount() === 1)
|
||||||
{
|
{
|
||||||
this.router.navigate(["studio", this.filters.studio.slug], {
|
this.router.navigate(["studio", this.filters.studio.slug], {
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
@ -236,7 +236,7 @@ export class ItemsGridComponent implements OnInit
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.filters.people.length == 1 && this.getFilterCount() == 1)
|
if (this.filters.people.length === 1 && this.getFilterCount() === 1)
|
||||||
{
|
{
|
||||||
this.router.navigate(["people", this.filters.people[0].slug], {
|
this.router.navigate(["people", this.filters.people[0].slug], {
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
@ -244,14 +244,14 @@ export class ItemsGridComponent implements OnInit
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.getFilterCount() == 0 || this.router.url != "/browse")
|
if (this.getFilterCount() === 0 || this.router.url !== "/browse")
|
||||||
{
|
{
|
||||||
let params = {[category]: param}
|
const params: {[key: string]: string} = {[category]: param};
|
||||||
if (this.router.url.startsWith("/studio") && category != "studio")
|
if (this.router.url.startsWith("/studio") && category !== "studio")
|
||||||
params.studio = this.route.snapshot.params.slug;
|
params.studio = this.route.snapshot.params.slug;
|
||||||
if (this.router.url.startsWith("/genre") && category != "genres")
|
if (this.router.url.startsWith("/genre") && category !== "genres")
|
||||||
params.genres = `${this.route.snapshot.params.slug}`;
|
params.genres = `${this.route.snapshot.params.slug}`;
|
||||||
if (this.router.url.startsWith("/people") && category != "people")
|
if (this.router.url.startsWith("/people") && category !== "people")
|
||||||
params.people = `${this.route.snapshot.params.slug}`;
|
params.people = `${this.route.snapshot.params.slug}`;
|
||||||
|
|
||||||
this.router.navigate(["/browse"], {
|
this.router.navigate(["/browse"], {
|
||||||
@ -270,32 +270,32 @@ export class ItemsGridComponent implements OnInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nameGetter(obj: Studio)
|
nameGetter(obj: Studio): string
|
||||||
{
|
{
|
||||||
return obj?.name ?? "None";
|
return obj?.name ?? "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumb(slug: string)
|
getThumb(slug: string): SafeStyle
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
getDate(item: LibraryItem | Show | ShowRole | Collection)
|
getDate(item: LibraryItem | Show | ShowRole | Collection): string
|
||||||
{
|
{
|
||||||
return ItemsUtils.getDate(item);
|
return ItemsUtils.getDate(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLink(item: LibraryItem | Show | ShowRole | Collection)
|
getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
||||||
{
|
{
|
||||||
return ItemsUtils.getLink(item);
|
return ItemsUtils.getLink(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(type: string, order: boolean)
|
sort(type: string, order: boolean): void
|
||||||
{
|
{
|
||||||
this.sortType = type;
|
this.sortType = type;
|
||||||
this.sortUp = order;
|
this.sortUp = order;
|
||||||
|
|
||||||
let param: string = `${this.sortType.replace(/\s/g, "")}:${this.sortUp ? "asc" : "desc"}`;
|
const param: string = `${this.sortType.replace(/\s/g, "")}:${this.sortUp ? "asc" : "desc"}`;
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
relativeTo: this.route,
|
relativeTo: this.route,
|
||||||
queryParams: { sortBy: param },
|
queryParams: { sortBy: param },
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import {Component, Input} from "@angular/core";
|
import { Component, Input } from "@angular/core";
|
||||||
import {Collection} from "../../models/resources/collection";
|
import { Collection } from "../../models/resources/collection";
|
||||||
import {DomSanitizer} from "@angular/platform-browser";
|
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||||
import {HorizontalScroller} from "../../misc/horizontal-scroller";
|
import { HorizontalScroller } from "../../misc/horizontal-scroller";
|
||||||
import {Page} from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
import {Show, ShowRole} from "../../models/resources/show";
|
import { Show, ShowRole } from "../../models/resources/show";
|
||||||
import {LibraryItem} from "../../models/resources/library-item";
|
import { LibraryItem } from "../../models/resources/library-item";
|
||||||
import {ItemsUtils} from "../../misc/items-utils";
|
import { ItemsUtils } from "../../misc/items-utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-items-list',
|
selector: "app-items-list",
|
||||||
templateUrl: './items-list.component.html',
|
templateUrl: "./items-list.component.html",
|
||||||
styleUrls: ['./items-list.component.scss']
|
styleUrls: ["./items-list.component.scss"]
|
||||||
})
|
})
|
||||||
export class ItemsListComponent extends HorizontalScroller
|
export class ItemsListComponent extends HorizontalScroller
|
||||||
{
|
{
|
||||||
@ -22,17 +22,17 @@ export class ItemsListComponent extends HorizontalScroller
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumb(slug: string)
|
getThumb(slug: string): SafeUrl
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(/poster/" + slug + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
getDate(item: LibraryItem | Show | ShowRole | Collection)
|
getDate(item: LibraryItem | Show | ShowRole | Collection): string
|
||||||
{
|
{
|
||||||
return ItemsUtils.getDate(item);
|
return ItemsUtils.getDate(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLink(item: LibraryItem | Show | ShowRole | Collection)
|
getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
||||||
{
|
{
|
||||||
return ItemsUtils.getLink(item);
|
return ItemsUtils.getLink(item);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
|
import { Component, Input } from "@angular/core";
|
||||||
import { MatButton } from "@angular/material/button";
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
import { People } from "../../models/resources/people";
|
import { People } from "../../models/resources/people";
|
||||||
import {HorizontalScroller} from "../../misc/horizontal-scroller";
|
import { HorizontalScroller } from "../../misc/horizontal-scroller";
|
||||||
import {Page} from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-people-list',
|
selector: "app-people-list",
|
||||||
templateUrl: './people-list.component.html',
|
templateUrl: "./people-list.component.html",
|
||||||
styleUrls: ['./people-list.component.scss']
|
styleUrls: ["./people-list.component.scss"]
|
||||||
})
|
})
|
||||||
export class PeopleListComponent extends HorizontalScroller
|
export class PeopleListComponent extends HorizontalScroller
|
||||||
{
|
{
|
||||||
@ -20,7 +19,7 @@ export class PeopleListComponent extends HorizontalScroller
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPeopleIcon(slug: string)
|
getPeopleIcon(slug: string): SafeStyle
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(/peopleimg/" + slug + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(/peopleimg/" + slug + ")");
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||||
import {DomSanitizer} from "@angular/platform-browser";
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||||
import {Show} from "../../models/resources/show";
|
import { Show } from "../../models/resources/show";
|
||||||
import {Page} from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shows-grid',
|
selector: "app-shows-grid",
|
||||||
templateUrl: './show-grid.component.html',
|
templateUrl: "./show-grid.component.html",
|
||||||
styleUrls: ['./show-grid.component.scss']
|
styleUrls: ["./show-grid.component.scss"]
|
||||||
})
|
})
|
||||||
export class ShowGridComponent
|
export class ShowGridComponent
|
||||||
{
|
{
|
||||||
@Input() shows: Page<Show>
|
@Input() shows: Page<Show>;
|
||||||
@Input() externalShows: boolean = false;
|
@Input() externalShows: boolean = false;
|
||||||
@Output() clickCallback: EventEmitter<Show> = new EventEmitter();
|
@Output() clickCallback: EventEmitter<Show> = new EventEmitter();
|
||||||
|
|
||||||
constructor(private sanitizer: DomSanitizer) { }
|
constructor(private sanitizer: DomSanitizer) { }
|
||||||
|
|
||||||
getThumb(show: Show)
|
getThumb(show: Show): SafeStyle
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle(`url(${show.poster})`);
|
return this.sanitizer.bypassSecurityTrustStyle(`url(${show.poster})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLink(show: Show)
|
getLink(show: Show): string
|
||||||
{
|
{
|
||||||
if (this.externalShows)
|
if (this.externalShows)
|
||||||
return null;
|
return null;
|
||||||
|
@ -4,30 +4,30 @@ export class CustomRouteReuseStrategy extends RouteReuseStrategy
|
|||||||
{
|
{
|
||||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean
|
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean
|
||||||
{
|
{
|
||||||
if (curr.routeConfig?.path == "browse"
|
if (curr.routeConfig?.path === "browse"
|
||||||
|| curr.routeConfig?.path == "genre/:slug"
|
|| curr.routeConfig?.path === "genre/:slug"
|
||||||
|| curr.routeConfig?.path == "studio/:slug")
|
|| curr.routeConfig?.path === "studio/:slug")
|
||||||
{
|
{
|
||||||
return future.routeConfig.path == "browse"
|
return future.routeConfig.path === "browse"
|
||||||
|| future.routeConfig.path == "genre/:slug"
|
|| future.routeConfig.path === "genre/:slug"
|
||||||
|| future.routeConfig.path == "studio/:slug";
|
|| future.routeConfig.path === "studio/:slug";
|
||||||
}
|
}
|
||||||
return future.routeConfig === curr.routeConfig;
|
return future.routeConfig === curr.routeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldAttach(route: ActivatedRouteSnapshot): boolean
|
shouldAttach(): boolean
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldDetach(route: ActivatedRouteSnapshot): boolean
|
shouldDetach(): boolean
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {}
|
store(): void {}
|
||||||
|
|
||||||
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null
|
retrieve(): DetachedRouteHandle | null
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { Component, ElementRef, ViewChild } from "@angular/core";
|
import { ElementRef, ViewChild } from "@angular/core";
|
||||||
import { MatButton } from "@angular/material/button";
|
import { MatButton } from "@angular/material/button";
|
||||||
|
|
||||||
// noinspection AngularMissingOrInvalidDeclarationInModule
|
|
||||||
@Component({
|
|
||||||
template: ""
|
|
||||||
})
|
|
||||||
export class HorizontalScroller
|
export class HorizontalScroller
|
||||||
{
|
{
|
||||||
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
|
@ViewChild("scrollView", { static: true }) private scrollView: ElementRef;
|
||||||
@ -12,35 +9,37 @@ export class HorizontalScroller
|
|||||||
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
|
@ViewChild("rightBtn", { static: false }) private rightBtn: MatButton;
|
||||||
@ViewChild("itemsDom", { static: false }) private itemsDom: ElementRef;
|
@ViewChild("itemsDom", { static: false }) private itemsDom: ElementRef;
|
||||||
|
|
||||||
scrollLeft()
|
scrollLeft(): void
|
||||||
{
|
{
|
||||||
let scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
|
const scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
|
||||||
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
|
this.scrollView.nativeElement.scrollBy({ top: 0, left: -scroll, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollRight()
|
scrollRight(): void
|
||||||
{
|
{
|
||||||
let scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
|
const scroll: number = this.roundScroll(this.scrollView.nativeElement.offsetWidth * 0.80);
|
||||||
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
|
this.scrollView.nativeElement.scrollBy({ top: 0, left: scroll, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
roundScroll(offset: number): number
|
roundScroll(offset: number): number
|
||||||
{
|
{
|
||||||
let itemSize: number = this.itemsDom.nativeElement.scrollWidth;
|
const itemSize: number = this.itemsDom.nativeElement.scrollWidth;
|
||||||
|
|
||||||
offset = Math.round(offset / itemSize) * itemSize;
|
offset = Math.round(offset / itemSize) * itemSize;
|
||||||
if (offset == 0)
|
if (offset === 0)
|
||||||
offset = itemSize;
|
offset = itemSize;
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
onScroll()
|
onScroll(): void
|
||||||
{
|
{
|
||||||
if (this.scrollView.nativeElement.scrollLeft <= 0)
|
const scroll: any = this.scrollView.nativeElement;
|
||||||
|
|
||||||
|
if (scroll.scrollLeft <= 0)
|
||||||
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
|
this.leftBtn._elementRef.nativeElement.classList.add("d-none");
|
||||||
else
|
else
|
||||||
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
|
this.leftBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||||
if (this.scrollView.nativeElement.scrollLeft >= this.scrollView.nativeElement.scrollWidth - this.scrollView.nativeElement.clientWidth)
|
if (scroll.scrollLeft >= scroll.scrollWidth - scroll.clientWidth)
|
||||||
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
|
this.rightBtn._elementRef.nativeElement.classList.add("d-none");
|
||||||
else
|
else
|
||||||
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
|
this.rightBtn._elementRef.nativeElement.classList.remove("d-none");
|
||||||
|
@ -7,7 +7,7 @@ export class ItemsUtils
|
|||||||
{
|
{
|
||||||
static getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
static getLink(item: LibraryItem | Show | ShowRole | Collection): string
|
||||||
{
|
{
|
||||||
if ("type" in item && item.type == ItemType.Collection)
|
if ("type" in item && item.type === ItemType.Collection)
|
||||||
return "/collection/" + item.slug;
|
return "/collection/" + item.slug;
|
||||||
else
|
else
|
||||||
return "/show/" + item.slug;
|
return "/show/" + item.slug;
|
||||||
@ -21,13 +21,13 @@ export class ItemsUtils
|
|||||||
return `as ${item.role} (${item.type})`;
|
return `as ${item.role} (${item.type})`;
|
||||||
return `as ${item.role}`;
|
return `as ${item.role}`;
|
||||||
}
|
}
|
||||||
if ("type" in item && item.type && typeof item.type == "string")
|
if ("type" in item && item.type && typeof item.type === "string")
|
||||||
return item.type;
|
return item.type;
|
||||||
|
|
||||||
if (!("startYear" in item))
|
if (!("startYear" in item))
|
||||||
return "";
|
return "";
|
||||||
if (item.endYear && item.startYear != item.endYear)
|
if (item.endYear && item.startYear !== item.endYear)
|
||||||
return `${item.startYear} - ${item.endYear}`
|
return `${item.startYear} - ${item.endYear}`;
|
||||||
return item.startYear?.toString();
|
return item.startYear?.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Directive, Output, EventEmitter, HostListener, HostBinding, ElementRef } from "@angular/core";
|
import { Directive, Output, EventEmitter, HostListener, HostBinding, ElementRef } from "@angular/core";
|
||||||
import MouseDownEvent = JQuery.MouseDownEvent;
|
import MouseDownEvent = JQuery.MouseDownEvent;
|
||||||
import TouchStartEvent = JQuery.TouchStartEvent;
|
import TouchStartEvent = JQuery.TouchStartEvent;
|
||||||
|
import ContextMenuEvent = JQuery.ContextMenuEvent;
|
||||||
|
import ClickEvent = JQuery.ClickEvent;
|
||||||
|
|
||||||
function cancelClick(event): void
|
function cancelClick(event: ClickEvent): void
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -19,7 +21,10 @@ export class LongPressDirective
|
|||||||
|
|
||||||
constructor(private ref: ElementRef) {}
|
constructor(private ref: ElementRef) {}
|
||||||
|
|
||||||
@HostBinding('class.longpress')
|
@HostBinding("style.-webkit-touch-callout")
|
||||||
|
defaultLongTouchEvent: string = "none";
|
||||||
|
|
||||||
|
@HostBinding("class.longpress")
|
||||||
get longPress(): boolean
|
get longPress(): boolean
|
||||||
{
|
{
|
||||||
return this._timer !== null;
|
return this._timer !== null;
|
||||||
@ -29,10 +34,10 @@ export class LongPressDirective
|
|||||||
@HostListener("mousedown", ["$event"])
|
@HostListener("mousedown", ["$event"])
|
||||||
start(event: MouseDownEvent | TouchStartEvent): void
|
start(event: MouseDownEvent | TouchStartEvent): void
|
||||||
{
|
{
|
||||||
const startBox = event.target.getBoundingClientRect();
|
const startBox: DOMRect = event.target.getBoundingClientRect();
|
||||||
this._timer = setTimeout(() =>
|
this._timer = setTimeout(() =>
|
||||||
{
|
{
|
||||||
const endBox = event.target.getBoundingClientRect();
|
const endBox: DOMRect = event.target.getBoundingClientRect();
|
||||||
if (startBox.top !== endBox.top || startBox.left !== endBox.left)
|
if (startBox.top !== endBox.top || startBox.left !== endBox.left)
|
||||||
return;
|
return;
|
||||||
this.longPressed.emit();
|
this.longPressed.emit();
|
||||||
@ -63,11 +68,8 @@ export class LongPressDirective
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HostListener("contextmenu", ["$event"])
|
@HostListener("contextmenu", ["$event"])
|
||||||
context(event): void
|
context(event: ContextMenuEvent): void
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostBinding("style.-webkit-touch-callout")
|
|
||||||
defaultLongTouchEvent: string = "none";
|
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import {AbstractControl, NG_VALIDATORS, Validator} from "@angular/forms";
|
import { AbstractControl, NG_VALIDATORS, Validator } from "@angular/forms";
|
||||||
import {Directive} from "@angular/core";
|
import { Directive } from "@angular/core";
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: "[passwordValidator]",
|
selector: "[appPasswordValidator]",
|
||||||
providers: [{provide: NG_VALIDATORS, useExisting: PasswordValidator, multi: true}]
|
providers: [{provide: NG_VALIDATORS, useExisting: PasswordValidator, multi: true}]
|
||||||
})
|
})
|
||||||
export class PasswordValidator implements Validator
|
export class PasswordValidator implements Validator
|
||||||
{
|
{
|
||||||
validate(control: AbstractControl): {[key: string]: any} | null
|
validate(control: AbstractControl): {[key: string]: any} | null
|
||||||
{
|
{
|
||||||
if (!control.value)
|
if (!control.value)
|
||||||
return null;
|
return null;
|
||||||
if (!/[a-z]/.test(control.value))
|
if (!/[a-z]/.test(control.value))
|
||||||
return {"passwordError": {error: "The password must contains a lowercase letter."}};
|
return {passwordError: {error: "The password must contains a lowercase letter."}};
|
||||||
if (!/[A-Z]/.test(control.value))
|
if (!/[A-Z]/.test(control.value))
|
||||||
return {"passwordError": {error: "The password must contains an uppercase letter."}};
|
return {passwordError: {error: "The password must contains an uppercase letter."}};
|
||||||
if (!/[0-9]/.test(control.value))
|
if (!/[0-9]/.test(control.value))
|
||||||
return {"passwordError": {error: "The password must contains a digit."}};
|
return {passwordError: {error: "The password must contains a digit."}};
|
||||||
if (!/\W/.test(control.value))
|
if (!/\W/.test(control.value))
|
||||||
return {"passwordError": {error: "The password must contains a non-alphanumeric character."}};
|
return {passwordError: {error: "The password must contains a non-alphanumeric character."}};
|
||||||
if (control.value.toString().length < 6)
|
if (control.value.toString().length < 6)
|
||||||
return {"passwordError": {error: "Password must be at least 6 character long."}};
|
return {passwordError: {error: "Password must be at least 6 character long."}};
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Provider} from "./provider"
|
import { Provider } from "./provider";
|
||||||
|
|
||||||
export interface ExternalID
|
export interface ExternalID
|
||||||
{
|
{
|
||||||
provider: Provider;
|
provider: Provider;
|
||||||
dataID: string;
|
dataID: string;
|
||||||
link: string;
|
link: string;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {HttpClient} from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
|
|
||||||
export class Page<T>
|
export class Page<T>
|
||||||
{
|
{
|
||||||
@ -9,12 +9,12 @@ export class Page<T>
|
|||||||
items: T[];
|
items: T[];
|
||||||
private _isLoading: boolean = false;
|
private _isLoading: boolean = false;
|
||||||
|
|
||||||
constructor(init?:Partial<Page<T>>)
|
constructor(init?: Partial<Page<T>>)
|
||||||
{
|
{
|
||||||
Object.assign(this, init);
|
Object.assign(this, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNext(client: HttpClient)
|
loadNext(client: HttpClient): void
|
||||||
{
|
{
|
||||||
if (this.next == null || this._isLoading)
|
if (this.next == null || this._isLoading)
|
||||||
return;
|
return;
|
||||||
@ -30,8 +30,8 @@ export class Page<T>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
changeType(type: string)
|
changeType(type: string): string
|
||||||
{
|
{
|
||||||
return this.first.replace(/\/\w*($|\?)/, `/${type}$1`)
|
return this.first.replace(/\/\w*($|\?)/, `/${type}$1`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export interface Provider
|
export interface Provider
|
||||||
{
|
{
|
||||||
name: string;
|
name: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Show } from "./show";
|
import { Show } from "./show";
|
||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export interface Collection extends IResource
|
export interface Collection extends IResource
|
||||||
{
|
{
|
||||||
name: string;
|
name: string;
|
||||||
poster: string;
|
poster: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
startYear: number,
|
startYear: number;
|
||||||
endYear: number,
|
endYear: number;
|
||||||
shows: Show[];
|
shows: Show[];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ExternalID} from "../external-id";
|
import { ExternalID } from "../external-id";
|
||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export interface Episode extends IResource
|
export interface Episode extends IResource
|
||||||
{
|
{
|
||||||
@ -8,7 +8,7 @@ export interface Episode extends IResource
|
|||||||
title: string;
|
title: string;
|
||||||
thumb: string;
|
thumb: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
releaseDate;
|
releaseDate: string;
|
||||||
runtime: number;
|
runtime: number;
|
||||||
showTitle: string;
|
showTitle: string;
|
||||||
externalIDs: ExternalID[];
|
externalIDs: ExternalID[];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export enum ItemType
|
export enum ItemType
|
||||||
{
|
{
|
||||||
@ -9,12 +9,12 @@ export enum ItemType
|
|||||||
|
|
||||||
export interface LibraryItem extends IResource
|
export interface LibraryItem extends IResource
|
||||||
{
|
{
|
||||||
title: string
|
title: string;
|
||||||
overview: string
|
overview: string;
|
||||||
status: string
|
status: string;
|
||||||
trailerUrl: string
|
trailerUrl: string;
|
||||||
startYear: number
|
startYear: number;
|
||||||
endYear: number
|
endYear: number;
|
||||||
poster: string
|
poster: string;
|
||||||
type: ItemType
|
type: ItemType;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export interface Library extends IResource
|
export interface Library extends IResource
|
||||||
{
|
{
|
||||||
id: number;
|
id: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import {ExternalID} from "../external-id";
|
import { ExternalID } from "../external-id";
|
||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
import {Show} from "./show";
|
import { Show } from "./show";
|
||||||
|
|
||||||
export interface People extends IResource
|
export interface People extends IResource
|
||||||
{
|
{
|
||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
type: string;
|
type: string;
|
||||||
poster: string;
|
poster: string;
|
||||||
|
|
||||||
shows: Show;
|
shows: Show;
|
||||||
externalIDs: ExternalID[];
|
externalIDs: ExternalID[];
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@ export interface IResource
|
|||||||
{
|
{
|
||||||
id: number;
|
id: number;
|
||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Episode } from "./episode";
|
import { Episode } from "./episode";
|
||||||
import {ExternalID} from "../external-id";
|
import { ExternalID } from "../external-id";
|
||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export interface Season extends IResource
|
export interface Season extends IResource
|
||||||
{
|
{
|
||||||
@ -8,5 +8,5 @@ export interface Season extends IResource
|
|||||||
title: string;
|
title: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
episodes: Episode[];
|
episodes: Episode[];
|
||||||
externalIDs: ExternalID[]
|
externalIDs: ExternalID[];
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {Season} from "./season";
|
import { Season } from "./season";
|
||||||
import {Genre} from "./genre";
|
import { Genre } from "./genre";
|
||||||
import {People} from "./people";
|
import { People } from "./people";
|
||||||
import {Studio} from "./studio";
|
import { Studio } from "./studio";
|
||||||
import {ExternalID} from "../external-id";
|
import { ExternalID } from "../external-id";
|
||||||
import {IResource} from "./resource";
|
import { IResource } from "./resource";
|
||||||
|
|
||||||
export interface Show extends IResource
|
export interface Show extends IResource
|
||||||
{
|
{
|
||||||
@ -18,7 +18,7 @@ export interface Show extends IResource
|
|||||||
trailerUrl: string;
|
trailerUrl: string;
|
||||||
isMovie: boolean;
|
isMovie: boolean;
|
||||||
startYear: number;
|
startYear: number;
|
||||||
endYear : number;
|
endYear: number;
|
||||||
poster: string;
|
poster: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
backdrop: string;
|
backdrop: string;
|
||||||
@ -38,8 +38,8 @@ export interface ShowRole extends IResource
|
|||||||
trailerUrl: string;
|
trailerUrl: string;
|
||||||
isMovie: boolean;
|
isMovie: boolean;
|
||||||
startYear: number;
|
startYear: number;
|
||||||
endYear : number;
|
endYear: number;
|
||||||
poster: string;
|
poster: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
backdrop: string;
|
backdrop: string;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { Episode } from "./resources/episode";
|
|||||||
import { People } from "./resources/people";
|
import { People } from "./resources/people";
|
||||||
import { Studio } from "./resources/studio";
|
import { Studio } from "./resources/studio";
|
||||||
import { Genre } from "./resources/genre";
|
import { Genre } from "./resources/genre";
|
||||||
import {Collection} from "./resources/collection";
|
import { Collection } from "./resources/collection";
|
||||||
|
|
||||||
export interface SearchResult
|
export interface SearchResult
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ export interface WatchItem
|
|||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
releaseDate;
|
releaseDate: string;
|
||||||
isMovie: boolean;
|
isMovie: boolean;
|
||||||
|
|
||||||
previousEpisode: Episode;
|
previousEpisode: Episode;
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import {Component} from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
import {Collection} from "../../models/resources/collection";
|
import { Collection } from "../../models/resources/collection";
|
||||||
import {ActivatedRoute} from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import {DomSanitizer} from "@angular/platform-browser";
|
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
|
||||||
import {Show, ShowRole} from "../../models/resources/show";
|
import { Show, ShowRole } from "../../models/resources/show";
|
||||||
import {Page} from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
import {People} from "../../models/resources/people";
|
import { People } from "../../models/resources/people";
|
||||||
import {LibraryItem} from "../../models/resources/library-item";
|
import { LibraryItem } from "../../models/resources/library-item";
|
||||||
import {ItemsUtils} from "../../misc/items-utils";
|
import { ItemsUtils } from "../../misc/items-utils";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-collection',
|
selector: "app-collection",
|
||||||
templateUrl: './collection.component.html',
|
templateUrl: "./collection.component.html",
|
||||||
styleUrls: ['./collection.component.scss']
|
styleUrls: ["./collection.component.scss"]
|
||||||
})
|
})
|
||||||
export class CollectionComponent
|
export class CollectionComponent
|
||||||
{
|
{
|
||||||
@ -27,12 +27,12 @@ export class CollectionComponent
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumb()
|
getThumb(): SafeStyle
|
||||||
{
|
{
|
||||||
return this.sanitizer.bypassSecurityTrustStyle("url(" + this.collection.poster + ")");
|
return this.sanitizer.bypassSecurityTrustStyle("url(" + this.collection.poster + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
getDate(item: LibraryItem | Show | ShowRole | Collection | People)
|
getDate(item: LibraryItem | Show | ShowRole | Collection | People): string
|
||||||
{
|
{
|
||||||
return ItemsUtils.getDate(item);
|
return ItemsUtils.getDate(item);
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,19 @@ import { Show } from "../../models/resources/show";
|
|||||||
import { Genre } from "../../models/resources/genre";
|
import { Genre } from "../../models/resources/genre";
|
||||||
import { MatChipInputEvent } from "@angular/material/chips";
|
import { MatChipInputEvent } from "@angular/material/chips";
|
||||||
import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete";
|
import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete";
|
||||||
import { Observable, of} from "rxjs";
|
import { Observable, of } from "rxjs";
|
||||||
import { catchError, filter, map, mergeAll, tap } from "rxjs/operators";
|
import { catchError, filter, map, mergeAll, tap } from "rxjs/operators";
|
||||||
import { Studio } from "../../models/resources/studio";
|
import { Studio } from "../../models/resources/studio";
|
||||||
import { Provider } from "../../models/provider";
|
import { Provider } from "../../models/provider";
|
||||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||||
import { ShowGridComponent } from "../../components/show-grid/show-grid.component";
|
import { ShowGridComponent } from "../../components/show-grid/show-grid.component";
|
||||||
import { GenreService, ShowService, StudioService } from "../../services/api.service";
|
import { GenreService, ShowService, StudioService } from "../../services/api.service";
|
||||||
|
import { ExternalID } from "../../models/external-id";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-metadata-edit',
|
selector: "app-metadata-edit",
|
||||||
templateUrl: './metadata-edit.component.html',
|
templateUrl: "./metadata-edit.component.html",
|
||||||
styleUrls: ['./metadata-edit.component.scss']
|
styleUrls: ["./metadata-edit.component.scss"]
|
||||||
})
|
})
|
||||||
export class MetadataEditComponent implements OnInit
|
export class MetadataEditComponent implements OnInit
|
||||||
{
|
{
|
||||||
@ -27,14 +28,14 @@ export class MetadataEditComponent implements OnInit
|
|||||||
|
|
||||||
genreForm: FormControl = new FormControl();
|
genreForm: FormControl = new FormControl();
|
||||||
filteredGenres: Observable<Genre[]>;
|
filteredGenres: Observable<Genre[]>;
|
||||||
|
|
||||||
@ViewChild("identifyGrid") private identifyGrid: ShowGridComponent;
|
@ViewChild("identifyGrid") private identifyGrid: ShowGridComponent;
|
||||||
private identifying: Observable<Show[]>;
|
private _identifying: Observable<Show[]>;
|
||||||
private identifiedShows: [string, Show[]];
|
private _identifiedShows: [string, Show[]];
|
||||||
public providers: Provider[] = [];
|
public providers: Provider[] = [];
|
||||||
|
|
||||||
public metadataChanged: boolean = false;
|
public metadataChanged: boolean = false;
|
||||||
|
|
||||||
constructor(public dialogRef: MatDialogRef<MetadataEditComponent>,
|
constructor(public dialogRef: MatDialogRef<MetadataEditComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public show: Show,
|
@Inject(MAT_DIALOG_DATA) public show: Show,
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
@ -51,7 +52,7 @@ export class MetadataEditComponent implements OnInit
|
|||||||
this.reIdentify(this.show.title);
|
this.reIdentify(this.show.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.filteredGenres = this.genreForm.valueChanges
|
this.filteredGenres = this.genreForm.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
@ -82,16 +83,17 @@ export class MetadataEditComponent implements OnInit
|
|||||||
|
|
||||||
apply(): void
|
apply(): void
|
||||||
{
|
{
|
||||||
if (this.metadataChanged)
|
if (this.metadataChanged)
|
||||||
{
|
{
|
||||||
this.http.post("/api/show/re-identify/" + this.show.slug, this.show.externalIDs).subscribe(
|
this.http.post("/api/show/re-identify/" + this.show.slug, this.show.externalIDs).subscribe(
|
||||||
(show: Show) =>
|
() => {},
|
||||||
|
() =>
|
||||||
{
|
{
|
||||||
return;
|
this.snackBar.open("An unknown error occurred.", null, {
|
||||||
},
|
horizontalPosition: "left",
|
||||||
() =>
|
panelClass: ["snackError"],
|
||||||
{
|
duration: 2500
|
||||||
this.snackBar.open("An unknown error occurred.", null, { horizontalPosition: "left", panelClass: ['snackError'], duration: 2500 });
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.dialogRef.close(this.show);
|
this.dialogRef.close(this.show);
|
||||||
@ -104,37 +106,37 @@ export class MetadataEditComponent implements OnInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addAlias(event: MatChipInputEvent)
|
addAlias(event: MatChipInputEvent): void
|
||||||
{
|
{
|
||||||
const input = event.input;
|
const input: HTMLInputElement = event.input;
|
||||||
const value = event.value;
|
const value: string = event.value;
|
||||||
|
|
||||||
this.show.aliases.push(value);
|
this.show.aliases.push(value);
|
||||||
if (input)
|
if (input)
|
||||||
input.value = "";
|
input.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAlias(alias: string)
|
removeAlias(alias: string): void
|
||||||
{
|
{
|
||||||
const i = this.show.aliases.indexOf(alias);
|
const i: number = this.show.aliases.indexOf(alias);
|
||||||
this.show.aliases.splice(i, 1);
|
this.show.aliases.splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
addGenre(event: MatChipInputEvent)
|
addGenre(event: MatChipInputEvent): void
|
||||||
{
|
{
|
||||||
const input = event.input;
|
const input: HTMLInputElement = event.input;
|
||||||
const value = event.value;
|
const value: string = event.value;
|
||||||
let genre: Genre = {id: 0, slug: null, name: value};
|
const genre: Genre = {id: 0, slug: null, name: value};
|
||||||
|
|
||||||
this.show.genres.push(genre);
|
this.show.genres.push(genre);
|
||||||
if (input)
|
if (input)
|
||||||
input.value = "";
|
input.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
removeGenre(genre: Genre): void
|
removeGenre(genre: Genre): void
|
||||||
{
|
{
|
||||||
const i = this.show.genres.indexOf(genre);
|
const i: number = this.show.genres.indexOf(genre);
|
||||||
this.show.genres.splice(i, 1);
|
this.show.genres.splice(i, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,41 +147,42 @@ export class MetadataEditComponent implements OnInit
|
|||||||
|
|
||||||
identityShow(name: string): Observable<Show[]>
|
identityShow(name: string): Observable<Show[]>
|
||||||
{
|
{
|
||||||
if (this.identifiedShows && this.identifiedShows[0] === name)
|
if (this._identifiedShows && this._identifiedShows[0] === name)
|
||||||
return of(this.identifiedShows[1]);
|
return of(this._identifiedShows[1]);
|
||||||
this.identifying = this.http.get<Show[]>("/api/show/identify/" + name + "?isMovie=" + this.show.isMovie).pipe(
|
this._identifying = this.http.get<Show[]>("/api/show/identify/" + name + "?isMovie=" + this.show.isMovie).pipe(
|
||||||
tap(result => this.identifiedShows = [name, result])
|
tap(result => this._identifiedShows = [name, result])
|
||||||
);
|
);
|
||||||
return this.identifying;
|
return this._identifying;
|
||||||
}
|
}
|
||||||
|
|
||||||
reIdentify(search: string)
|
reIdentify(search: string): void
|
||||||
{
|
{
|
||||||
|
// TODO implement this
|
||||||
// this.identityShow(search).subscribe(x => this.identifyGrid.shows = x);
|
// this.identityShow(search).subscribe(x => this.identifyGrid.shows = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetadataID(provider: Provider)
|
getMetadataID(provider: Provider): ExternalID
|
||||||
{
|
{
|
||||||
return this.show.externalIDs.find(x => x.provider.name == provider.name);
|
return this.show.externalIDs.find(x => x.provider.name === provider.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMetadataID(provider: Provider, id: string, link: string = undefined)
|
setMetadataID(provider: Provider, id: string, link: string = null): void
|
||||||
{
|
{
|
||||||
let i = this.show.externalIDs.findIndex(x => x.provider.name == provider.name);
|
const i: number = this.show.externalIDs.findIndex(x => x.provider.name === provider.name);
|
||||||
|
|
||||||
this.metadataChanged = true;
|
this.metadataChanged = true;
|
||||||
if (i != -1)
|
if (i !== -1)
|
||||||
{
|
{
|
||||||
this.show.externalIDs[i].dataID = id;
|
this.show.externalIDs[i].dataID = id;
|
||||||
this.show.externalIDs[i].link = link;
|
this.show.externalIDs[i].link = link;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.show.externalIDs.push({provider: provider, dataID: id, link: link});
|
this.show.externalIDs.push({provider, dataID: id, link});
|
||||||
}
|
}
|
||||||
|
|
||||||
identifyID(show: Show)
|
identifyID(show: Show): void
|
||||||
{
|
{
|
||||||
for (let id of show.externalIDs)
|
for (const id of show.externalIDs)
|
||||||
this.setMetadataID(id.provider, id.dataID, id.link);
|
this.setMetadataID(id.provider, id.dataID, id.link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-not-found',
|
selector: "app-not-found",
|
||||||
templateUrl: './not-found.component.html',
|
templateUrl: "./not-found.component.html",
|
||||||
styleUrls: ['./not-found.component.scss']
|
styleUrls: ["./not-found.component.scss"]
|
||||||
})
|
})
|
||||||
export class NotFoundComponent implements OnInit {
|
export class NotFoundComponent
|
||||||
|
{
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { detect } from "detect-browser";
|
import { BotInfo, BrowserInfo, detect, NodeInfo, ReactNativeInfo, SearchBotDeviceInfo } from "detect-browser";
|
||||||
import { Track, WatchItem } from "../../models/watch-item";
|
import { Track, WatchItem } from "../../models/watch-item";
|
||||||
|
|
||||||
export enum method
|
export enum method
|
||||||
@ -31,8 +31,8 @@ export class SupportList
|
|||||||
|
|
||||||
export function getWhatIsSupported(player: HTMLVideoElement, item: WatchItem): SupportList
|
export function getWhatIsSupported(player: HTMLVideoElement, item: WatchItem): SupportList
|
||||||
{
|
{
|
||||||
let supportList: SupportList = new SupportList();
|
const supportList: SupportList = new SupportList();
|
||||||
let browser = detect();
|
const browser: BrowserInfo | SearchBotDeviceInfo | BotInfo | NodeInfo | ReactNativeInfo = detect();
|
||||||
|
|
||||||
if (!browser)
|
if (!browser)
|
||||||
{
|
{
|
||||||
@ -54,33 +54,33 @@ function containerIsSupported(player: HTMLVideoElement, container: string, brows
|
|||||||
switch (container)
|
switch (container)
|
||||||
{
|
{
|
||||||
case "asf":
|
case "asf":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "avi":
|
case "avi":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "mpg":
|
case "mpg":
|
||||||
case "mpeg":
|
case "mpeg":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "flv":
|
case "flv":
|
||||||
return browser == "tizen" || browser == "orsay";
|
return browser === "tizen" || browser === "orsay";
|
||||||
case "3gp":
|
case "3gp":
|
||||||
case "mts":
|
case "mts":
|
||||||
case "trp":
|
case "trp":
|
||||||
case "vob":
|
case "vob":
|
||||||
case "vro":
|
case "vro":
|
||||||
return browser == "tizen" || browser == "orsay";
|
return browser === "tizen" || browser === "orsay";
|
||||||
case "mov":
|
case "mov":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge" || browser == "chrome";
|
return browser === "tizen" || browser === "orsay" || browser === "edge" || browser === "chrome";
|
||||||
case "m2ts":
|
case "m2ts":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "wmv":
|
case "wmv":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "ts":
|
case "ts":
|
||||||
return browser == "tizen" || browser == "orsay" || browser == "edge";
|
return browser === "tizen" || browser === "orsay" || browser === "edge";
|
||||||
case "mp4":
|
case "mp4":
|
||||||
case "m4v":
|
case "m4v":
|
||||||
return true;
|
return true;
|
||||||
case "mkv":
|
case "mkv":
|
||||||
if (browser == "tizen" || browser == "orsay" || browser == "chrome" || browser == "edge")
|
if (browser === "tizen" || browser === "orsay" || browser === "chrome" || browser === "edge")
|
||||||
return true;
|
return true;
|
||||||
return !!(player.canPlayType("video/x-matroska") || player.canPlayType("video/mkv"));
|
return !!(player.canPlayType("video/x-matroska") || player.canPlayType("video/mkv"));
|
||||||
|
|
||||||
@ -89,66 +89,66 @@ function containerIsSupported(player: HTMLVideoElement, container: string, brows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//SHOULD CHECK FOR DEPTH (8bits ok but 10bits unsupported for almost every browsers)
|
// SHOULD CHECK FOR DEPTH (8bits ok but 10bits unsupported for almost every browsers)
|
||||||
function videoCodecIsSupported(player: HTMLVideoElement, codec: string, browser: string): boolean
|
function videoCodecIsSupported(player: HTMLVideoElement, codec: string, browser: string): boolean
|
||||||
{
|
{
|
||||||
switch (codec)
|
switch (codec)
|
||||||
{
|
{
|
||||||
case "h264":
|
case "h264":
|
||||||
return !!player.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"');
|
return !!player.canPlayType("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"");
|
||||||
case "h265":
|
case "h265":
|
||||||
case "hevc":
|
case "hevc":
|
||||||
if (browser == "tizen" || browser == "orsay" || browser == "xboxOne" || browser == "ios")
|
if (browser === "tizen" || browser === "orsay" || browser === "xboxOne" || browser === "ios")
|
||||||
return true;
|
return true;
|
||||||
//SHOULD SUPPORT CHROMECAST ULTRA
|
// SHOULD SUPPORT CHROMECAST ULTRA
|
||||||
// if (browser.chromecast)
|
// if (browser.chromecast)
|
||||||
// {
|
// {
|
||||||
|
|
||||||
// var isChromecastUltra = userAgent.indexOf('aarch64') !== -1;
|
// var isChromecastUltra = userAgent.indexOf('aarch64') !=== -1;
|
||||||
// if (isChromecastUltra)
|
// if (isChromecastUltra)
|
||||||
// {
|
// {
|
||||||
// return true;
|
// return true;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
return !!player.canPlayType('video/hevc; codecs="hevc, aac"');
|
return !!player.canPlayType("video/hevc; codecs=\"hevc, aac\"");
|
||||||
case "mpeg2video":
|
case "mpeg2video":
|
||||||
return browser == "orsay" || browser == "tizen" || browser == "edge";
|
return browser === "orsay" || browser === "tizen" || browser === "edge";
|
||||||
case "vc1":
|
case "vc1":
|
||||||
return browser == "orsay" || browser == "tizen" || browser == "edge";
|
return browser === "orsay" || browser === "tizen" || browser === "edge";
|
||||||
case "msmpeg4v2":
|
case "msmpeg4v2":
|
||||||
return browser == "orsay" || browser == "tizen";
|
return browser === "orsay" || browser === "tizen";
|
||||||
case "vp8":
|
case "vp8":
|
||||||
return !!player.canPlayType('video/webm; codecs="vp8');
|
return !!player.canPlayType("video/webm; codecs=\"vp8");
|
||||||
case "vp9":
|
case "vp9":
|
||||||
return !!player.canPlayType('video/webm; codecs="vp9"');
|
return !!player.canPlayType("video/webm; codecs=\"vp9\"");
|
||||||
case "vorbis":
|
case "vorbis":
|
||||||
return browser == "orsay" || browser == "tizen" || !!player.canPlayType('video/webm; codecs="vp8');
|
return browser === "orsay" || browser === "tizen" || !!player.canPlayType("video/webm; codecs=\"vp8");
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//SHOULD CHECK FOR NUMBER OF AUDIO CHANNEL (2 ok but 5 not in some browsers)
|
// SHOULD CHECK FOR NUMBER OF AUDIO CHANNEL (2 ok but 5 not in some browsers)
|
||||||
function audioCodecIsSupported(player: HTMLVideoElement, codec: string, browser: string): boolean
|
function audioCodecIsSupported(player: HTMLVideoElement, codec: string, browser: string): boolean
|
||||||
{
|
{
|
||||||
switch (codec)
|
switch (codec)
|
||||||
{
|
{
|
||||||
case "mp3":
|
case "mp3":
|
||||||
return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.69"') ||
|
return !!player.canPlayType("video/mp4; codecs=\"avc1.640029, mp4a.69\"") ||
|
||||||
!!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.6B"');
|
!!player.canPlayType("video/mp4; codecs=\"avc1.640029, mp4a.6B\"");
|
||||||
case "aac":
|
case "aac":
|
||||||
return !!player.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"');
|
return !!player.canPlayType("video/mp4; codecs=\"avc1.640029, mp4a.40.2\"");
|
||||||
case "mp2":
|
case "mp2":
|
||||||
return browser == "orsay" || browser == "tizen" || browser == "edge";
|
return browser === "orsay" || browser === "tizen" || browser === "edge";
|
||||||
case "pcm_s16le":
|
case "pcm_s16le":
|
||||||
case "pcm_s24le":
|
case "pcm_s24le":
|
||||||
return browser == "orsay" || browser == "tizen" || browser == "edge";
|
return browser === "orsay" || browser === "tizen" || browser === "edge";
|
||||||
case "aac_latm":
|
case "aac_latm":
|
||||||
return browser == "orsay" || browser == "tizen";
|
return browser === "orsay" || browser === "tizen";
|
||||||
case "opus":
|
case "opus":
|
||||||
return !!player.canPlayType('audio/ogg; codecs="opus"');
|
return !!player.canPlayType("audio/ogg; codecs=\"opus\"");
|
||||||
case "flac":
|
case "flac":
|
||||||
return browser == "orsay" || browser == "tizen" || browser == "edge";
|
return browser === "orsay" || browser === "tizen" || browser === "edge";
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
<div class="controller container-fluid" id="controller">
|
<div class="controller container-fluid" id="controller">
|
||||||
<div class="img d-none d-sm-block">
|
<div class="img d-none d-sm-block">
|
||||||
<img src="poster/{{this.item.showSlug}}" />
|
<img src="poster/{{this.item.showSlug}}" alt="poster" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h3 *ngIf="!this.item.isMovie">S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}</h3>
|
<h3 *ngIf="!this.item.isMovie">S{{this.item.seasonNumber}}:E{{this.item.episodeNumber}} - {{this.item.title}}</h3>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
|
|
||||||
<div id="next">
|
<div id="next">
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<img src="{{this.item.nextEpisode.thumb}}" />
|
<img src="{{this.item.nextEpisode.thumb}}" alt="next episode thumbnail" />
|
||||||
</div>
|
</div>
|
||||||
<div id="overview">
|
<div id="overview">
|
||||||
<h6>S{{this.item.nextEpisode.seasonNumber}}:E{{this.item.nextEpisode.episodeNumber}} - {{this.item.nextEpisode.title}}</h6>
|
<h6>S{{this.item.nextEpisode.seasonNumber}}:E{{this.item.nextEpisode.episodeNumber}} - {{this.item.nextEpisode.title}}</h6>
|
||||||
|
@ -24,7 +24,9 @@ import {
|
|||||||
} from "./playbackMethodDetector";
|
} from "./playbackMethodDetector";
|
||||||
import { AppComponent } from "../../app.component";
|
import { AppComponent } from "../../app.component";
|
||||||
import { Track, WatchItem } from "../../models/watch-item";
|
import { Track, WatchItem } from "../../models/watch-item";
|
||||||
import SubtitlesOctopus from "libass-wasm/dist/js/subtitles-octopus.js"
|
import SubtitlesOctopus from "libass-wasm/dist/js/subtitles-octopus.js";
|
||||||
|
import MouseMoveEvent = JQuery.MouseMoveEvent;
|
||||||
|
import TouchMoveEvent = JQuery.TouchMoveEvent;
|
||||||
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
@ -52,7 +54,7 @@ export class BufferToWidthPipe implements PipeTransform
|
|||||||
{
|
{
|
||||||
transform(buffered: TimeRanges, duration: number): string
|
transform(buffered: TimeRanges, duration: number): string
|
||||||
{
|
{
|
||||||
if (buffered.length == 0)
|
if (buffered.length === 0)
|
||||||
return "0";
|
return "0";
|
||||||
return `${buffered.end(buffered.length - 1) / duration * 100}%`;
|
return `${buffered.end(buffered.length - 1) / duration * 100}%`;
|
||||||
}
|
}
|
||||||
@ -66,7 +68,7 @@ export class VolumeToButtonPipe implements PipeTransform
|
|||||||
{
|
{
|
||||||
transform(volume: number, muted: boolean): string
|
transform(volume: number, muted: boolean): string
|
||||||
{
|
{
|
||||||
if (volume == 0 || muted)
|
if (volume === 0 || muted)
|
||||||
return "volume_off";
|
return "volume_off";
|
||||||
else if (volume < 25)
|
else if (volume < 25)
|
||||||
return "volume_mute";
|
return "volume_mute";
|
||||||
@ -167,7 +169,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
private startup: StartupService)
|
private startup: StartupService)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
document.getElementById("nav").classList.add("d-none");
|
document.getElementById("nav").classList.add("d-none");
|
||||||
if (AppComponent.isMobile)
|
if (AppComponent.isMobile)
|
||||||
@ -223,7 +225,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy()
|
ngOnDestroy(): void
|
||||||
{
|
{
|
||||||
if (this.subtitlesManager)
|
if (this.subtitlesManager)
|
||||||
this.subtitlesManager.dispose();
|
this.subtitlesManager.dispose();
|
||||||
@ -236,13 +238,13 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
$(document).off();
|
$(document).off();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit()
|
ngAfterViewInit(): void
|
||||||
{
|
{
|
||||||
if (this.oidcSecurity === undefined)
|
if (this.oidcSecurity === undefined)
|
||||||
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
this.oidcSecurity = this.injector.get(OidcSecurityService);
|
||||||
this.hlsPlayer.config.xhrSetup = xhr =>
|
this.hlsPlayer.config.xhrSetup = xhr =>
|
||||||
{
|
{
|
||||||
const token = this.oidcSecurity.getToken();
|
const token: string = this.oidcSecurity.getToken();
|
||||||
if (token)
|
if (token)
|
||||||
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
xhr.setRequestHeader("Authorization", "Bearer " + token);
|
||||||
};
|
};
|
||||||
@ -252,17 +254,17 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
setTimeout(() => this.route.data.subscribe(() =>
|
setTimeout(() => this.route.data.subscribe(() =>
|
||||||
{
|
{
|
||||||
// TODO remove the query param for the method (should be a session setting).
|
// TODO remove the query param for the method (should be a session setting).
|
||||||
let queryMethod: string = this.route.snapshot.queryParams["method"];
|
const queryMethod: string = this.route.snapshot.queryParams.method;
|
||||||
this.supportList = getWhatIsSupported(this.player, this.item);
|
this.supportList = getWhatIsSupported(this.player, this.item);
|
||||||
this.selectPlayMethod(queryMethod ? method[queryMethod] : this.supportList.getPlaybackMethod());
|
this.selectPlayMethod(queryMethod ? method[queryMethod] : this.supportList.getPlaybackMethod());
|
||||||
|
|
||||||
// TODO remove this, it should be a user's setting.
|
// TODO remove this, it should be a user's setting.
|
||||||
const subSlug: string = this.route.snapshot.queryParams["sub"];
|
const subSlug: string = this.route.snapshot.queryParams.sub;
|
||||||
if (subSlug != null)
|
if (subSlug != null)
|
||||||
{
|
{
|
||||||
const languageCode: string = subSlug.substring(0, 3);
|
const languageCode: string = subSlug.substring(0, 3);
|
||||||
const forced: boolean = subSlug.length > 3 && subSlug.substring(4) == "for";
|
const forced: boolean = subSlug.length > 3 && subSlug.substring(4) === "for";
|
||||||
const sub: Track = this.item.subtitles.find(x => x.language == languageCode && x.isForced == forced);
|
const sub: Track = this.item.subtitles.find(x => x.language === languageCode && x.isForced === forced);
|
||||||
this.selectSubtitle(sub, false);
|
this.selectSubtitle(sub, false);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -278,7 +280,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
return AppComponent.isMobile;
|
return AppComponent.isMobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTimeFromSeekbar(pageX: number)
|
getTimeFromSeekbar(pageX: number): number
|
||||||
{
|
{
|
||||||
const value: number = (pageX - this.progressBar.offsetLeft) / this.progressBar.clientWidth;
|
const value: number = (pageX - this.progressBar.offsetLeft) / this.progressBar.clientWidth;
|
||||||
const percent: number = Math.max(0, Math.min(value, 1));
|
const percent: number = Math.max(0, Math.min(value, 1));
|
||||||
@ -308,14 +310,14 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HostListener("document:touchmove", ["$event"])
|
@HostListener("document:touchmove", ["$event"])
|
||||||
touchSeek(event)
|
touchSeek(event: TouchMoveEvent): void
|
||||||
{
|
{
|
||||||
if (this.seeking)
|
if (this.seeking)
|
||||||
this.player.currentTime = this.getTimeFromSeekbar(event.changedTouches[0].pageX);
|
this.player.currentTime = this.getTimeFromSeekbar(event.changedTouches[0].pageX);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener("document:mousemove", ["$event"])
|
@HostListener("document:mousemove", ["$event"])
|
||||||
mouseMove(event)
|
mouseMove(event: MouseMoveEvent): void
|
||||||
{
|
{
|
||||||
if (this.seeking)
|
if (this.seeking)
|
||||||
this.player.currentTime = this.getTimeFromSeekbar(event.pageX);
|
this.player.currentTime = this.getTimeFromSeekbar(event.pageX);
|
||||||
@ -343,14 +345,14 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPlayMethod(playMethod: method)
|
selectPlayMethod(playMethod: method): void
|
||||||
{
|
{
|
||||||
this.playMethod = playMethod;
|
this.playMethod = playMethod;
|
||||||
const url: string = [
|
const url: string = [
|
||||||
"/video",
|
"/video",
|
||||||
this.playMethod.toLowerCase(),
|
this.playMethod.toLowerCase(),
|
||||||
this.item.slug,
|
this.item.slug,
|
||||||
this.playMethod != method.direct ? 'master.m3u8' : null
|
this.playMethod !== method.direct ? "master.m3u8" : null
|
||||||
].filter(x => x !== null).join("/");
|
].filter(x => x !== null).join("/");
|
||||||
if (this.playMethod === method.direct || this.player.canPlayType("application/vnd.apple.mpegurl"))
|
if (this.playMethod === method.direct || this.player.canPlayType("application/vnd.apple.mpegurl"))
|
||||||
this.player.src = url;
|
this.player.src = url;
|
||||||
@ -365,11 +367,11 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
back()
|
back(): void
|
||||||
{
|
{
|
||||||
if (this.startup.loadedFromWatch)
|
if (this.startup.loadedFromWatch)
|
||||||
{
|
{
|
||||||
this.router.navigate(["show", this.startup.show], {replaceUrl: true})
|
this.router.navigate(["show", this.startup.show], {replaceUrl: true});
|
||||||
this.startup.loadedFromWatch = false;
|
this.startup.loadedFromWatch = false;
|
||||||
this.startup.show = null;
|
this.startup.show = null;
|
||||||
}
|
}
|
||||||
@ -377,7 +379,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
next()
|
next(): void
|
||||||
{
|
{
|
||||||
if (this.item.nextEpisode == null)
|
if (this.item.nextEpisode == null)
|
||||||
return;
|
return;
|
||||||
@ -387,7 +389,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
previous()
|
previous(): void
|
||||||
{
|
{
|
||||||
if (this.item.previousEpisode == null)
|
if (this.item.previousEpisode == null)
|
||||||
return;
|
return;
|
||||||
@ -397,7 +399,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
videoClicked()
|
videoClicked(): void
|
||||||
{
|
{
|
||||||
if (AppComponent.isMobile)
|
if (AppComponent.isMobile)
|
||||||
this.showControls = !this.showControls;
|
this.showControls = !this.showControls;
|
||||||
@ -408,7 +410,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlayback()
|
togglePlayback(): void
|
||||||
{
|
{
|
||||||
if (this.player.paused)
|
if (this.player.paused)
|
||||||
this.player.play();
|
this.player.play();
|
||||||
@ -416,7 +418,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
this.player.pause();
|
this.player.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
fullscreen()
|
fullscreen(): void
|
||||||
{
|
{
|
||||||
if (this.isFullScreen)
|
if (this.isFullScreen)
|
||||||
document.exitFullscreen();
|
document.exitFullscreen();
|
||||||
@ -424,7 +426,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
document.body.requestFullscreen();
|
document.body.requestFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectSubtitle(subtitle: Track | number, changeUrl: boolean = true)
|
async selectSubtitle(subtitle: Track | number, changeUrl: boolean = true): Promise<void>
|
||||||
{
|
{
|
||||||
if (typeof(subtitle) === "number")
|
if (typeof(subtitle) === "number")
|
||||||
{
|
{
|
||||||
@ -444,7 +446,7 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
subSlug += "-for";
|
subSlug += "-for";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router.navigate([], {
|
await this.router.navigate([], {
|
||||||
relativeTo: this.route,
|
relativeTo: this.route,
|
||||||
queryParams: {sub: subSlug},
|
queryParams: {sub: subSlug},
|
||||||
replaceUrl: true,
|
replaceUrl: true,
|
||||||
@ -475,11 +477,11 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
});
|
});
|
||||||
this.removeHtmlTrack();
|
this.removeHtmlTrack();
|
||||||
|
|
||||||
if (subtitle.codec == "ass")
|
if (subtitle.codec === "ass")
|
||||||
{
|
{
|
||||||
if (!this.subtitlesManager)
|
if (!this.subtitlesManager)
|
||||||
{
|
{
|
||||||
let fonts: {[key: string]: string} = await this.shows.getFonts(this.item.showSlug).toPromise();
|
const fonts: { [key: string]: string } = await this.shows.getFonts(this.item.showSlug).toPromise();
|
||||||
this.subtitlesManager = new SubtitlesOctopus({
|
this.subtitlesManager = new SubtitlesOctopus({
|
||||||
video: this.player,
|
video: this.player,
|
||||||
subUrl: `subtitle/${subtitle.slug}`,
|
subUrl: `subtitle/${subtitle.slug}`,
|
||||||
@ -489,12 +491,12 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
else
|
else
|
||||||
this.subtitlesManager.setTrackByUrl(`subtitle/${subtitle.slug}`);
|
this.subtitlesManager.setTrackByUrl(`subtitle/${subtitle.slug}`);
|
||||||
}
|
}
|
||||||
else if (subtitle.codec == "subrip")
|
else if (subtitle.codec === "subrip")
|
||||||
{
|
{
|
||||||
if (this.subtitlesManager)
|
if (this.subtitlesManager)
|
||||||
this.subtitlesManager.freeTrack();
|
this.subtitlesManager.freeTrack();
|
||||||
|
|
||||||
let track = document.createElement("track");
|
const track: HTMLTrackElement = document.createElement("track");
|
||||||
track.kind = "subtitles";
|
track.kind = "subtitles";
|
||||||
track.label = subtitle.displayName;
|
track.label = subtitle.displayName;
|
||||||
track.srclang = subtitle.language;
|
track.srclang = subtitle.language;
|
||||||
@ -510,9 +512,9 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeHtmlTrack()
|
removeHtmlTrack(): void
|
||||||
{
|
{
|
||||||
let elements = this.player.getElementsByTagName("track");
|
const elements: HTMLCollectionOf<HTMLTrackElement> = this.player.getElementsByTagName("track");
|
||||||
if (elements.length > 0)
|
if (elements.length > 0)
|
||||||
elements.item(0).remove();
|
elements.item(0).remove();
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, AfterViewInit } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { SearchResult } from "../../models/search-result";
|
import { SearchResult } from "../../models/search-result";
|
||||||
import { Title } from "@angular/platform-browser";
|
import { Title } from "@angular/platform-browser";
|
||||||
import {Page} from "../../models/page";
|
import { Page } from "../../models/page";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search',
|
selector: "app-search",
|
||||||
templateUrl: './search.component.html',
|
templateUrl: "./search.component.html",
|
||||||
styleUrls: ['./search.component.scss']
|
styleUrls: ["./search.component.scss"]
|
||||||
})
|
})
|
||||||
export class SearchComponent implements OnInit, OnDestroy
|
export class SearchComponent implements OnInit, OnDestroy, AfterViewInit
|
||||||
{
|
{
|
||||||
items: SearchResult;
|
items: SearchResult;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private title: Title) { }
|
constructor(private route: ActivatedRoute, private title: Title) { }
|
||||||
|
|
||||||
ngOnInit()
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.route.data.subscribe((data) =>
|
this.route.data.subscribe((data) =>
|
||||||
{
|
{
|
||||||
@ -24,16 +24,16 @@ export class SearchComponent implements OnInit, OnDestroy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit()
|
ngAfterViewInit(): void
|
||||||
{
|
{
|
||||||
let searchBar: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
|
const searchBar: HTMLInputElement = document.getElementById("search") as HTMLInputElement;
|
||||||
searchBar.classList.add("searching");
|
searchBar.classList.add("searching");
|
||||||
searchBar.value = this.items.query;
|
searchBar.value = this.items.query;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy()
|
ngOnDestroy(): void
|
||||||
{
|
{
|
||||||
let searchBar: HTMLInputElement = <HTMLInputElement>document.getElementById("search");
|
const searchBar: HTMLInputElement = document.getElementById("search") as HTMLInputElement;
|
||||||
searchBar.classList.remove("searching");
|
searchBar.classList.remove("searching");
|
||||||
searchBar.value = "";
|
searchBar.value = "";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="backdrop">
|
<div class="backdrop">
|
||||||
<img id="backdrop" src="backdrop/{{this.show.slug}}" />
|
<img id="backdrop" src="backdrop/{{this.show.slug}}" alt="backdrop" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header container pt-sm-5">
|
<div class="header container pt-sm-5">
|
||||||
@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-menu #showMenu="matMenu">
|
<mat-menu #showMenu="matMenu">
|
||||||
<button mat-menu-item (click)="editMetadata()">Edit metadata</button>
|
<button mat-menu-item (click)="editMetadata()">Edit metadata</button>
|
||||||
<button mat-menu-item (click)="redownloadImages()">Re-download images</button>
|
<button mat-menu-item (click)="redownloadImages()">Re-download images</button>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<app-episodes-list [episodes]="episodes[season]"></app-episodes-list>
|
<app-episodes-list [episodes]="episodes[season]"></app-episodes-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container-fluid mt-5">
|
<div class="container-fluid mt-5">
|
||||||
<h3>Staff</h3>
|
<h3>Staff</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
import {Component, Inject} from '@angular/core';
|
import { Component, Inject } from "@angular/core";
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
||||||
import {DomSanitizer} from "@angular/platform-browser";
|
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-trailer-dialog',
|
selector: "app-trailer-dialog",
|
||||||
templateUrl: './trailer-dialog.component.html',
|
templateUrl: "./trailer-dialog.component.html",
|
||||||
styleUrls: ['./trailer-dialog.component.scss']
|
styleUrls: ["./trailer-dialog.component.scss"]
|
||||||
})
|
})
|
||||||
export class TrailerDialogComponent
|
export class TrailerDialogComponent
|
||||||
{
|
{
|
||||||
constructor(public dialogRef: MatDialogRef<TrailerDialogComponent>, public sanitizer: DomSanitizer, @Inject(MAT_DIALOG_DATA) public trailer: string) {}
|
constructor(public dialogRef: MatDialogRef<TrailerDialogComponent>,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public trailer: string)
|
||||||
|
{}
|
||||||
|
|
||||||
getYtTrailer()
|
getYtTrailer(): SafeUrl
|
||||||
{
|
{
|
||||||
if (!this.trailer.includes("youtube.com"))
|
if (!this.trailer.includes("youtube.com"))
|
||||||
return null;
|
return null;
|
||||||
let uri = "https://www.youtube.com/embed/" + this.trailer.substring(this.trailer.indexOf("watch?v=") + 8) + "?autoplay=1";
|
const ytID: string = this.trailer.substring(this.trailer.indexOf("watch?v=") + 8);
|
||||||
|
const uri: string = `https://www.youtube.com/embed/${ytID}?autoplay=1`;
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(uri);
|
return this.sanitizer.bypassSecurityTrustResourceUrl(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
import {Observable} from "rxjs"
|
import { Observable } from "rxjs";
|
||||||
import {Page} from "../models/page";
|
import { Page } from "../models/page";
|
||||||
import { Genre } from "../models/resources/genre";
|
import { Genre } from "../models/resources/genre";
|
||||||
import {IResource} from "../models/resources/resource";
|
import { IResource } from "../models/resources/resource";
|
||||||
import {Library} from "../models/resources/library";
|
import { Library } from "../models/resources/library";
|
||||||
import {LibraryItem} from "../models/resources/library-item";
|
import { LibraryItem } from "../models/resources/library-item";
|
||||||
import {map} from "rxjs/operators";
|
import { map } from "rxjs/operators";
|
||||||
import {Season} from "../models/resources/season";
|
import { Season } from "../models/resources/season";
|
||||||
import {Episode} from "../models/resources/episode";
|
import { Episode } from "../models/resources/episode";
|
||||||
import {People} from "../models/resources/people";
|
import { People } from "../models/resources/people";
|
||||||
import {Show} from "../models/resources/show";
|
import { Show } from "../models/resources/show";
|
||||||
import { Studio } from "../models/resources/studio";
|
import { Studio } from "../models/resources/studio";
|
||||||
|
|
||||||
export interface ApiArgs
|
export interface ApiArgs
|
||||||
@ -34,7 +34,7 @@ class CrudApi<T extends IResource>
|
|||||||
{
|
{
|
||||||
if (args == null)
|
if (args == null)
|
||||||
return "";
|
return "";
|
||||||
let params: string = Object.keys(args).map(x => `${x}=${args[x]}`).join("&");
|
const params: string = Object.keys(args).map(x => `${x}=${args[x]}`).join("&");
|
||||||
|
|
||||||
return params ? `?${params}` : "";
|
return params ? `?${params}` : "";
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ class CrudApi<T extends IResource>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class LibraryService extends CrudApi<Library>
|
export class LibraryService extends CrudApi<Library>
|
||||||
{
|
{
|
||||||
@ -78,7 +78,7 @@ export class LibraryService extends CrudApi<Library>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class LibraryItemService extends CrudApi<LibraryItem>
|
export class LibraryItemService extends CrudApi<LibraryItem>
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ export class LibraryItemService extends CrudApi<LibraryItem>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class SeasonService extends CrudApi<Season>
|
export class SeasonService extends CrudApi<Season>
|
||||||
{
|
{
|
||||||
@ -106,7 +106,7 @@ export class SeasonService extends CrudApi<Season>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class EpisodeService extends CrudApi<Episode>
|
export class EpisodeService extends CrudApi<Episode>
|
||||||
{
|
{
|
||||||
@ -129,7 +129,7 @@ export class EpisodeService extends CrudApi<Episode>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class PeopleService extends CrudApi<People>
|
export class PeopleService extends CrudApi<People>
|
||||||
{
|
{
|
||||||
@ -146,7 +146,7 @@ export class PeopleService extends CrudApi<People>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class GenreService extends CrudApi<Genre>
|
export class GenreService extends CrudApi<Genre>
|
||||||
{
|
{
|
||||||
@ -163,7 +163,7 @@ export class GenreService extends CrudApi<Genre>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class ShowService extends CrudApi<Show>
|
export class ShowService extends CrudApi<Show>
|
||||||
{
|
{
|
||||||
@ -172,7 +172,7 @@ export class ShowService extends CrudApi<Show>
|
|||||||
super(client, "shows");
|
super(client, "shows");
|
||||||
}
|
}
|
||||||
|
|
||||||
getForCollection(collection: string | number, args?: ApiArgs) : Observable<Page<Show>>
|
getForCollection(collection: string | number, args?: ApiArgs): Observable<Page<Show>>
|
||||||
{
|
{
|
||||||
return this.client.get<Page<Show>>(`/api/collections/${collection}/shows${this.ArgsAsQuery(args)}`)
|
return this.client.get<Page<Show>>(`/api/collections/${collection}/shows${this.ArgsAsQuery(args)}`)
|
||||||
.pipe(map(x => Object.assign(new Page<Show>(), x)));
|
.pipe(map(x => Object.assign(new Page<Show>(), x)));
|
||||||
@ -185,7 +185,7 @@ export class ShowService extends CrudApi<Show>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root"
|
||||||
})
|
})
|
||||||
export class StudioService extends CrudApi<Studio>
|
export class StudioService extends CrudApi<Studio>
|
||||||
{
|
{
|
||||||
@ -194,7 +194,7 @@ export class StudioService extends CrudApi<Studio>
|
|||||||
super(client, "studios");
|
super(client, "studios");
|
||||||
}
|
}
|
||||||
|
|
||||||
getForShow(show: string | number) : Observable<Studio>
|
getForShow(show: string | number): Observable<Studio>
|
||||||
{
|
{
|
||||||
return this.client.get<Studio>(`/api/show/${show}/studio}`);
|
return this.client.get<Studio>(`/api/show/${show}/studio}`);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export class ItemResolver
|
|||||||
{
|
{
|
||||||
public static resolvers: any[] = [];
|
public static resolvers: any[] = [];
|
||||||
|
|
||||||
static forResource<T>(resource: string)
|
static forResource<T>(resource: string): any
|
||||||
{
|
{
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class Resolver implements Resolve<T>
|
class Resolver implements Resolve<T>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
|
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
|
||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||||
import {ActivatedRouteSnapshot, Resolve} from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve } from "@angular/router";
|
||||||
import {Observable, EMPTY} from 'rxjs';
|
import { Observable, EMPTY } from "rxjs";
|
||||||
import {catchError, map} from 'rxjs/operators';
|
import { catchError, map } from "rxjs/operators";
|
||||||
import {Page} from "../models/page";
|
import { Page } from "../models/page";
|
||||||
import {IResource} from "../models/resources/resource";
|
import { IResource } from "../models/resources/resource";
|
||||||
|
|
||||||
type RouteMapper = (route: ActivatedRouteSnapshot, endpoint: string, queryParams: [string, string][]) => string;
|
type RouteMapper = (route: ActivatedRouteSnapshot, endpoint: string, queryParams: [string, string][]) => string;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export class PageResolver
|
|||||||
|
|
||||||
static forResource<T extends IResource>(resource: string,
|
static forResource<T extends IResource>(resource: string,
|
||||||
copyParams: boolean | string[] | RouteMapper = false,
|
copyParams: boolean | string[] | RouteMapper = false,
|
||||||
defaultQuery: string = null)
|
defaultQuery: string = null): any
|
||||||
{
|
{
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class Resolver implements Resolve<Page<T>>
|
class Resolver implements Resolve<Page<T>>
|
||||||
@ -27,23 +27,23 @@ export class PageResolver
|
|||||||
|
|
||||||
resolve(route: ActivatedRouteSnapshot): Page<T> | Observable<Page<T>> | Promise<Page<T>>
|
resolve(route: ActivatedRouteSnapshot): Page<T> | Observable<Page<T>> | Promise<Page<T>>
|
||||||
{
|
{
|
||||||
let res: string = resource.replace(/:([^:]*?)(\/|$|&)/g, (x, y, z) => `${route.paramMap.get(y)}${z}`);
|
const res: string = resource.replace(/:([^:]*?)(\/|$|&)/g, (x, y, z) => `${route.paramMap.get(y)}${z}`);
|
||||||
let query: [string, string][] = defaultQuery
|
const query: [string, string][] = defaultQuery
|
||||||
?.replace(/:([^:]*?)(\/|$|&)/g, (x, y, z) => `${route.paramMap.get(y)}${z}`)
|
?.replace(/:([^:]*?)(\/|$|&)/g, (x, y, z) => `${route.paramMap.get(y)}${z}`)
|
||||||
.split('&')
|
.split("&")
|
||||||
.map(x => x.split('=') as [string, string]);
|
.map(x => x.split("=") as [string, string]);
|
||||||
let uri: string;
|
let uri: string;
|
||||||
if (typeof copyParams == "function")
|
if (typeof copyParams === "function")
|
||||||
uri = copyParams(route, res, query);
|
uri = copyParams(route, res, query);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let entries: [string, string][] = copyParams == true
|
const entries: [string, string][] = copyParams === true
|
||||||
? Object.entries(route.queryParams)
|
? Object.entries(route.queryParams)
|
||||||
: Object.entries(route.queryParams).filter(x => copyParams && copyParams.includes(x[0]));
|
: Object.entries(route.queryParams).filter(x => copyParams && copyParams.includes(x[0]));
|
||||||
if (query)
|
if (query)
|
||||||
entries.push(...query);
|
entries.push(...query);
|
||||||
let params: string = entries.length > 0
|
const params: string = entries.length > 0
|
||||||
? '?' + entries.map(x => `${x[0]}=${x[1]}`).join('&')
|
? "?" + entries.map(x => `${x[0]}=${x[1]}`).join("&")
|
||||||
: "";
|
: "";
|
||||||
uri = `api/${res}${params}`;
|
uri = `api/${res}${params}`;
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ export class PageResolver
|
|||||||
console.log(error.status + " - " + error.message);
|
console.log(error.status + " - " + error.message);
|
||||||
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
|
this.snackBar.open(`An unknown error occurred: ${error.message}.`, null, {
|
||||||
horizontalPosition: "left",
|
horizontalPosition: "left",
|
||||||
panelClass: ['snackError'],
|
panelClass: ["snackError"],
|
||||||
duration: 2500
|
duration: 2500
|
||||||
});
|
});
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
|
@ -15,7 +15,7 @@ export class PreLoaderService
|
|||||||
|
|
||||||
load<T>(route: string): Observable<T[]>
|
load<T>(route: string): Observable<T[]>
|
||||||
{
|
{
|
||||||
let loaded = this.cache.find(x => x[0] === route);
|
const loaded: [string, any[]] = this.cache.find(x => x[0] === route);
|
||||||
if (loaded != null)
|
if (loaded != null)
|
||||||
return of(loaded[1]);
|
return of(loaded[1]);
|
||||||
return this.http.get<Page<T>>(route).pipe(map(newData =>
|
return this.http.get<Page<T>>(route).pipe(map(newData =>
|
||||||
|
@ -15,7 +15,7 @@ export class StartupService
|
|||||||
if (window.location.pathname.startsWith("/watch/"))
|
if (window.location.pathname.startsWith("/watch/"))
|
||||||
{
|
{
|
||||||
this.loadedFromWatch = true;
|
this.loadedFromWatch = true;
|
||||||
this.show = window.location.pathname.match(/^\/watch\/(?<show>.*)(-s\d+e\d+)+?$/).groups["show"];
|
this.show = window.location.pathname.match(/^\/watch\/(?<show>.*)(-s\d+e\d+)+?$/).groups.show;
|
||||||
}
|
}
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export const environment = {
|
export const environment: any = {
|
||||||
production: true
|
production: true
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
// The list of file replacements can be found in `angular.json`.
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
export const environment = {
|
export const environment: any = {
|
||||||
production: false
|
production: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from "@angular/core";
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||||
|
import { AppModule } from "./app/app.module";
|
||||||
import { AppModule } from './app/app.module';
|
import { environment } from "./environments/environment";
|
||||||
import { environment } from './environments/environment';
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
* Zone JS is required by default for Angular itself.
|
* Zone JS is required by default for Angular itself.
|
||||||
*/
|
*/
|
||||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
import "zone.js/dist/zone"; // Included with Angular CLI.
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
|
16
tslint.json
16
tslint.json
@ -42,14 +42,12 @@
|
|||||||
"trace"
|
"trace"
|
||||||
],
|
],
|
||||||
"no-empty": false,
|
"no-empty": false,
|
||||||
"no-inferrable-types": [
|
"no-inferrable-types": false,
|
||||||
true,
|
|
||||||
"ignore-params"
|
|
||||||
],
|
|
||||||
"no-non-null-assertion": true,
|
"no-non-null-assertion": true,
|
||||||
"no-redundant-jsdoc": true,
|
"no-redundant-jsdoc": true,
|
||||||
|
"jsdoc-format": false,
|
||||||
"no-switch-case-fall-through": true,
|
"no-switch-case-fall-through": true,
|
||||||
"no-var-requires": false,
|
"no-var-requires": true,
|
||||||
"object-literal-key-quotes": [
|
"object-literal-key-quotes": [
|
||||||
true,
|
true,
|
||||||
"as-needed"
|
"as-needed"
|
||||||
@ -74,7 +72,10 @@
|
|||||||
},
|
},
|
||||||
"typedef": [
|
"typedef": [
|
||||||
true,
|
true,
|
||||||
"call-signature"
|
"call-signature",
|
||||||
|
"parameter",
|
||||||
|
"property-declaration",
|
||||||
|
"variable-declaration"
|
||||||
],
|
],
|
||||||
"typedef-whitespace": {
|
"typedef-whitespace": {
|
||||||
"options": [
|
"options": [
|
||||||
@ -98,7 +99,8 @@
|
|||||||
"options": [
|
"options": [
|
||||||
"ban-keywords",
|
"ban-keywords",
|
||||||
"check-format",
|
"check-format",
|
||||||
"allow-pascal-case"
|
"allow-leading-underscore",
|
||||||
|
"require-const-for-all-caps"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"whitespace": {
|
"whitespace": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user