mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-08 02:34:16 -04:00
Using auto completion for people & studio and updating everything
This commit is contained in:
parent
33d8759e0f
commit
c2efae048c
2783
package-lock.json
generated
2783
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@ -9,39 +9,40 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^9.1.12",
|
||||
"@angular/cdk": "^9.2.4",
|
||||
"@angular/common": "^9.1.12",
|
||||
"@angular/compiler": "^9.1.12",
|
||||
"@angular/core": "^9.1.12",
|
||||
"@angular/forms": "^9.1.12",
|
||||
"@angular/material": "^9.2.4",
|
||||
"@angular/platform-browser": "^9.1.12",
|
||||
"@angular/platform-browser-dynamic": "^9.1.12",
|
||||
"@angular/router": "^9.1.12",
|
||||
"angular-auth-oidc-client": "10.0.14",
|
||||
"@angular/animations": "^10.1.4",
|
||||
"@angular/cdk": "^10.2.3",
|
||||
"@angular/common": "^10.1.4",
|
||||
"@angular/compiler": "^10.1.4",
|
||||
"@angular/core": "^10.1.4",
|
||||
"@angular/forms": "^10.1.4",
|
||||
"@angular/material": "^10.2.3",
|
||||
"@angular/platform-browser": "^10.1.4",
|
||||
"@angular/platform-browser-dynamic": "^10.1.4",
|
||||
"@angular/router": "^10.1.4",
|
||||
"angular-auth-oidc-client": "11.2.0",
|
||||
"bootstrap": "^4.5.2",
|
||||
"detect-browser": "^5.1.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"hls.js": "^0.13.2",
|
||||
"hls.js": "^0.14.13",
|
||||
"jquery": "^3.5.1",
|
||||
"ngx-infinite-scroll": "^9.1.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"zone.js": "^0.10.3"
|
||||
"rxjs": "^6.6.3",
|
||||
"zone.js": "^0.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.901.12",
|
||||
"@angular/cli": "^9.1.12",
|
||||
"@angular/compiler-cli": "^9.1.12",
|
||||
"@angular/language-service": "^9.1.12",
|
||||
"@angular-devkit/build-angular": "^0.1001.4",
|
||||
"@angular/cli": "^10.1.4",
|
||||
"@angular/compiler-cli": "^10.1.4",
|
||||
"@angular/language-service": "^10.1.4",
|
||||
"@types/bootstrap": "^4.5.0",
|
||||
"@types/hls.js": "^0.12.6",
|
||||
"@types/hls.js": "^0.13.1",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/node": "^13.13.21",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/video.js": "^7.3.11",
|
||||
"codelyzer": "^5.2.2",
|
||||
"ts-node": "~8.6.2",
|
||||
"tslint": "^5.0.0",
|
||||
"typescript": "~3.7.5"
|
||||
"codelyzer": "^6.0.1",
|
||||
"ts-node": "~9.0.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "~4.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { NotFoundComponent } from './pages/not-found/not-found.component';
|
||||
import { PageResolver } from './services/page-resolver.service';
|
||||
import { ShowDetailsComponent } from './pages/show-details/show-details.component';
|
||||
import { AuthGuard } from "./auth/misc/authenticated-guard.service";
|
||||
import { LibraryItem } from "../models/resources/library-item";
|
||||
import { LibraryItem } from "./models/resources/library-item";
|
||||
import {
|
||||
EpisodeService,
|
||||
LibraryItemService,
|
||||
@ -15,14 +15,14 @@ import {
|
||||
SeasonService,
|
||||
ShowService
|
||||
} from "./services/api.service";
|
||||
import { Show } from "../models/resources/show";
|
||||
import { Show } from "./models/resources/show";
|
||||
import { ItemResolver } from "./services/item-resolver.service";
|
||||
import { CollectionComponent } from "./pages/collection/collection.component";
|
||||
import { Collection } from "../models/resources/collection";
|
||||
import { Collection } from "./models/resources/collection";
|
||||
import { SearchComponent } from "./pages/search/search.component";
|
||||
import { SearchResult } from "../models/search-result";
|
||||
import { SearchResult } from "./models/search-result";
|
||||
import { PlayerComponent } from "./pages/player/player.component";
|
||||
import { WatchItem } from "../models/watch-item";
|
||||
import { WatchItem } from "./models/watch-item";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "browse", component: ItemsGridComponent, pathMatch: "full",
|
||||
|
@ -26,7 +26,15 @@
|
||||
</li>
|
||||
<ng-template #accountDrop>
|
||||
<li #accountParent class="nav-item icon" style="opacity: 1 !important;">
|
||||
<img matRipple [src]="authManager.user.picture" [matMenuTriggerFor]="accountMenu" class="profilePicture" matTooltipPosition="below" [matTooltip]="authManager.user.username" fallback="more.svg" (error)="accountParent.style.removeProperty('opacity');" />
|
||||
<img alt="Account"
|
||||
matRipple
|
||||
[src]="authManager.account.picture"
|
||||
[matMenuTriggerFor]="accountMenu"
|
||||
class="profilePicture"
|
||||
matTooltipPosition="below"
|
||||
[matTooltip]="authManager.account.username"
|
||||
fallback="more.svg"
|
||||
(error)="accountParent.style.removeProperty('opacity');" />
|
||||
</li>
|
||||
<mat-menu #accountMenu="matMenu">
|
||||
<button class="dropButton" mat-menu-item (click)="this.openAccountDialog()">Settings</button>
|
||||
|
@ -2,10 +2,9 @@ import {Component} from '@angular/core';
|
||||
import {Event, Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError} from '@angular/router';
|
||||
import {Location} from "@angular/common";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {Account} from "../models/account";
|
||||
import {AccountComponent} from "./auth/account/account.component";
|
||||
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 * as $ from "jquery";
|
||||
|
||||
@ -77,11 +76,7 @@ export class AppComponent
|
||||
|
||||
openAccountDialog()
|
||||
{
|
||||
const dialog = this.dialog.open(AccountComponent, {width: "500px", data: this.authManager.getAccount()});
|
||||
dialog.afterClosed().subscribe((result: Account) =>
|
||||
{
|
||||
this.authManager.getUser();
|
||||
});
|
||||
this.dialog.open(AccountComponent, {width: "500px", data: this.authManager.account});
|
||||
}
|
||||
|
||||
get isAuthenticated(): boolean
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Component, ElementRef, Inject, ViewChild} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Account} from "../../../models/account";
|
||||
import {Account} from "../../models/account";
|
||||
|
||||
|
||||
@Component({
|
||||
@ -14,7 +14,9 @@ export class AccountComponent
|
||||
selectedPicture: File;
|
||||
@ViewChild("accountImg") accountImg: ElementRef;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<AccountComponent>, @Inject(MAT_DIALOG_DATA) public account: Account, private http: HttpClient) {}
|
||||
constructor(public dialogRef: MatDialogRef<AccountComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public account: Account,
|
||||
private http: HttpClient) {}
|
||||
|
||||
finish()
|
||||
{
|
||||
|
@ -1,31 +1,49 @@
|
||||
import {APP_INITIALIZER, NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AccountComponent} from "./account/account.component";
|
||||
import {AuthPipe} from "./misc/auth.pipe";
|
||||
import {UnauthorizedComponent} from "./unauthorized/unauthorized.component";
|
||||
import {LogoutComponent} from "./logout/logout.component";
|
||||
import {ConfigResult, OidcConfigService, OidcSecurityService, OpenIdConfiguration, AuthModule as OidcModule} from "angular-auth-oidc-client";
|
||||
import {HTTP_INTERCEPTORS, HttpClient} from "@angular/common/http";
|
||||
import {AuthGuard} from "./misc/authenticated-guard.service";
|
||||
import {AuthorizerInterceptor} from "./misc/authorizer-interceptor.service";
|
||||
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||
import {MatIconModule} from "@angular/material/icon";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
import {MatDialogModule} from "@angular/material/dialog";
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {MatSelectModule} from "@angular/material/select";
|
||||
import {MatMenuModule} from "@angular/material/menu";
|
||||
import {MatSliderModule} from "@angular/material/slider";
|
||||
import {MatTooltipModule} from "@angular/material/tooltip";
|
||||
import {MatRippleModule} from "@angular/material/core";
|
||||
import {MatCardModule} from "@angular/material/card";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatTabsModule} from "@angular/material/tabs";
|
||||
import {MatCheckboxModule} from "@angular/material/checkbox";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { HTTP_INTERCEPTORS, HttpClient } from "@angular/common/http";
|
||||
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
||||
import { FormsModule } from "@angular/forms";
|
||||
import { MatButtonModule } from "@angular/material/button";
|
||||
import { MatCardModule } from "@angular/material/card";
|
||||
import { MatCheckboxModule } from "@angular/material/checkbox";
|
||||
import { MatRippleModule } from "@angular/material/core";
|
||||
import { MatDialogModule } from "@angular/material/dialog";
|
||||
import { MatFormFieldModule } from "@angular/material/form-field";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatMenuModule } from "@angular/material/menu";
|
||||
import { MatSelectModule } from "@angular/material/select";
|
||||
import { MatSliderModule } from "@angular/material/slider";
|
||||
import { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { AuthModule as OidcModule, LogLevel, OidcConfigService } from "angular-auth-oidc-client";
|
||||
import { tap } from "rxjs/operators";
|
||||
import { AccountComponent } from "./account/account.component";
|
||||
import { LogoutComponent } from "./logout/logout.component";
|
||||
import { AuthPipe } from "./misc/auth.pipe";
|
||||
import { AuthGuard } from "./misc/authenticated-guard.service";
|
||||
import { AuthorizerInterceptor } from "./misc/authorizer-interceptor.service";
|
||||
import { UnauthorizedComponent } from "./unauthorized/unauthorized.component";
|
||||
|
||||
export function loadConfig(oidcConfigService: OidcConfigService)
|
||||
{
|
||||
return () => oidcConfigService.load_using_stsServer(window.location.origin);
|
||||
return () => oidcConfigService.withConfig({
|
||||
stsServer: window.location.origin,
|
||||
redirectUrl: "/",
|
||||
postLogoutRedirectUri: "/logout",
|
||||
clientId: "kyoo.webapp",
|
||||
responseType: "code",
|
||||
triggerAuthorizationResultEvent: false,
|
||||
scope: "openid profile offline_access",
|
||||
silentRenew: true,
|
||||
silentRenewUrl: "/silent.html",
|
||||
useRefreshToken: true,
|
||||
startCheckSession: true,
|
||||
|
||||
forbiddenRoute: "/forbidden",
|
||||
unauthorizedRoute: "/unauthorized",
|
||||
logLevel: LogLevel.Debug
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
@ -51,7 +69,8 @@ export function loadConfig(oidcConfigService: OidcConfigService)
|
||||
FormsModule,
|
||||
MatTabsModule,
|
||||
MatCheckboxModule,
|
||||
OidcModule.forRoot()
|
||||
OidcModule.forRoot(),
|
||||
RouterModule
|
||||
],
|
||||
entryComponents: [
|
||||
AccountComponent
|
||||
@ -74,32 +93,9 @@ export function loadConfig(oidcConfigService: OidcConfigService)
|
||||
})
|
||||
export class AuthModule
|
||||
{
|
||||
constructor(private oidcSecurityService: OidcSecurityService, private oidcConfigService: OidcConfigService, http: HttpClient)
|
||||
constructor(http: HttpClient)
|
||||
{
|
||||
this.oidcConfigService.onConfigurationLoaded.subscribe((configResult: ConfigResult) =>
|
||||
{
|
||||
const config: OpenIdConfiguration = {
|
||||
stsServer: configResult.customConfig.stsServer,
|
||||
redirect_url: "/",
|
||||
post_logout_redirect_uri: "/logout",
|
||||
client_id: 'kyoo.webapp',
|
||||
response_type: "code",
|
||||
trigger_authorization_result_event: false,
|
||||
scope: "openid profile",
|
||||
silent_renew: true,
|
||||
silent_renew_url: "/silent.html",
|
||||
use_refresh_token: false,
|
||||
start_checksession: true,
|
||||
|
||||
forbidden_route: '/Forbidden',
|
||||
unauthorized_route: '/Unauthorized',
|
||||
log_console_warning_active: true,
|
||||
log_console_debug_active: true
|
||||
};
|
||||
|
||||
this.oidcSecurityService.setupModule(config, configResult.authWellknownEndpoints);
|
||||
});
|
||||
|
||||
http.get("/api/account/default-permissions").subscribe((result: string[]) => AuthGuard.defaultPermissions = result);
|
||||
AuthGuard.permissionsObservable = http.get<string[]>("/api/account/default-permissions")
|
||||
.pipe(tap(x => AuthGuard.defaultPermissions = x));
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AuthorizationResult, AuthorizationState, OidcSecurityService, ValidationResult} from "angular-auth-oidc-client";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Account} from "../../models/account";
|
||||
import {Router} from "@angular/router";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { OidcSecurityService } from "angular-auth-oidc-client";
|
||||
import { Account } from "../models/account";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -10,31 +9,23 @@ import {Router} from "@angular/router";
|
||||
export class AuthService
|
||||
{
|
||||
isAuthenticated: boolean = false;
|
||||
user: any;
|
||||
account: Account = null;
|
||||
|
||||
constructor(public oidcSecurityService: OidcSecurityService, private http: HttpClient, private router: Router)
|
||||
constructor(private oidcSecurityService: OidcSecurityService,
|
||||
private http: HttpClient)
|
||||
{
|
||||
if (this.oidcSecurityService.moduleSetup)
|
||||
this.authorizeCallback();
|
||||
else
|
||||
this.oidcSecurityService.onModuleSetup.subscribe(() =>
|
||||
{
|
||||
this.authorizeCallback();
|
||||
});
|
||||
|
||||
this.oidcSecurityService.onAuthorizationResult.subscribe((authorizationResult: AuthorizationResult) =>
|
||||
this.oidcSecurityService.checkAuth()
|
||||
.subscribe((auth: boolean) => this.isAuthenticated = auth);
|
||||
this.oidcSecurityService.userData$.subscribe(x =>
|
||||
{
|
||||
this.getUser();
|
||||
this.isAuthenticated = authorizationResult.authorizationState == AuthorizationState.authorized;
|
||||
});
|
||||
this.getUser();
|
||||
}
|
||||
|
||||
getUser()
|
||||
{
|
||||
this.oidcSecurityService.getUserData().subscribe(userData =>
|
||||
{
|
||||
this.user = userData;
|
||||
if (x == null)
|
||||
return;
|
||||
this.account = {
|
||||
email: x.email,
|
||||
username: x.username,
|
||||
picture: x.picture,
|
||||
permissions: x.permissions.split(',')
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,30 +36,10 @@ export class AuthService
|
||||
|
||||
logout()
|
||||
{
|
||||
document.cookie = "Authenticated=false; expires=" + new Date(2147483647 * 1000).toUTCString();
|
||||
this.http.get("api/account/logout").subscribe(() =>
|
||||
{
|
||||
// this.http.get("api/account/logout").subscribe(() =>
|
||||
// {
|
||||
this.oidcSecurityService.logoff();
|
||||
});
|
||||
}
|
||||
|
||||
private authorizeCallback()
|
||||
{
|
||||
if (window.location.href.indexOf("?code=") != -1)
|
||||
this.oidcSecurityService.authorizedCallbackWithCode(window.location.toString());
|
||||
else if (window.location.href.indexOf("/login") == -1)
|
||||
{
|
||||
this.oidcSecurityService.getIsAuthorized().subscribe((authorized: boolean) =>
|
||||
{
|
||||
this.isAuthenticated = authorized;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getAccount(): Account
|
||||
{
|
||||
if (!this.isAuthenticated)
|
||||
return null;
|
||||
return {email: this.user.email, username: this.user.username, picture: this.user.picture};
|
||||
// document.cookie = "Authenticated=false; expires=" + new Date(2147483647 * 1000).toUTCString();
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: 'app-logout',
|
||||
templateUrl: './logout.component.html',
|
||||
styleUrls: ['./logout.component.scss']
|
||||
selector: "app-logout",
|
||||
templateUrl: "./logout.component.html",
|
||||
styleUrls: ["./logout.component.scss"]
|
||||
})
|
||||
export class LogoutComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
export class LogoutComponent {}
|
||||
|
@ -21,7 +21,7 @@ export class AuthPipe implements PipeTransform
|
||||
const headers = new HttpHeaders({"Authorization": "Bearer " + token});
|
||||
const img = await this.http.get(uri, {headers, responseType: 'blob'}).toPromise();
|
||||
const reader = new FileReader();
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
reader.onloadend = () => resolve(reader.result as string);
|
||||
reader.readAsDataURL(img);
|
||||
});
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
UrlSegment,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
Router
|
||||
} from '@angular/router';
|
||||
import {Observable} from 'rxjs';
|
||||
@ -17,6 +16,7 @@ export class AuthGuard
|
||||
{
|
||||
public static guards: any[] = [];
|
||||
public static defaultPermissions: string[];
|
||||
public static permissionsObservable: Observable<string[]>;
|
||||
|
||||
static forPermissions(...permissions: string[])
|
||||
{
|
||||
@ -25,31 +25,31 @@ export class AuthGuard
|
||||
{
|
||||
constructor(private router: Router, private authManager: AuthService) {}
|
||||
|
||||
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
|
||||
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean>
|
||||
{
|
||||
if (!this.checkPermissions())
|
||||
if (!await this.checkPermissions())
|
||||
{
|
||||
this.router.navigate(["/unauthorized"]);
|
||||
await this.router.navigate(["/unauthorized"]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean
|
||||
async canLoad(route: Route, segments: UrlSegment[]): Promise<boolean>
|
||||
{
|
||||
if (!this.checkPermissions())
|
||||
if (!await this.checkPermissions())
|
||||
{
|
||||
this.router.navigate(["/unauthorized"]);
|
||||
await this.router.navigate(["/unauthorized"]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkPermissions(): boolean
|
||||
async checkPermissions(): Promise<boolean>
|
||||
{
|
||||
if (this.authManager.isAuthenticated)
|
||||
{
|
||||
let perms = this.authManager.user.permissions.split(",");
|
||||
const perms: string[] = this.authManager.account.permissions;
|
||||
for (let perm of permissions) {
|
||||
if (!perms.includes(perm))
|
||||
return false;
|
||||
@ -58,8 +58,11 @@ export class AuthGuard
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AuthGuard.defaultPermissions == undefined)
|
||||
await AuthGuard.permissionsObservable.toPromise()
|
||||
|
||||
for (let perm of permissions)
|
||||
if (AuthGuard.defaultPermissions?.includes(perm) === true)
|
||||
if (!AuthGuard.defaultPermissions.includes(perm))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Component, Input} from '@angular/core';
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { Episode } from "../../../models/resources/episode";
|
||||
import { Episode } from "../../models/resources/episode";
|
||||
import {HorizontalScroller} from "../../misc/horizontal-scroller";
|
||||
import {Page} from "../../../models/page";
|
||||
import {Page} from "../../models/page";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
|
||||
@Component({
|
||||
|
@ -25,12 +25,13 @@
|
||||
</mat-chip-list>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="this.studios.length > 0">
|
||||
<br/>
|
||||
<h4><b>Studios</b></h4>
|
||||
<br/>
|
||||
|
||||
<ng-container>
|
||||
<mat-form-field class="w-100 px-3" (click)="$event.stopPropagation();">
|
||||
<input type="text" matInput [matAutocomplete]="auto" [formControl]="studioForm">
|
||||
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"
|
||||
<mat-label>Studio</mat-label>
|
||||
<input type="text" matInput [matAutocomplete]="autoStudio" [formControl]="studioForm" placeholder="None">
|
||||
<mat-autocomplete autoActiveFirstOption #autoStudio="matAutocomplete"
|
||||
(optionSelected)="this.addFilter('studio', $event.option.value, false)"
|
||||
[displayWith]="this.nameGetter">
|
||||
<mat-option *ngIf="this.shouldDisplayNoneStudio()" [value]="null">None</mat-option>
|
||||
@ -40,6 +41,35 @@
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</ng-container>
|
||||
|
||||
<ng-container>
|
||||
<mat-form-field class="w-100 px-3" (click)="$event.stopPropagation();">
|
||||
<mat-label>People</mat-label>
|
||||
<mat-chip-list #peopleList>
|
||||
<mat-chip *ngFor="let people of this.filters.people"
|
||||
color="accent" selected
|
||||
removable="true"
|
||||
(removed)="this.addFilter('people', people)"
|
||||
(click)="this.addFilter('people', people)">
|
||||
{{people.name || people.slug}}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input #peopleInput
|
||||
[matAutocomplete]="autoPpl"
|
||||
[matChipInputFor]="peopleList"
|
||||
[formControl]="peopleForm"
|
||||
(matChipInputTokenEnd)="this.addFilter('people', {id: 0, slug: $event.value});
|
||||
$event.input.value = null;"/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete #autoPpl="matAutocomplete"
|
||||
(optionSelected)="this.addFilter('people', $event.option.value);
|
||||
peopleInput.value = null;">
|
||||
<mat-option *ngFor="let people of this.filteredPeople | async" [value]="people">
|
||||
{{people.name}}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #sortMenu="matMenu">
|
||||
|
@ -21,7 +21,7 @@ button
|
||||
.show
|
||||
{
|
||||
width: 27%;
|
||||
min-width: 120px;
|
||||
min-width: 100px;
|
||||
max-width: 168px;
|
||||
list-style: none;
|
||||
margin: .5em;
|
||||
@ -32,6 +32,7 @@ button
|
||||
@include media-breakpoint-up(sm)
|
||||
{
|
||||
width: 22%;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md)
|
||||
|
@ -2,18 +2,20 @@ import { Component, Input, OnInit } from "@angular/core";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params, Router } from "@angular/router";
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Genre } from "../../../models/resources/genre";
|
||||
import { LibraryItem } from "../../../models/resources/library-item";
|
||||
import { Page } from "../../../models/page";
|
||||
import { Genre } from "../../models/resources/genre";
|
||||
import { LibraryItem } from "../../models/resources/library-item";
|
||||
import { Page } from "../../models/page";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { IResource } from "../../../models/resources/resource";
|
||||
import { Show, ShowRole } from "../../../models/resources/show";
|
||||
import { Collection } from "../../../models/resources/collection";
|
||||
import { Studio } from "../../../models/resources/studio";
|
||||
import { People } from "../../models/resources/people";
|
||||
import { IResource } from "../../models/resources/resource";
|
||||
import { Show, ShowRole } from "../../models/resources/show";
|
||||
import { Collection } from "../../models/resources/collection";
|
||||
import { Studio } from "../../models/resources/studio";
|
||||
import { ItemsUtils } from "../../misc/items-utils";
|
||||
import { PeopleService, StudioService } from "../../services/api.service";
|
||||
import { PreLoaderService } from "../../services/pre-loader.service";
|
||||
import { Observable } from "rxjs"
|
||||
import { map, startWith, tap } from "rxjs/operators"
|
||||
import { catchError, filter, map, mergeAll } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: 'app-items-grid',
|
||||
@ -31,20 +33,25 @@ export class ItemsGridComponent implements OnInit
|
||||
sortKeys: string[] = ["title", "start year", "end year"]
|
||||
sortUp: boolean = true;
|
||||
|
||||
public static readonly showOnlyFilters: string[] = ["genres", "studio"]
|
||||
public static readonly showOnlyFilters: string[] = ["genres", "studio", "people"]
|
||||
public static readonly filters: string[] = [].concat(...ItemsGridComponent.showOnlyFilters)
|
||||
filters: {genres: Genre[], studio: Studio} = {genres: [], studio: null};
|
||||
filters: {genres: Genre[], studio: Studio, people: People[]} = {genres: [], studio: null, people: []};
|
||||
|
||||
genres: Genre[] = [];
|
||||
studios: Studio[] = [];
|
||||
|
||||
studioForm: FormControl = new FormControl();
|
||||
filteredStudios: Observable<Studio[]>;
|
||||
|
||||
peopleForm: FormControl = new FormControl();
|
||||
filteredPeople: Observable<People[]>;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private sanitizer: DomSanitizer,
|
||||
private loader: PreLoaderService,
|
||||
public client: HttpClient,
|
||||
private router: Router)
|
||||
private router: Router,
|
||||
private studioApi: StudioService,
|
||||
private peopleApi: PeopleService,
|
||||
public client: HttpClient)
|
||||
{
|
||||
this.route.data.subscribe((data) =>
|
||||
{
|
||||
@ -60,11 +67,6 @@ export class ItemsGridComponent implements OnInit
|
||||
this.genres = data;
|
||||
this.updateGenresFilterFromQuery(this.route.snapshot.queryParams);
|
||||
});
|
||||
this.loader.load<Studio>("/api/studios?limit=0").subscribe(data =>
|
||||
{
|
||||
this.studios = data;
|
||||
this.updateStudioFilterFromQuery(this.route.snapshot.queryParams);
|
||||
});
|
||||
}
|
||||
|
||||
updateGenresFilterFromQuery(query: Params)
|
||||
@ -82,17 +84,41 @@ export class ItemsGridComponent implements OnInit
|
||||
|
||||
updateStudioFilterFromQuery(query: Params)
|
||||
{
|
||||
this.filters.studio = this.studios.find(x => x.slug == query.studio
|
||||
|| x.slug == this.route.snapshot.params.slug);
|
||||
const slug: string = query.studio ?? this.route.snapshot.params.slug;
|
||||
|
||||
if (slug && this.filters.studio?.slug != slug)
|
||||
{
|
||||
this.filters.studio = {id: 0, slug: slug, name: slug};
|
||||
this.studioApi.get(slug).subscribe(x => this.filters.studio = x);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit()
|
||||
{
|
||||
this.filteredStudios = this.studioForm.valueChanges
|
||||
.pipe(
|
||||
map(x => x == null ? "" : x),
|
||||
filter(x => x),
|
||||
map(x => typeof x === "string" ? x : x.name),
|
||||
map(x => this.studios.filter(y => y.name.toLowerCase().indexOf(x.toLowerCase()) != -1))
|
||||
map(x => this.studioApi.search(x)),
|
||||
mergeAll(),
|
||||
catchError(x =>
|
||||
{
|
||||
console.log(x);
|
||||
return [];
|
||||
})
|
||||
);
|
||||
|
||||
this.filteredPeople = this.peopleForm.valueChanges
|
||||
.pipe(
|
||||
filter(x => x),
|
||||
map(x => typeof x === "string" ? x : x.name),
|
||||
map(x => this.peopleApi.search(x)),
|
||||
mergeAll(),
|
||||
catchError(x =>
|
||||
{
|
||||
console.log(x);
|
||||
return [];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@ -102,6 +128,7 @@ export class ItemsGridComponent implements OnInit
|
||||
}
|
||||
|
||||
// TODO add /people to the switch list.
|
||||
// TODO only load studios & people when the user open the menu or load them from the server when typing.
|
||||
|
||||
/*
|
||||
* /browse -> /api/items | /api/shows
|
||||
@ -141,14 +168,14 @@ export class ItemsGridComponent implements OnInit
|
||||
{
|
||||
if (isArray)
|
||||
{
|
||||
if (this.filters[category].includes(filter))
|
||||
if (this.filters[category].includes(filter) || this.filters[category].some(x => x.slug == filter.slug))
|
||||
this.filters[category].splice(this.filters[category].indexOf(filter), 1);
|
||||
else
|
||||
this.filters[category].push(filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.filters[category] == filter)
|
||||
if (this.filters[category] == filter || this.filters[category]?.slug == filter.slug)
|
||||
{
|
||||
if (!toggle)
|
||||
return;
|
||||
@ -184,6 +211,14 @@ export class ItemsGridComponent implements OnInit
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.filters.people.length == 1 && this.getFilterCount() == 1)
|
||||
{
|
||||
this.router.navigate(["people", this.filters.people[0].slug], {
|
||||
replaceUrl: true,
|
||||
queryParams: {sortBy: this.route.snapshot.queryParams.sortBy}
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.getFilterCount() == 0 || this.router.url != "/browse")
|
||||
{
|
||||
let params = {[category]: param}
|
||||
@ -191,6 +226,8 @@ export class ItemsGridComponent implements OnInit
|
||||
params.studio = this.route.snapshot.params.slug;
|
||||
if (this.router.url.startsWith("/genre") && category != "genres")
|
||||
params.genres = `${this.route.snapshot.params.slug}`;
|
||||
if (this.router.url.startsWith("/people") && category != "people")
|
||||
params.people = `${this.route.snapshot.params.slug}`;
|
||||
|
||||
this.router.navigate(["/browse"], {
|
||||
queryParams: params,
|
||||
|
@ -1,11 +1,11 @@
|
||||
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 {HorizontalScroller} from "../../misc/horizontal-scroller";
|
||||
import {Page} from "../../../models/page";
|
||||
import {Page} from "../../models/page";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Show, ShowRole} from "../../../models/resources/show";
|
||||
import {LibraryItem} from "../../../models/resources/library-item";
|
||||
import {Show, ShowRole} from "../../models/resources/show";
|
||||
import {LibraryItem} from "../../models/resources/library-item";
|
||||
import {ItemsUtils} from "../../misc/items-utils";
|
||||
|
||||
@Component({
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { MatButton } from "@angular/material/button";
|
||||
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 {Page} from "../../../models/page";
|
||||
import {Page} from "../../models/page";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
|
||||
@Component({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {Show} from "../../../models/resources/show";
|
||||
import {Page} from "../../../models/page";
|
||||
import {Show} from "../../models/resources/show";
|
||||
import {Page} from "../../models/page";
|
||||
|
||||
@Component({
|
||||
selector: 'app-shows-grid',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {ItemType, LibraryItem} from "../../models/resources/library-item";
|
||||
import {Show, ShowRole} from "../../models/resources/show";
|
||||
import {Collection} from "../../models/resources/collection";
|
||||
import {People} from "../../models/resources/people";
|
||||
import {ItemType, LibraryItem} from "../models/resources/library-item";
|
||||
import {Show, ShowRole} from "../models/resources/show";
|
||||
import {Collection} from "../models/resources/collection";
|
||||
import {People} from "../models/resources/people";
|
||||
|
||||
export class ItemsUtils
|
||||
{
|
||||
|
@ -3,4 +3,5 @@ export interface Account
|
||||
username: string;
|
||||
email: string;
|
||||
picture: string;
|
||||
permissions: string[];
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Collection} from "../../../models/resources/collection";
|
||||
import {Collection} from "../../models/resources/collection";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {Show, ShowRole} from "../../../models/resources/show";
|
||||
import {Page} from "../../../models/page";
|
||||
import {People} from "../../../models/resources/people";
|
||||
import {LibraryItem} from "../../../models/resources/library-item";
|
||||
import {Show, ShowRole} from "../../models/resources/show";
|
||||
import {Page} from "../../models/page";
|
||||
import {People} from "../../models/resources/people";
|
||||
import {LibraryItem} from "../../models/resources/library-item";
|
||||
import {ItemsUtils} from "../../misc/items-utils";
|
||||
|
||||
@Component({
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {Component, ElementRef, Inject, ViewChild} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Show} from "../../../models/resources/show";
|
||||
import {Genre} from "../../../models/resources/genre";
|
||||
import {Show} from "../../models/resources/show";
|
||||
import {Genre} from "../../models/resources/genre";
|
||||
import {MatChipInputEvent} from "@angular/material/chips";
|
||||
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
|
||||
import {Observable, of} from "rxjs";
|
||||
import {tap} from "rxjs/operators";
|
||||
import {Studio} from "../../../models/resources/studio";
|
||||
import {Provider} from "../../../models/provider";
|
||||
import {Studio} from "../../models/resources/studio";
|
||||
import {Provider} from "../../models/provider";
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {ShowGridComponent} from "../../components/show-grid/show-grid.component";
|
||||
|
||||
|
@ -2,7 +2,7 @@ import {Component, Injector, OnInit, ViewEncapsulation} from '@angular/core';
|
||||
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||
import {DomSanitizer, Title} from "@angular/platform-browser";
|
||||
import {ActivatedRoute, Event, NavigationCancel, NavigationEnd, NavigationStart, Router} from "@angular/router";
|
||||
import {Track, WatchItem} from "../../../models/watch-item";
|
||||
import {Track, WatchItem} from "../../models/watch-item";
|
||||
import {Location} from "@angular/common";
|
||||
import * as Hls from "hls.js"
|
||||
import {getPlaybackMethod, getWhatIsSupported, method, SupportList} from "../../../videoSupport/playbackMethodDetector";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { SearchResult } from "../../../models/search-result";
|
||||
import { SearchResult } from "../../models/search-result";
|
||||
import { Title } from "@angular/platform-browser";
|
||||
import {Page} from "../../../models/page";
|
||||
import {Page} from "../../models/page";
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
|
@ -2,15 +2,15 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import { Episode } from "../../../models/resources/episode";
|
||||
import { Show } from "../../../models/resources/show";
|
||||
import { Episode } from "../../models/resources/episode";
|
||||
import { Show } from "../../models/resources/show";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {TrailerDialogComponent} from "../trailer-dialog/trailer-dialog.component";
|
||||
import {MetadataEditComponent} from "../metadata-edit/metadata-edit.component";
|
||||
import {Season} from "../../../models/resources/season";
|
||||
import {Season} from "../../models/resources/season";
|
||||
import {EpisodeService, PeopleService, SeasonService} from "../../services/api.service";
|
||||
import {Page} from "../../../models/page";
|
||||
import {People} from "../../../models/resources/people";
|
||||
import {Page} from "../../models/page";
|
||||
import {People} from "../../models/resources/people";
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-details',
|
||||
|
@ -1,15 +1,16 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Observable} from "rxjs"
|
||||
import {Page} from "../../models/page";
|
||||
import {IResource} from "../../models/resources/resource";
|
||||
import {Library} from "../../models/resources/library";
|
||||
import {LibraryItem} from "../../models/resources/library-item";
|
||||
import {Page} from "../models/page";
|
||||
import {IResource} from "../models/resources/resource";
|
||||
import {Library} from "../models/resources/library";
|
||||
import {LibraryItem} from "../models/resources/library-item";
|
||||
import {map} from "rxjs/operators";
|
||||
import {Season} from "../../models/resources/season";
|
||||
import {Episode} from "../../models/resources/episode";
|
||||
import {People} from "../../models/resources/people";
|
||||
import {Show} from "../../models/resources/show";
|
||||
import {Season} from "../models/resources/season";
|
||||
import {Episode} from "../models/resources/episode";
|
||||
import {People} from "../models/resources/people";
|
||||
import {Show} from "../models/resources/show";
|
||||
import { Studio } from "../models/resources/studio";
|
||||
|
||||
export interface ApiArgs
|
||||
{
|
||||
@ -57,6 +58,11 @@ class CrudApi<T extends IResource>
|
||||
{
|
||||
return this.client.delete<T>(`/api/${this.route}/${item.slug}`);
|
||||
}
|
||||
|
||||
search(name: string): Observable<T[]>
|
||||
{
|
||||
return this.client.get<T[]>(`/api/search/${name}/${this.route}`);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
@ -155,3 +161,19 @@ export class ShowService extends CrudApi<Show>
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StudioService extends CrudApi<Studio>
|
||||
{
|
||||
constructor(client: HttpClient)
|
||||
{
|
||||
super(client, "studios");
|
||||
}
|
||||
|
||||
getForShow(show: string | number) : Observable<Studio>
|
||||
{
|
||||
return this.client.get<Studio>(`/api/show/${show}/studio}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@ import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {ActivatedRouteSnapshot, Resolve} from '@angular/router';
|
||||
import {Observable, EMPTY} from 'rxjs';
|
||||
import {catchError, map} from 'rxjs/operators';
|
||||
import {Page} from "../../models/page";
|
||||
import {IResource} from "../../models/resources/resource";
|
||||
import {Page} from "../models/page";
|
||||
import {IResource} from "../models/resources/resource";
|
||||
|
||||
type RouteMapper = (route: ActivatedRouteSnapshot, endpoint: string, queryParams: [string, string][]) => string;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Page } from "../../models/page";
|
||||
import { Page } from "../models/page";
|
||||
import { Observable, of } from "rxjs"
|
||||
import { map } from "rxjs/operators"
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { detect } from "detect-browser";
|
||||
import { Track, WatchItem } from "../models/watch-item";
|
||||
import { Track, WatchItem } from "../app/models/watch-item";
|
||||
|
||||
export enum method
|
||||
{
|
||||
|
@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="./" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Silent Renew</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
/* The parent window hosts the Angular application */
|
||||
var parent = window.parent;
|
||||
/* Send the id_token information to the oidc message handler */
|
||||
var event = new CustomEvent('oidc-silent-renew-message', { detail: window.location });
|
||||
parent.dispatchEvent(event);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user