chore: Angular 17 upgrade (#5082)

pull/5083/head
Jamie 2 months ago committed by GitHub
parent adfbc845a2
commit 617f26b332
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -82,22 +82,22 @@
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "ombi:build" "buildTarget": "ombi:build"
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "ombi:build:production" "buildTarget": "ombi:build:production"
}, },
"hmr": { "hmr": {
"hmr": true, "hmr": true,
"browserTarget": "ombi:build:hmr" "buildTarget": "ombi:build:hmr"
} }
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "ombi:build" "buildTarget": "ombi:build"
} }
}, },
"lint": { "lint": {

@ -1,70 +1,71 @@
{ {
"name": "ombi", "name": "ombi",
"version": "3.0.0", "version": "4.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --port 3578 --configuration hmr", "start": "ng serve --port 3578 --configuration hmr",
"build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build -c production", "build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build -c production",
"lint": "ng lint", "lint": "ng lint",
"docs:json": "compodoc -p ./tsconfig.json -e json -d .", "docs:json": "compodoc -p ./tsconfig.json -e json -d .",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006",
"chromatic": "chromatic --exit-zero-on-changes", "chromatic": "chromatic --exit-zero-on-changes",
"storybookbuild": "yarn build-storybook" "storybookbuild": "yarn build-storybook"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^15.2.10", "@angular/animations": "^17.1.3",
"@angular/cdk": "^14.2.7", "@angular/cdk": "16.2.14",
"@angular/common": "^15.2.10", "@angular/common": "^17.1.3",
"@angular/compiler": "^15.2.10", "@angular/compiler": "^17.1.3",
"@angular/core": "^15.2.10", "@angular/core": "^17.1.3",
"@angular/forms": "^15.2.10", "@angular/forms": "^17.1.3",
"@angular/material": "^14.2.7", "@angular/material": "^14.2.7",
"@angular/platform-browser": "^15.2.10", "@angular/platform-browser": "^17.1.3",
"@angular/platform-browser-dynamic": "^15.2.10", "@angular/platform-browser-dynamic": "^17.1.3",
"@angular/platform-server": "^15.2.10", "@angular/platform-server": "^17.1.3",
"@angular/router": "^15.2.10", "@angular/router": "^17.1.3",
"@angularclass/hmr": "^3.0.0", "@angularclass/hmr": "^3.0.0",
"@auth0/angular-jwt": "^5.0.2", "@auth0/angular-jwt": "^5.0.2",
"@fortawesome/fontawesome-free": "^6.5.0", "@fortawesome/fontawesome-free": "^6.4.2",
"@microsoft/signalr": "^6.0.23", "@microsoft/signalr": "^6.0.23",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^7.0.0",
"@ngxs/devtools-plugin": "3.8.2", "@ngxs/devtools-plugin": "3.8.1",
"@ngxs/store": "3.8.2", "@ngxs/store": "3.8.1",
"@types/jquery": "^3.5.28", "@types/jquery": "^3.5.23",
"@yellowspot/ng-truncate": "^2.0.0", "@yellowspot/ng-truncate": "^2.0.0",
"angularx-qrcode": "^15.0.0", "angularx-qrcode": "^16.0.0",
"bootstrap": "^4.2.1", "bootstrap": "^4.2.1",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"jquery": "3.7.1", "date-fns": "3.3.1",
"lodash": "^4.17.21", "jquery": "3.7.1",
"moment": "^2.30.1", "lodash": "^4.17.21",
"ng2-cookies": "^1.0.12", "lodash-es": "^4.17.21",
"ngx-clipboard": "^12.1.0", "ng2-cookies": "^1.0.12",
"ngx-infinite-scroll": "^9.0.0", "ngx-clipboard": "^16.0.0",
"ngx-moment": "^3.0.1", "ngx-date-fns": "^11.0.0",
"ngx-order-pipe": "^2.2.0", "ngx-infinite-scroll": "^17.0.0",
"popper.js": "^1.14.3", "popper.js": "^1.14.3",
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primeng": "^15.4.1", "primeng": "^17.6.0",
"rxjs": "^7.5.4", "rxjs": "^7.5.4",
"ts-md5": "^1.2.7", "ts-md5": "^1.2.7",
"zone.js": "~0.13.2" "zone.js": "0.14.3"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^15.0.2", "@angular-devkit/build-angular": "^17.1.3",
"@angular/cli": "^15.0.2", "@angular/cli": "^17.1.3",
"@angular/compiler-cli": "^15.0.4", "@angular/compiler-cli": "^17.1.3",
"@babel/core": "^7.18.9", "@babel/core": "^7.18.9",
"@compodoc/compodoc": "^1.1.19", "@compodoc/compodoc": "^1.1.19",
"@storybook/angular": "^6.5.9", "@storybook/angular": "7.6.14",
"chromatic": "^6.7.1", "@types/node": "^20.11.17",
"typescript": "~4.8.4" "chromatic": "^6.7.1",
}, "typescript": "5.2.2"
"optionalDependencies": { },
"protractor": "~5.4.0", "optionalDependencies": {
"ts-node": "~5.0.1", "protractor": "~5.4.0",
"tslint": "^5.12.0" "ts-node": "~5.0.1",
} "tslint": "^5.12.0"
}
} }

@ -1,12 +1,12 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { CanActivate } from "@angular/router";
import { AuthService } from "./auth.service"; import { AuthService } from "./auth.service";
import { StorageService } from "../shared/storage/storage-service"; import { StorageService } from "../shared/storage/storage-service";
@Injectable() @Injectable()
export class AuthGuard implements CanActivate { export class AuthGuard {
constructor(private auth: AuthService, private router: Router, constructor(private auth: AuthService, private router: Router,
private store: StorageService) { } private store: StorageService) { }

@ -14,7 +14,7 @@
<p id="detailed-request-requestedby-{{request.mediaId}}">{{'MediaDetails.RequestedBy' | translate}} {{request.username}}</p> <p id="detailed-request-requestedby-{{request.mediaId}}">{{'MediaDetails.RequestedBy' | translate}} {{request.username}}</p>
</div> </div>
<div class="col-12"> <div class="col-12">
<p id="detailed-request-date-{{request.mediaId}}">{{'MediaDetails.OnDate' | translate}} {{request.requestDate | amFromUtc | amLocal | amUserLocale | amDateFormat: 'l LT'}}</p> <p id="detailed-request-date-{{request.mediaId}}">{{'MediaDetails.OnDate' | translate}} {{request.requestDate | dfnsFormat: 'Ppp'}}</p>
</div> </div>
<div class="col-12"> <div class="col-12">
<p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p> <p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p>

@ -1,43 +1,42 @@
import { OmbiCommonModules } from "../modules"; import { OmbiCommonModules } from '../modules';
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from '@angular/core';
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from '@angular/platform-browser';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ImageService } from "../../services"; import { ImageService } from '../../services';
import { fadeInOutAnimation } from "app/animations/fadeinout"; import { fadeInOutAnimation } from 'app/animations/fadeinout';
@Component({ @Component({
standalone: true, standalone: true,
selector: 'ombi-image-background', selector: 'ombi-image-background',
templateUrl: './image-background.component.html', templateUrl: './image-background.component.html',
styleUrls: ['./image-background.component.scss'], styleUrls: ['./image-background.component.scss'],
imports: [...OmbiCommonModules, BrowserAnimationsModule], imports: [...OmbiCommonModules, BrowserAnimationsModule],
providers: [ ImageService ], providers: [ImageService],
animations: [ fadeInOutAnimation ], animations: [fadeInOutAnimation],
}) })
export class ImageBackgroundComponent implements OnInit, OnDestroy { export class ImageBackgroundComponent implements OnInit, OnDestroy {
public background: any;
public name: string;
private timer: any;
public background: any; constructor(private images: ImageService, private sanitizer: DomSanitizer) {}
public name: string;
private timer: NodeJS.Timeout;
constructor(private images: ImageService, private sanitizer: DomSanitizer) { } public ngOnDestroy(): void {
clearTimeout(this.timer);
}
public ngOnDestroy(): void { public ngOnInit(): void {
clearTimeout(this.timer); this.cycleBackground();
}
public ngOnInit(): void { this.timer = setInterval(() => {
this.cycleBackground(); this.cycleBackground();
}, 30000);
}
this.timer = setInterval(() => { private cycleBackground() {
this.cycleBackground(); this.images.getRandomBackgroundWithInfo().subscribe((x) => {
}, 30000); this.background = this.sanitizer.bypassSecurityTrustStyle('url(' + x.url + ')');
} this.name = x.name;
});
private cycleBackground() { }
this.images.getRandomBackgroundWithInfo().subscribe((x) => { }
this.background = this.sanitizer.bypassSecurityTrustStyle("url(" + x.url + ")");
this.name = x.name;
});
}
}

@ -1,4 +1,3 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { MomentModule } from "ngx-moment";
export const OmbiCommonModules = [ CommonModule, MomentModule ]; export const OmbiCommonModules = [CommonModule];

@ -1,4 +0,0 @@
$primary-colour: #df691a;
$primary-colour-outline: #ff761b;
$bg-colour: #333333;
$bg-colour-disabled: #252424;

@ -6,8 +6,7 @@ import { CustomPageService, NotificationService } from "../services";
@Component({ @Component({
templateUrl: "./custompage.component.html", templateUrl: "./custompage.component.html",
styleUrls: ["./custompage.component.scss"], })
})
export class CustomPageComponent implements OnInit { export class CustomPageComponent implements OnInit {
public form: UntypedFormGroup; public form: UntypedFormGroup;

@ -1,43 +1,46 @@
<div class="small-middle-container"> <div class="small-middle-container">
<div class="section"> <div class="section">
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{'Discovery.Genres' | translate}}</h2> <h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select> <genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
</div> </div>
<div class="section"> <div class="section">
<h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2> <h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
<div> <div>
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list> <ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
</div> </div>
</div> </div>
<div class="section" [hidden]="!showSeasonal">
<h2>{{'Discovery.SeasonalTab' | translate}}</h2>
<div>
<carousel-list [id]="'seasonal'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Seasonal" (movieCount)="setSeasonalMovieCount($event)"></carousel-list>
</div>
</div>
<div class="section"> <div class="section" [hidden]="!showSeasonal">
<h2>{{'Discovery.PopularTab' | translate}}</h2> <h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
<div> <div>
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list> <carousel-list
</div> [id]="'seasonal'"
[isAdmin]="isAdmin"
[discoverType]="DiscoverType.Seasonal"
(movieCount)="setSeasonalMovieCount($event)"
></carousel-list>
</div>
</div>
</div> <div class="section">
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
<div>
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
</div>
</div>
<div class="section"> <div class="section">
<h2>{{'Discovery.TrendingTab' | translate}}</h2> <h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
<div > <div>
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list> <carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
</div> </div>
</div>
</div> <div class="section">
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
<div class="section"> <div>
<h2>{{'Discovery.UpcomingTab' | translate}}</h2> <carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
<div> </div>
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list> </div>
</div> </div>
</div>
</div>

@ -1,28 +1,42 @@
<mat-card class="issue-card" *ngIf="!deleted"> <mat-card class="issue-card" *ngIf="!deleted">
<mat-card-header> <mat-card-header>
<mat-card-title>{{issue.subject}}</mat-card-title> <mat-card-title>{{ issue.subject }}</mat-card-title>
<mat-card-subtitle>{{'Issues.UserOnDate' | translate: { user: issue.userReported?.userName, date: issue.createdDate | amLocal | amUserLocale | amDateFormat: 'LL' } }}</mat-card-subtitle> <mat-card-subtitle>{{
<mat-card-subtitle>{{issue.issueCategory.value}}</mat-card-subtitle> 'Issues.UserOnDate' | translate: { user: issue.userReported?.userName, date: issue.createdDate | dfnsFormat: 'PP' }
</mat-card-header> }}</mat-card-subtitle>
<mat-card-content> <mat-card-subtitle>{{ issue.issueCategory.value }}</mat-card-subtitle>
<p> </mat-card-header>
{{issue.description}} <mat-card-content>
</p> <p>
</mat-card-content> {{ issue.description }}
<mat-card-actions> </p>
<button mat-raised-button (click)="openChat(issue)" color="accent"><i class="far fa-comments"></i> {{'Issues.Chat' | translate }}</button> </mat-card-content>
<div *ngIf="isAdmin && settings;then content else empty"></div> <mat-card-actions>
<ng-template #content> <button mat-raised-button (click)="openChat(issue)" color="accent"><i class="far fa-comments"></i> {{ 'Issues.Chat' | translate }}</button>
<button mat-raised-button color="accent" <div *ngIf="isAdmin && settings; then content; else empty"></div>
*ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress" <ng-template #content>
(click)="inProgress(issue)">{{'Issues.MarkInProgress' | translate }}</button> <button
mat-raised-button
color="accent"
*ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress"
(click)="inProgress(issue)"
>
{{ 'Issues.MarkInProgress' | translate }}
</button>
<button mat-raised-button color="accent" <button
*ngIf="issue.status === IssueStatus.Pending && !settings.enableInProgress || issue.status == IssueStatus.InProgress" mat-raised-button
(click)="resolve(issue)">{{'Issues.MarkResolved' | translate}}</button> color="accent"
*ngIf="(issue.status === IssueStatus.Pending && !settings.enableInProgress) || issue.status == IssueStatus.InProgress"
(click)="resolve(issue)"
>
{{ 'Issues.MarkResolved' | translate }}
</button>
<button mat-raised-button color="warn" (click)="delete(issue)"><i class="far fa-times-circle"></i> {{'Issues.Delete' | translate}}</button></ng-template> <button mat-raised-button color="warn" (click)="delete(issue)">
<ng-template #empty></ng-template> <i class="far fa-times-circle"></i> {{ 'Issues.Delete' | translate }}
</mat-card-actions> </button></ng-template
>
<ng-template #empty></ng-template>
</mat-card-actions>
</mat-card> </mat-card>

@ -1,95 +1,124 @@
<div *ngIf="issue"> <div *ngIf="issue">
<div class="row issue-details"> <div class="row issue-details">
<div class="myBg backdrop" [style.background-image]="backgroundPath"></div> <div class="myBg backdrop" [style.background-image]="backgroundPath"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div> <div class="tint" style="background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.6) 100%)"></div>
<h1>{{issue.title}} </h1> <h1>{{ issue.title }}</h1>
<div class="col-md-6"> <div class="col-md-6">
<img class="img-responsive poster" src="{{posterPath}}" alt="poster"> <img class="img-responsive poster" src="{{ posterPath }}" alt="poster" />
<div class="issue-status"> <div class="issue-status">
<span *ngIf="issue.status === IssueStatus.Pending" id="pendingLabel" class="label label-warning">{{IssueStatus[issue.status]}}</span> <span *ngIf="issue.status === IssueStatus.Pending" id="pendingLabel" class="label label-warning">{{
<span *ngIf="issue.status === IssueStatus.InProgress" id="inprogressLabel" class="label label-info">{{IssueStatus[issue.status]}}</span> IssueStatus[issue.status]
<span *ngIf="issue.status === IssueStatus.Resolved" id="resolvedLabel" class="label label-success">{{IssueStatus[issue.status]}}</span> }}</span>
</div> <span *ngIf="issue.status === IssueStatus.InProgress" id="inprogressLabel" class="label label-info">{{
<span class="label label-success">{{issue.issueCategory.value}}</span> IssueStatus[issue.status]
<br> }}</span>
<span class="reported-by"> <span *ngIf="issue.status === IssueStatus.Resolved" id="resolvedLabel" class="label label-success">{{
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3> IssueStatus[issue.status]
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}:</h3> }}</span>
</span> </div>
<span class="reported-user"> <span class="label label-success">{{ issue.issueCategory.value }}</span>
<h3 *ngIf="issue.userReported?.alias">{{issue.userReported.alias}}</h3> <br />
<h3 *ngIf="!issue.userReported?.alias">{{issue.userReported.userName}}</h3> <span class="reported-by">
</span> <h3 *ngIf="issue.userReported?.alias">{{ 'Issues.ReportedBy' | translate }}:</h3>
<br> <h3 *ngIf="!issue.userReported?.alias">{{ 'Issues.ReportedBy' | translate }}:</h3>
<span class="subject-category"><h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}:</h3></span> </span>
<span class="subject"><h3 *ngIf="issue.subject">{{issue.subject}}</h3></span> <span class="reported-user">
<br> <h3 *ngIf="issue.userReported?.alias">{{ issue.userReported.alias }}</h3>
<div class="form-group"> <h3 *ngIf="!issue.userReported?.alias">{{ issue.userReported.userName }}</h3>
<label for="description" class="control-label" [translate]="'Issues.Description'"></label> </span>
<div> <br />
<textarea class="form-control-custom form-control" disabled="disabled" [(ngModel)]="issue.description" rows="5" type="text"></textarea> <span class="subject-category"
</div> ><h3 *ngIf="issue.subject">{{ 'Issues.Subject' | translate }}:</h3></span
</div> >
<span class="subject"
><h3 *ngIf="issue.subject">{{ issue.subject }}</h3></span
>
<br />
<div class="form-group">
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
<div>
<textarea
class="form-control-custom form-control"
disabled="disabled"
[(ngModel)]="issue.description"
rows="5"
type="text"
></textarea>
</div>
</div>
</div>
<div class="row chat-window col-xs-7 col-md-5" id="chat_window_1" style="margin-left: 10px">
<div class="col-xs-12 col-md-12">
<div class="panel panel-default">
<div class="panel-heading top-bar">
<div class="col-md-8 col-xs-8">
<h3 class="panel-title"><span class="glyphicon glyphicon-comment"></span> {{ 'Issues.Comments' | translate }}</h3>
</div>
</div>
<div *ngIf="comments" class="panel-body msg_container_base">
<div *ngIf="comments.length <= 0" class="row msg_container base_receive">
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<p [translate]="'Issues.NoComments'"></p>
</div>
</div>
</div>
</div> <div
*ngFor="let comment of comments"
class="row msg_container"
[ngClass]="{ base_sent: comment.adminComment, base_receive: !comment.adminComment }"
>
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<i
*ngIf="isAdmin"
style="float: right"
class="fas fa-times"
aria-hidden="true"
(click)="deleteComment(comment.id)"
></i>
<p>{{ comment.comment }}</p>
<time>{{ comment.username }} • {{ comment.date | dfnsFormat: 'Ppp' }}</time>
</div>
</div>
</div>
</div>
<div class="panel-footer">
<div class="input-group">
<input
id="btn-input"
type="text"
class="form-control input-sm chat_input"
[(ngModel)]="newComment.comment"
[attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate"
/>
<span class="input-group-btn">
<button
class="btn btn-primary btn-sm"
id="btn-chat"
(click)="addComment()"
[translate]="'Issues.SendMessageButton'"
></button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="row chat-window col-xs-7 col-md-5" id="chat_window_1" style="margin-left:10px;"> <div *ngIf="isAdmin && settings">
<div class="col-xs-12 col-md-12"> <div *ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress">
<div class="panel panel-default"> <button class="btn btn-primary btn-sm bottom-btn" (click)="inProgress()">{{ 'Issues.MarkInProgress' | translate }}</button>
<div class="panel-heading top-bar"> </div>
<div class="col-md-8 col-xs-8"> <div *ngIf="(issue.status === IssueStatus.Pending && !settings.enableInProgress) || issue.status == IssueStatus.InProgress">
<h3 class="panel-title"> <button class="btn btn-primary btn-sm bottom-btn" (click)="resolve()" [translate]="'Issues.MarkResolved'"></button>
<span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}} </div>
</h3> </div>
</div> </div>
</div> </div>
</div>
<div *ngIf="comments" class="panel-body msg_container_base">
<div *ngIf="comments.length <= 0" class="row msg_container base_receive">
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent">
<p [translate]="'Issues.NoComments'"></p>
</div>
</div>
</div>
<div *ngFor="let comment of comments" class="row msg_container" [ngClass]="{'base_sent': comment.adminComment, 'base_receive': !comment.adminComment}">
<div class="col-md-10 col-xs-10">
<div class="messages msg_sent"> <i *ngIf="isAdmin" style="float:right;" class="fas fa-times" aria-hidden="true" (click)="deleteComment(comment.id)"></i>
<p>{{comment.comment}}</p>
<time>{{comment.username}} • {{comment.date | amFromUtc | amLocal | amUserLocale | amDateFormat: 'l LT'}}</time>
</div>
</div>
</div>
</div>
<div class="panel-footer">
<div class="input-group">
<input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate" />
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div *ngIf="isAdmin && settings">
<div *ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress">
<button class="btn btn-primary btn-sm bottom-btn" (click)="inProgress()">{{'Issues.MarkInProgress' | translate }}</button>
</div>
<div *ngIf="issue.status === IssueStatus.Pending && !settings.enableInProgress || issue.status == IssueStatus.InProgress">
<button class="btn btn-primary btn-sm bottom-btn" (click)="resolve()" [translate]="'Issues.MarkResolved'"></button>
</div>
</div>
</div>
</div>
</div>

@ -1,47 +1,34 @@
import { NgModule } from "@angular/core"; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from '@angular/router';
// import { NbChatModule, NbThemeModule } from '@nebular/theme'; // import { NbChatModule, NbThemeModule } from '@nebular/theme';
import { OrderModule } from "ngx-order-pipe"; import { AuthGuard } from '../auth/auth.guard';
import { AuthGuard } from "../auth/auth.guard"; import { SharedModule } from '../shared/shared.module';
import { SharedModule } from "../shared/shared.module"; import { IssueDetailsComponent } from './issueDetails.component';
import { IssuesComponent } from './issues.component';
import { IssuesTableComponent } from './issuestable.component';
import { IssuesDetailsComponent } from './components/details/details.component';
import { IssueDetailsComponent } from "./issueDetails.component"; import { PipeModule } from '../pipes/pipe.module';
import { IssuesComponent } from "./issues.component";
import { IssuesTableComponent } from "./issuestable.component";
import { IssuesDetailsComponent } from "./components/details/details.component";
import { PipeModule } from "../pipes/pipe.module"; import * as fromComponents from './components';
import * as fromComponents from "./components";
const routes: Routes = [ const routes: Routes = [
{ path: "", component: IssuesComponent, canActivate: [AuthGuard] }, { path: '', component: IssuesComponent, canActivate: [AuthGuard] },
{ path: ":providerId", component: IssuesDetailsComponent, canActivate: [AuthGuard] }, { path: ':providerId', component: IssuesDetailsComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild(routes), RouterModule.forChild(routes),
OrderModule, PipeModule,
PipeModule, SharedModule,
SharedModule, // NbChatModule,
// NbChatModule, ],
], declarations: [IssuesComponent, IssueDetailsComponent, IssuesTableComponent, ...fromComponents.components],
declarations: [ exports: [RouterModule],
IssuesComponent, providers: [...fromComponents.providers],
IssueDetailsComponent,
IssuesTableComponent,
...fromComponents.components
],
exports: [
RouterModule,
],
providers: [
...fromComponents.providers
],
}) })
export class IssuesModule { } export class IssuesModule {}

@ -1,300 +1,408 @@
<div *ngIf="!movie" class="justify-content-md-center top-spacing loading-spinner"> <div *ngIf="!movie" class="justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner> <mat-spinner [color]="'accent'"></mat-spinner>
</div> </div>
<div *ngIf="movie" class="main-content-container"> <div *ngIf="movie" class="main-content-container">
<top-banner
[background]="movie.background"
[available]="movie.available"
[title]="movie.title"
[releaseDate]="movie.releaseDate"
[tagline]="movie.tagline"
></top-banner>
<div class="social-icons-container">
<social-icons
[homepage]="movie.homepage"
[theMoviedbId]="movie.id"
[hasTrailer]="movie.videos?.results?.length > 0"
[imdbId]="movie.imdbId"
[twitter]="movie.externalIds?.twitterId"
[facebook]="movie.externalIds?.facebookId"
[instagram]="movie.externalIds?.instagramId"
[available]="movie.available"
[isAdmin]="isAdmin"
[canShowAdvanced]="showAdvanced && movieRequest"
[type]="requestType"
[has4KRequest]="movie.has4KRequest"
(openTrailer)="openDialog()"
(onAdvancedOptions)="openAdvancedOptions()"
(onReProcessRequest)="reProcessRequest(false)"
(onReProcess4KRequest)="reProcessRequest(true)"
>
</social-icons>
</div>
<section id="info-wrapper">
<div class="small-middle-container">
<div class="row justify-content-center justify-content-sm-start header-container">
<div class="details-poster-container">
<media-poster [posterPath]="movie.posterPath"></media-poster>
</div>
<top-banner [background]="movie.background" [available]="movie.available" [title]="movie.title" <!--Next to poster-->
[releaseDate]="movie.releaseDate" [tagline]="movie.tagline"></top-banner> <div class="details-button-container">
<div class="social-icons-container"> <div class="col-12 media-row">
<span *ngIf="movie.available || movie.available4K">
<a
id="viewOnPlexButton"
*ngIf="movie.plexUrl"
href="{{ movie.plexUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn plex"
>
{{ 'Search.ViewOnPlex' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
<a
id="viewOnEmbyButton"
*ngIf="movie.embyUrl"
href="{{ movie.embyUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn emby"
>
{{ 'Search.ViewOnEmby' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
<a
id="viewOnJellyfinButton"
*ngIf="movie.jellyfinUrl"
href="{{ movie.jellyfinUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn jellyfin"
>
{{ 'Search.ViewOnJellyfin' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
</span>
<!-- Regular Movie Status -->
<button
mat-raised-button
class="btn-green btn-spacing"
id="availableBtn"
*ngIf="movie.available && !movie.plexUrl && !movie.embyUrl && !movie.jellyfinUrl"
>
{{ 'Common.Available' | translate }}
</button>
<span *ngIf="!movie.available">
<span *ngIf="movie.requested || movie.approved; then requestedBtn; else notRequestedBtn"></span>
<ng-template #requestedBtn>
<button
id="requestedBtn"
mat-raised-button
*ngIf="!hasRequest || (hasRequest && movieRequest && !movieRequest.denied)"
class="btn-spacing"
color="warn"
[disabled]
>
<i class="fas fa-check"></i>
{{ 'Common.Requested' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn>
<button
*ngIf="!movie.requested"
id="requestBtn"
mat-raised-button
class="btn-spacing"
color="primary"
(click)="request(false)"
>
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{ 'Common.Request' | translate }}
</button>
</ng-template>
</span>
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id" <!-- 4k Status -->
[hasTrailer]="movie.videos?.results?.length > 0" [imdbId]="movie.imdbId" <span *ngIf="is4KEnabled">
[twitter]="movie.externalIds?.twitterId" [facebook]="movie.externalIds?.facebookId" <span *permission="roleName4k">
[instagram]="movie.externalIds?.instagramId" [available]="movie.available" [isAdmin]="isAdmin" <button mat-raised-button class="btn-green btn-spacing" id="availableBtn4k" *ngIf="movie.available4K">
[canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" [has4KRequest]="movie.has4KRequest" {{ 'Common.Available4K' | translate }}
(openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()" </button>
(onReProcessRequest)="reProcessRequest(false)" (onReProcess4KRequest)="reProcessRequest(true)">
</social-icons>
</div> <span *ngIf="!movie.available4K">
<section id="info-wrapper"> <span *ngIf="movie.has4KRequest || movie.approved4K; then requestedBtn4K; else notRequestedBtn4K"></span>
<div class="small-middle-container"> <ng-template #requestedBtn4K>
<button
id="requestedBtn4K"
mat-raised-button
*ngIf="movieRequest && !movieRequest.denied4K"
class="btn-spacing"
color="warn"
[disabled]
>
<i class="fas fa-check"></i>
{{ 'Common.Requested4K' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn4K>
<button
*ngIf="!movie.has4KRequest"
id="requestBtn4k"
mat-raised-button
class="btn-spacing"
color="primary"
(click)="request(true)"
>
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{ 'Common.Request4K' | translate }}
</button>
</ng-template>
</span>
</span>
</span>
<div class="row justify-content-center justify-content-sm-start header-container"> <span *ngIf="movieRequest?.showSubscribe">
<div class="details-poster-container"> <button *ngIf="!movieRequest?.subscribed" (click)="notify()" id="notifyBtn" mat-raised-button class="btn-spacing">
<media-poster [posterPath]=movie.posterPath></media-poster> <i class="fas fa-bell"></i> {{ 'Requests.Notify' | translate }}
</div> </button>
<button *ngIf="movieRequest?.subscribed" (click)="unNotify()" id="unnotifyBtn" mat-raised-button class="btn-spacing">
<i class="fas fa-bell-slash"></i> {{ 'Requests.RemoveNotification' | translate }}
</button>
</span>
<!--Next to poster--> <span *ngIf="isAdmin && hasRequest">
<div class="details-button-container"> <button
<div class="col-12 media-row"> id="approveBtn"
<span *ngIf="movie.available || movie.available4K"> *ngIf="!movie.approved && movie.requested"
<a id="viewOnPlexButton" *ngIf="movie.plexUrl" href="{{movie.plexUrl}}" mat-raised-button (click)="approve(false)"
target="_blank" class="btn-spacing viewon-btn plex"> mat-raised-button
{{'Search.ViewOnPlex' | translate}} class="btn-spacing"
<i class="far fa-play-circle fa-2x"></i> color="accent"
</a> >
<a id="viewOnEmbyButton" *ngIf="movie.embyUrl" href="{{movie.embyUrl}}" mat-raised-button <i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}
target="_blank" class="btn-spacing viewon-btn emby"> </button>
{{'Search.ViewOnEmby' | translate}} <button
<i class="far fa-play-circle fa-2x"></i> id="markAvailableBtn"
</a> *ngIf="!movie.available && movie.requested"
<a id="viewOnJellyfinButton" *ngIf="movie.jellyfinUrl" href="{{movie.jellyfinUrl}}" (click)="markAvailable(false)"
mat-raised-button target="_blank" class="btn-spacing viewon-btn jellyfin"> mat-raised-button
{{'Search.ViewOnJellyfin' | translate}} class="btn-spacing"
<i class="far fa-play-circle fa-2x"></i> color="accent"
</a> >
</span> <i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
<!-- Regular Movie Status --> </button>
<button mat-raised-button class="btn-green btn-spacing" id="availableBtn" <button
*ngIf="movie.available && !movie.plexUrl && !movie.embyUrl && !movie.jellyfinUrl"> {{ id="markUnavailableBtn"
'Common.Available' | translate }}</button> *ngIf="movie.available && movie.requested"
<span *ngIf="!movie.available"> (click)="markUnavailable(false)"
<span mat-raised-button
*ngIf="movie.requested || movie.approved; then requestedBtn else notRequestedBtn"></span> class="btn-spacing"
<ng-template #requestedBtn> color="accent"
<button id="requestedBtn" mat-raised-button >
*ngIf="!hasRequest || hasRequest && movieRequest && !movieRequest.denied" <i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
class="btn-spacing" color="warn" [disabled]> </button>
<i class="fas fa-check"></i>
{{ 'Common.Requested' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn>
<button *ngIf="!movie.requested" id="requestBtn" mat-raised-button class="btn-spacing"
color="primary" (click)="request(false)">
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{'Common.Request' | translate }}
</button>
</ng-template>
</span>
<!-- 4k Status --> <!-- 4k -->
<span *ngIf="is4KEnabled"> <span *ngIf="is4KEnabled">
<span *permission="roleName4k"> <span *permission="roleName4k">
<button mat-raised-button class="btn-green btn-spacing" id="availableBtn4k" <button
*ngIf="movie.available4K"> {{ id="approve4kBtn"
'Common.Available4K' | translate }} *ngIf="!movie.approved4K && movie.has4KRequest"
</button> (click)="approve(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-plus"></i> {{ 'Common.Approve4K' | translate }}
</button>
<button
id="markAvailable4kBtn"
*ngIf="!movie.available4K && movie.has4KRequest"
(click)="markAvailable(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable4K' | translate }}
</button>
<button
id="markUnavailable4kBtn"
*ngIf="movie.available4K"
(click)="markUnavailable(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable4K' | translate }}
</button>
</span>
</span>
<span *ngIf="!movie.available4K"> <button
<span id="denyBtn"
*ngIf="movie.has4KRequest || movie.approved4K; then requestedBtn4K else notRequestedBtn4K"></span> *ngIf="!movieRequest.denied && movie.requested"
<ng-template #requestedBtn4K> mat-raised-button
<button id="requestedBtn4K" mat-raised-button class="btn-spacing"
*ngIf="movieRequest && !movieRequest.denied4K" class="btn-spacing" color="warn"
color="warn" [disabled]> (click)="deny(false)"
<i class="fas fa-check"></i> >
{{ 'Common.Requested4K' | translate }} <i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}
</button> </button>
</ng-template>
<ng-template #notRequestedBtn4K>
<button *ngIf="!movie.has4KRequest" id="requestBtn4k" mat-raised-button
class="btn-spacing" color="primary" (click)="request(true)">
<i *ngIf="movie.requestProcessing"
class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed"
class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing"
class="fas fa-check"></i>
{{'Common.Request4K' | translate }}
</button>
</ng-template>
</span>
</span>
</span>
<span *ngIf="movieRequest?.showSubscribe"> <button
<button *ngIf="!movieRequest?.subscribed" (click)="notify()" id="notifyBtn" id="deniedButton"
mat-raised-button class="btn-spacing"> <i class="fas fa-bell"></i> *ngIf="movieRequest && movieRequest.denied"
{{ 'Requests.Notify' | translate }}</button> [matTooltip]="movieRequest.deniedReason"
<button *ngIf="movieRequest?.subscribed" (click)="unNotify()" id="unnotifyBtn" mat-raised-button
mat-raised-button class="btn-spacing"> <i class="fas fa-bell-slash"></i> class="btn-spacing"
{{ 'Requests.RemoveNotification' | translate }}</button> color="warn"
</span> >
<i class="fas fa-times"></i> {{ 'MediaDetails.Denied' | translate }}
</button>
</span>
<span *ngIf="isAdmin && hasRequest"> <button id="reportIssueBtn" mat-raised-button class="btn-spacing" color="danger" (click)="issue()" *ngIf="issuesEnabled">
<button id="approveBtn" *ngIf="!movie.approved && movie.requested" (click)="approve(false)" <i class="fas fa-exclamation"></i> {{ 'Requests.ReportIssue' | translate }}
mat-raised-button class="btn-spacing" color="accent"> </button>
<i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }} <button
</button> id="viewCollectionBtn"
<button id="markAvailableBtn" *ngIf="!movie.available && movie.requested" *ngIf="movie.belongsToCollection"
(click)="markAvailable(false)" mat-raised-button class="btn-spacing" color="accent"> [routerLink]="'/discover/collection/' + movie.belongsToCollection.id"
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }} mat-raised-button
</button> class="btn-spacing"
<button id="markUnavailableBtn" *ngIf="movie.available && movie.requested" >
(click)="markUnavailable(false)" mat-raised-button class="btn-spacing" color="accent"> <i class="fas fa-list"></i> {{ 'MediaDetails.ViewCollection' | translate }}
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }} </button>
</button> </div>
</div>
</div>
<!-- 4k --> <div class="row">
<span *ngIf="is4KEnabled"> <div class="col-12 col-md-2">
<span *permission="roleName4k"> <mat-card class="mat-elevation-z8">
<button id="approve4kBtn" *ngIf="!movie.approved4K && movie.has4KRequest" <mat-card-content>
(click)="approve(true)" mat-raised-button class="btn-spacing" color="accent"> <movie-information-panel
<i class="fas fa-plus"></i> {{ 'Common.Approve4K' | translate }} [movie]="movie"
</button> [request]="movieRequest"
<button id="markAvailable4kBtn" *ngIf="!movie.available4K && movie.has4KRequest" [advancedOptions]="showAdvanced"
(click)="markAvailable(true)" mat-raised-button class="btn-spacing" ></movie-information-panel>
color="accent"> </mat-card-content>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable4K' | translate }} </mat-card>
</button> </div>
<button id="markUnavailable4kBtn" *ngIf="movie.available4K"
(click)="markUnavailable(true)" mat-raised-button class="btn-spacing"
color="accent">
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable4K' | translate }}
</button>
</span>
</span>
<button id="denyBtn" *ngIf="!movieRequest.denied && movie.requested" mat-raised-button <div class="col-12 col-md-10">
class="btn-spacing" color="warn" (click)="deny(false)"> <div class="row">
<i class="fas fa-times"></i> {{'Requests.Deny' | translate }} <div class="col-12">
</button> <mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
{{ movie.overview }}
</mat-card-content>
</mat-card>
</div>
</div>
<button id="deniedButton" *ngIf="movieRequest && movieRequest.denied" <div class="row">
[matTooltip]="movieRequest.deniedReason" mat-raised-button class="btn-spacing" <div class="col-12">
color="warn"> <cast-carousel [cast]="movie.credits.cast"></cast-carousel>
<i class="fas fa-times"></i> {{'MediaDetails.Denied' | translate }} </div>
</button> </div>
</span>
<button id="reportIssueBtn" mat-raised-button class="btn-spacing" color="danger" <div class="row">
(click)="issue()" *ngIf="issuesEnabled"> <div class="col-12">
<i class="fas fa-exclamation"></i> {{'Requests.ReportIssue' | translate }} <crew-carousel [crew]="movie.credits.crew"></crew-carousel>
</button> </div>
<button id="viewCollectionBtn" *ngIf="movie.belongsToCollection" </div>
[routerLink]="'/discover/collection/' + movie.belongsToCollection.id" mat-raised-button
class="btn-spacing">
<i class="fas fa-list"></i> {{'MediaDetails.ViewCollection' | translate}}
</button>
</div>
</div>
</div>
<div class="row"> <!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
<div class="col-12 col-md-2">
<mat-card class="mat-elevation-z8">
<mat-card-content>
<movie-information-panel [movie]="movie" [request]="movieRequest"
[advancedOptions]="showAdvanced"></movie-information-panel>
</mat-card-content>
</mat-card>
</div>
<div class="col-12 col-md-10">
<div class="row">
<div class="col-12">
<mat-card class=" mat-elevation-z8 spacing-below">
<mat-card-content>
{{movie.overview}}
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row">
<div class="col-12">
<cast-carousel [cast]="movie.credits.cast"></cast-carousel>
</div>
</div>
<div class="row">
<div class="col-12">
<crew-carousel [crew]="movie.credits.crew"></crew-carousel>
</div>
</div>
<!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
<div class="col-md-6" *ngFor="let video of movie.videos?.results"> <div class="col-md-6" *ngFor="let video of movie.videos?.results">
<iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div> </div>
</div> --> </div> -->
<div class="row" *ngIf="movie.videos?.results?.length > 0"> <div class="row" *ngIf="movie.videos?.results?.length > 0">
<div class="col-12"> <div class="col-12">
<mat-card class="mat-elevation-z8"> <mat-card class="mat-elevation-z8">
<mat-card-header>{{'MediaDetails.Trailers' | translate}}</mat-card-header> <mat-card-header>{{ 'MediaDetails.Trailers' | translate }}</mat-card-header>
<mat-card-content> <mat-card-content>
<p-carousel class="no-indicator" [numVisible]="2" [numScroll]="10" [page]="0" <p-carousel class="no-indicator" [numVisible]="2" [numScroll]="10" [page]="0" [value]="movie.videos?.results">
[value]="movie.videos?.results"> <ng-template let-result pTemplate="item">
<ng-template let-result pTemplate="item"> <iframe
<iframe width="98%" height="315px" width="98%"
[src]="'https://www.youtube.com/embed/' + result.key | safe" height="315px"
frameborder="0" [src]="'https://www.youtube.com/embed/' + result.key | safe"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" frameborder="0"
allowfullscreen></iframe> allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
</ng-template> allowfullscreen
</p-carousel> ></iframe>
</mat-card-content> </ng-template>
</mat-card> </p-carousel>
</div> </mat-card-content>
</div> </mat-card>
</div>
<div class="row"> </div>
<div class="col-12">
<div class="issuesPanel">
<issues-panel [providerId]="movie.imdbId" [isAdmin]="isAdmin"></issues-panel>
</div>
<mat-accordion class=" mat-elevation-z8 spacing-below ">
<mat-expansion-panel *ngIf="movie.recommendations?.results?.length> 0">
<mat-expansion-panel-header>
<mat-panel-title>
{{'MediaDetails.RecommendationsTitle' | translate}}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer "> <div class="row">
<div class="col-12">
<div class="issuesPanel">
<issues-panel [providerId]="movie.imdbId" [isAdmin]="isAdmin"></issues-panel>
</div>
<div class="col-md-2" *ngFor="let r of movie.recommendations?.results"> <mat-accordion class="mat-elevation-z8 spacing-below">
<div class="sidebar affixable affix-top preview-poster"> <mat-expansion-panel *ngIf="movie.recommendations?.results?.length > 0">
<div class="poster"> <mat-expansion-panel-header>
<a [routerLink]="'/details/movie/'+r.id"> <mat-panel-title>
<ombi-image class="real grow" matTooltip="{{r.title}}" {{ 'MediaDetails.RecommendationsTitle' | translate }}
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" </mat-panel-title>
alt="Poster" style="display: block;"> </ombi-image> </mat-expansion-panel-header>
</a>
</div>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="movie.similar?.results?.length > 0">
<mat-expansion-panel-header>
<mat-panel-title>
{{'MediaDetails.SimilarTitle' | translate}}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer"> <div class="row card-spacer">
<div class="col-md-2" *ngFor="let r of movie.recommendations?.results">
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<a [routerLink]="'/details/movie/' + r.id">
<ombi-image
class="real grow"
matTooltip="{{ r.title }}"
src="https://image.tmdb.org/t/p/w300/{{ r.poster_path }}"
alt="Poster"
style="display: block"
>
</ombi-image>
</a>
</div>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="movie.similar?.results?.length > 0">
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'MediaDetails.SimilarTitle' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="col-md-2" *ngFor="let r of movie.similar.results"> <div class="row card-spacer">
<div class="sidebar affixable affix-top preview-poster"> <div class="col-md-2" *ngFor="let r of movie.similar.results">
<div class="poster "> <div class="sidebar affixable affix-top preview-poster">
<a [routerLink]="'/details/movie/'+r.id"> <div class="poster">
<ombi-image class="real grow" matTooltip="{{r.title}}" <a [routerLink]="'/details/movie/' + r.id">
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" <ombi-image
alt="Poster" style="display: block;"></ombi-image> class="real grow"
</a> matTooltip="{{ r.title }}"
</div> src="https://image.tmdb.org/t/p/w300/{{ r.poster_path }}"
</div> alt="Poster"
</div> style="display: block"
</div> ></ombi-image>
</mat-expansion-panel> </a>
</mat-accordion> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </mat-expansion-panel>
</div> </mat-accordion>
<div class="bottom-page-gap"> </div>
</div> </div>
</section> </div>
</div>
</div>
<div class="bottom-page-gap"></div>
</section>
</div> </div>

@ -1,336 +1,376 @@
import { Component, OnInit, ViewEncapsulation } from "@angular/core"; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services"; import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from '../../../services';
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from '@angular/router';
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from '@angular/platform-browser';
import { ICrewViewModel, ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; import { ICrewViewModel, ISearchMovieResultV2 } from '../../../interfaces/ISearchMovieResultV2';
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from '@angular/material/dialog';
import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component"; import { YoutubeTrailerComponent } from '../shared/youtube-trailer.component';
import { AuthService } from "../../../auth/auth.service"; import { AuthService } from '../../../auth/auth.service';
import { IMovieRequests, RequestType, IAdvancedData } from "../../../interfaces"; import { IMovieRequests, RequestType, IAdvancedData } from '../../../interfaces';
import { DenyDialogComponent } from "../shared/deny-dialog/deny-dialog.component"; import { DenyDialogComponent } from '../shared/deny-dialog/deny-dialog.component';
import { NewIssueComponent } from "../shared/new-issue/new-issue.component"; import { NewIssueComponent } from '../shared/new-issue/new-issue.component';
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from '@ngx-translate/core';
import { MovieAdvancedOptionsComponent } from "./panels/movie-advanced-options/movie-advanced-options.component"; import { MovieAdvancedOptionsComponent } from './panels/movie-advanced-options/movie-advanced-options.component';
import { RequestServiceV2 } from "../../../services/requestV2.service"; import { RequestServiceV2 } from '../../../services/requestV2.service';
import { firstValueFrom, forkJoin } from "rxjs"; import { firstValueFrom, forkJoin } from 'rxjs';
import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component"; import { AdminRequestDialogComponent } from '../../../shared/admin-request-dialog/admin-request-dialog.component';
import { FeaturesFacade } from "../../../state/features/features.facade"; import { FeaturesFacade } from '../../../state/features/features.facade';
@Component({ @Component({
templateUrl: "./movie-details.component.html", templateUrl: './movie-details.component.html',
styleUrls: ["../../media-details.component.scss"], styleUrls: ['../../media-details.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None,
}) })
export class MovieDetailsComponent implements OnInit{ export class MovieDetailsComponent implements OnInit {
public movie: ISearchMovieResultV2; public movie: ISearchMovieResultV2;
public hasRequest: boolean; public hasRequest: boolean;
public movieRequest: IMovieRequests; public movieRequest: IMovieRequests;
public isAdmin: boolean; public isAdmin: boolean;
public advancedOptions: IAdvancedData; public advancedOptions: IAdvancedData;
public showAdvanced: boolean; // Set on the UI public showAdvanced: boolean; // Set on the UI
public issuesEnabled: boolean; public issuesEnabled: boolean;
public roleName4k = "Request4KMovie"; public roleName4k = 'Request4KMovie';
public is4KEnabled = false; public is4KEnabled = false;
public requestType = RequestType.movie; public requestType = RequestType.movie;
private theMovidDbId: number; private theMovidDbId: number;
private imdbId: string; private imdbId: string;
private snapMovieId: string; private snapMovieId: string;
constructor(
private searchService: SearchV2Service,
private route: ActivatedRoute,
private router: Router,
private sanitizer: DomSanitizer,
private imageService: ImageService,
public dialog: MatDialog,
private requestService: RequestService,
private requestService2: RequestServiceV2,
private radarrService: RadarrService,
public messageService: MessageService,
private auth: AuthService,
private settingsState: SettingsStateService,
private translate: TranslateService,
private featureFacade: FeaturesFacade,
) {
this.snapMovieId = this.route.snapshot.params.movieDbId;
this.route.params.subscribe(async (params: any) => {
if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) {
if (params.movieDbId.startsWith('tt')) {
this.imdbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (this.imdbId !== this.snapMovieId) {
this.reloadComponent();
}
}
}
this.theMovidDbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (params.movieDbId !== this.snapMovieId) {
this.reloadComponent();
}
});
}
constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private router: Router, reloadComponent() {
private sanitizer: DomSanitizer, private imageService: ImageService, let currentUrl = this.router.url;
public dialog: MatDialog, private requestService: RequestService, this.router.routeReuseStrategy.shouldReuseRoute = () => false;
private requestService2: RequestServiceV2, private radarrService: RadarrService, this.router.onSameUrlNavigation = 'reload';
public messageService: MessageService, private auth: AuthService, private settingsState: SettingsStateService, this.router.navigate([currentUrl]);
private translate: TranslateService, private featureFacade: FeaturesFacade) { }
this.snapMovieId = this.route.snapshot.params.movieDbId;
this.route.params.subscribe(async (params: any) => {
if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) {
if (params.movieDbId.startsWith("tt")) {
this.imdbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (this.imdbId !== this.snapMovieId) {
this.reloadComponent()
}
}
}
this.theMovidDbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (params.movieDbId !== this.snapMovieId) {
this.reloadComponent()
}
});
}
reloadComponent() { async ngOnInit() {
let currentUrl = this.router.url; this.is4KEnabled = this.featureFacade.is4kEnabled();
this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.issuesEnabled = this.settingsState.getIssue();
this.router.onSameUrlNavigation = 'reload'; this.isAdmin = this.auth.hasRole('admin') || this.auth.hasRole('poweruser');
this.router.navigate([currentUrl]);
}
async ngOnInit() { if (this.isAdmin) {
this.is4KEnabled = this.featureFacade.is4kEnabled(); this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled());
this.issuesEnabled = this.settingsState.getIssue(); }
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
if (this.isAdmin) { if (this.imdbId) {
this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled()); this.searchService.getMovieByImdbId(this.imdbId).subscribe(async (x) => {
} this.movie = x;
this.checkPoster();
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
if (this.movie.requestId > 0) {
// Load up this request
this.hasRequest = true;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
}
this.loadBanner();
});
} else {
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async (x) => {
this.movie = x;
this.checkPoster();
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
if (this.movie.requestId > 0) {
// Load up this request
this.hasRequest = true;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.loadAdvancedInfo();
}
this.loadBanner();
});
}
}
if (this.imdbId) { public async request(is4K: boolean, userId?: string) {
this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => { if (!this.is4KEnabled) {
this.movie = x; is4K = false;
this.checkPoster(); }
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew); if (this.isAdmin) {
if (this.movie.requestId > 0) { const dialog = this.dialog.open(AdminRequestDialogComponent, {
// Load up this request width: '700px',
this.hasRequest = true; data: { type: RequestType.movie, id: this.movie.id, is4K: is4K },
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId); panelClass: 'modal-panel',
} });
this.loadBanner(); dialog.afterClosed().subscribe(async (result) => {
}); if (result) {
} else { const requestResult = await firstValueFrom(
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => { this.requestService.requestMovie({
this.movie = x; theMovieDbId: this.theMovidDbId,
this.checkPoster(); languageCode: this.translate.currentLang,
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew); qualityPathOverride: result.radarrPathId,
if (this.movie.requestId > 0) { requestOnBehalf: result.username?.id,
// Load up this request rootFolderOverride: result.radarrFolderId,
this.hasRequest = true; is4KRequest: is4K,
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId); }),
this.loadAdvancedInfo(); );
} if (requestResult.result) {
this.loadBanner(); if (is4K) {
}); this.movie.has4KRequest = true;
} } else {
} this.movie.requested = true;
}
this.movie.requestId = requestResult.requestId;
this.messageService.send(this.translate.instant('Requests.RequestAddedSuccessfully', { title: this.movie.title }), 'Ok');
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
} else {
this.messageService.sendRequestEngineResultError(requestResult);
}
}
});
} else {
const result = await firstValueFrom(
this.requestService.requestMovie({
theMovieDbId: this.theMovidDbId,
languageCode: this.translate.currentLang,
requestOnBehalf: userId,
qualityPathOverride: undefined,
rootFolderOverride: undefined,
is4KRequest: is4K,
}),
);
if (result.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(this.translate.instant('Requests.RequestAddedSuccessfully', { title: this.movie.title }), 'Ok');
} else {
this.messageService.sendRequestEngineResultError(result);
}
}
}
public async request(is4K: boolean, userId?: string) { public openDialog() {
if (!this.is4KEnabled) { this.dialog.open(YoutubeTrailerComponent, {
is4K = false; width: '560px',
} data: this.movie.videos.results[0].key,
if (this.isAdmin) { });
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id, is4K: is4K }, panelClass: 'modal-panel' }); }
dialog.afterClosed().subscribe(async (result) => {
if (result) {
const requestResult = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
languageCode: this.translate.currentLang,
qualityPathOverride: result.radarrPathId,
requestOnBehalf: result.username?.id,
rootFolderOverride: result.radarrFolderId,
is4KRequest: is4K }));
if (requestResult.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = requestResult.requestId;
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.movie.title }), "Ok");
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
} else {
this.messageService.sendRequestEngineResultError(requestResult);
}
}
});
} else {
const result = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: this.translate.currentLang, requestOnBehalf: userId, qualityPathOverride: undefined, rootFolderOverride: undefined, is4KRequest: is4K }));
if (result.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.movie.title }), "Ok");
} else {
this.messageService.sendRequestEngineResultError(result);
}
}
}
public openDialog() { public async deny() {
this.dialog.open(YoutubeTrailerComponent, { const dialogRef = this.dialog.open(DenyDialogComponent, {
width: '560px', width: '250px',
data: this.movie.videos.results[0].key data: { requestId: this.movieRequest.id, requestType: RequestType.movie },
}); });
}
public async deny() { dialogRef.afterClosed().subscribe((result) => {
const dialogRef = this.dialog.open(DenyDialogComponent, { this.movieRequest.denied = result.denied;
width: '250px', this.movieRequest.deniedReason = result.reason;
data: { requestId: this.movieRequest.id, requestType: RequestType.movie } });
}); }
dialogRef.afterClosed().subscribe(result => { public async issue() {
this.movieRequest.denied = result.denied; let provider = this.movie.id.toString();
this.movieRequest.deniedReason = result.reason; if (this.movie.imdbId) {
}); provider = this.movie.imdbId;
} }
const dialogRef = this.dialog.open(NewIssueComponent, {
width: '500px',
data: {
requestId: this.movieRequest ? this.movieRequest.id : null,
requestType: RequestType.movie,
providerId: provider,
title: this.movie.title,
posterPath: this.movie.posterPath,
},
});
}
public async issue() { public async approve(is4K: boolean) {
let provider = this.movie.id.toString(); const result = await firstValueFrom(this.requestService.approveMovie({ id: this.movieRequest.id, is4K }));
if (this.movie.imdbId) { if (result.result) {
provider = this.movie.imdbId; if (is4K) {
} this.movie.approved4K = true;
const dialogRef = this.dialog.open(NewIssueComponent, { } else {
width: '500px', this.movie.approved = true;
data: { requestId: this.movieRequest ? this.movieRequest.id : null, requestType: RequestType.movie, providerId: provider, title: this.movie.title, posterPath: this.movie.posterPath } }
}); this.messageService.send(this.translate.instant('Requests.SuccessfullyApproved'), 'Ok');
} } else {
this.messageService.sendRequestEngineResultError(result);
}
}
public async approve(is4K: boolean) { public async markAvailable(is4K: boolean) {
const result = await firstValueFrom(this.requestService.approveMovie({ id: this.movieRequest.id, is4K })); const result = await firstValueFrom(this.requestService.markMovieAvailable({ id: this.movieRequest.id, is4K }));
if (result.result) { if (result.result) {
if (is4K) { if (is4K) {
this.movie.approved4K = true; this.movie.available4K = true;
} else { } else {
this.movie.approved = true; this.movie.available = true;
} }
this.messageService.send(this.translate.instant("Requests.SuccessfullyApproved"), "Ok"); this.messageService.send(this.translate.instant('Requests.NowAvailable'), 'Ok');
} else { } else {
this.messageService.sendRequestEngineResultError(result); this.messageService.sendRequestEngineResultError(result);
} }
} }
public async markAvailable(is4K: boolean) { public async markUnavailable(is4K: boolean) {
const result = await firstValueFrom(this.requestService.markMovieAvailable({ id: this.movieRequest.id, is4K })) const result = await firstValueFrom(this.requestService.markMovieUnavailable({ id: this.movieRequest.id, is4K }));
if (result.result) { if (result.result) {
if (is4K) { if (is4K) {
this.movie.available4K = true; this.movie.available4K = false;
} else { } else {
this.movie.available = true; this.movie.available = false;
} }
this.messageService.send(this.translate.instant("Requests.NowAvailable"), "Ok"); this.messageService.send(this.translate.instant('Requests.NowUnavailable'), 'Ok');
} else { } else {
this.messageService.sendRequestEngineResultError(result); this.messageService.sendRequestEngineResultError(result);
} }
} }
public setAdvancedOptions(data: IAdvancedData) {
this.advancedOptions = data;
if (data.rootFolderId) {
this.movieRequest.qualityOverrideTitle = data.profiles.filter((x) => x.id == data.profileId)[0].name;
}
if (data.profileId) {
this.movieRequest.rootPathOverrideTitle = data.rootFolders.filter((x) => x.id == data.rootFolderId)[0].path;
}
}
public async markUnavailable(is4K: boolean) { public async openAdvancedOptions() {
const result = await firstValueFrom(this.requestService.markMovieUnavailable({ id: this.movieRequest.id, is4K })); const dialog = this.dialog.open(MovieAdvancedOptionsComponent, {
if (result.result) { width: '700px',
if (is4K) { data: <IAdvancedData>{ movieRequest: this.movieRequest },
this.movie.available4K = false; panelClass: 'modal-panel',
} else { });
this.movie.available = false; await dialog.afterClosed().subscribe(async (result) => {
} if (result) {
this.messageService.send(this.translate.instant("Requests.NowUnavailable"), "Ok"); result.rootFolder = result.rootFolders.filter((f) => f.id === +result.rootFolderId)[0];
} else { result.profile = result.profiles.filter((f) => f.id === +result.profileId)[0];
this.messageService.sendRequestEngineResultError(result); await this.requestService2
} .updateMovieAdvancedOptions({
} qualityOverride: result.profileId,
rootPathOverride: result.rootFolderId,
languageProfile: 0,
requestId: this.movieRequest.id,
})
.toPromise();
this.setAdvancedOptions(result);
}
});
}
public setAdvancedOptions(data: IAdvancedData) { public reProcessRequest(is4K: boolean) {
this.advancedOptions = data; this.requestService2.reprocessRequest(this.movieRequest.id, RequestType.movie, is4K).subscribe((result) => {
if (data.rootFolderId) { if (result.result) {
this.movieRequest.qualityOverrideTitle = data.profiles.filter(x => x.id == data.profileId)[0].name; this.messageService.send(result.message ? result.message : this.translate.instant('Requests.SuccessfullyReprocessed'), 'Ok');
} } else {
if (data.profileId) { this.messageService.sendRequestEngineResultError(result);
this.movieRequest.rootPathOverrideTitle = data.rootFolders.filter(x => x.id == data.rootFolderId)[0].path; }
} });
} }
public async openAdvancedOptions() { public notify() {
const dialog = this.dialog.open(MovieAdvancedOptionsComponent, { width: "700px", data: <IAdvancedData>{ movieRequest: this.movieRequest }, panelClass: 'modal-panel' }) this.requestService.subscribeToMovie(this.movieRequest.id).subscribe((result) => {
await dialog.afterClosed().subscribe(async result => { if (result) {
if (result) { this.movie.subscribed = true;
result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; this.messageService.send(this.translate.instant('Requests.SuccessfulNotify', { title: this.movie.title }), 'Ok');
result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; } else {
await this.requestService2.updateMovieAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, languageProfile: 0, requestId: this.movieRequest.id }).toPromise(); this.messageService.send(this.translate.instant('Requests.CouldntNotify', { title: this.movie.title }), 'Ok');
this.setAdvancedOptions(result); }
} });
}); }
}
public reProcessRequest(is4K: boolean) { public unNotify() {
this.requestService2.reprocessRequest(this.movieRequest.id, RequestType.movie, is4K).subscribe(result => { this.requestService.unSubscribeToMovie(this.movieRequest.id).subscribe((result) => {
if (result.result) { if (result) {
this.messageService.send(result.message ? result.message : this.translate.instant("Requests.SuccessfullyReprocessed"), "Ok"); this.movie.subscribed = false;
} else { this.messageService.send(this.translate.instant('Requests.SuccessfulUnNotify', { title: this.movie.title }), 'Ok');
this.messageService.sendRequestEngineResultError(result); } else {
} this.messageService.send(this.translate.instant('Requests.CouldntNotify', { title: this.movie.title }), 'Ok');
}); }
} });
}
public notify() { private loadBanner() {
this.requestService.subscribeToMovie(this.movieRequest.id).subscribe(result => { this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe((x) => {
if (result) { if (!this.movie.backdropPath) {
this.movie.subscribed = true; this.movie.background = this.sanitizer.bypassSecurityTrustStyle('url(' + x + ')');
this.messageService.send(this.translate.instant("Requests.SuccessfulNotify", {title: this.movie.title}), "Ok"); } else {
} else { this.movie.background = this.sanitizer.bypassSecurityTrustStyle(
this.messageService.send(this.translate.instant("Requests.CouldntNotify", {title: this.movie.title}), "Ok"); 'url(https://image.tmdb.org/t/p/original/' + this.movie.backdropPath + ')',
} );
}); }
} });
}
private checkPoster() {
if (this.movie.posterPath == null) {
this.movie.posterPath = '../../../images/default_movie_poster.png';
} else {
this.movie.posterPath = 'https://image.tmdb.org/t/p/w300/' + this.movie.posterPath;
}
}
private loadAdvancedInfo() {
const profile = this.radarrService.getQualityProfilesFromSettings();
const folders = this.radarrService.getRootFoldersFromSettings();
public unNotify() { forkJoin([profile, folders]).subscribe((x) => {
this.requestService.unSubscribeToMovie(this.movieRequest.id).subscribe(result => { const radarrProfiles = x[0] ?? [];
if (result) { const radarrRootFolders = x[1] ?? [];
this.movie.subscribed = false;
this.messageService.send(this.translate.instant("Requests.SuccessfulUnNotify", {title: this.movie.title}), "Ok");
} else {
this.messageService.send(this.translate.instant("Requests.CouldntNotify", {title: this.movie.title}), "Ok");
}
});
}
private loadBanner() { const profile = radarrProfiles.filter((p) => {
this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe(x => { return p.id === this.movieRequest.qualityOverride;
if (!this.movie.backdropPath) { });
this.movie.background = this.sanitizer.bypassSecurityTrustStyle if (profile.length > 0) {
("url(" + x + ")"); this.movieRequest.qualityOverrideTitle = profile[0].name;
} else { }
this.movie.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/original/" + this.movie.backdropPath + ")");
}
});
}
private checkPoster() {
if (this.movie.posterPath == null) {
this.movie.posterPath = "../../../images/default_movie_poster.png";
}
else {
this.movie.posterPath = "https://image.tmdb.org/t/p/w300/" + this.movie.posterPath
};
}
private loadAdvancedInfo() {
const profile = this.radarrService.getQualityProfilesFromSettings();
const folders = this.radarrService.getRootFoldersFromSettings();
forkJoin([profile, folders]).subscribe(x => { const path = radarrRootFolders.filter((folder) => {
const radarrProfiles = x[0]; return folder.id === this.movieRequest.rootPathOverride;
const radarrRootFolders = x[1]; });
if (path.length > 0) {
this.movieRequest.rootPathOverrideTitle = path[0].path;
}
});
}
const profile = radarrProfiles.filter((p) => { private orderCrew(crew: ICrewViewModel[]): ICrewViewModel[] {
return p.id === this.movieRequest.qualityOverride; return crew.sort((a, b) => {
}); if (a.job === 'Director') {
if (profile.length > 0) { return -1;
this.movieRequest.qualityOverrideTitle = profile[0].name; } else if (b.job === 'Director') {
} return 1;
} else {
const path = radarrRootFolders.filter((folder) => { return 0;
return folder.id === this.movieRequest.rootPathOverride; }
}); });
if (path.length > 0) { }
this.movieRequest.rootPathOverrideTitle = path[0].path;
}
});
}
private orderCrew(crew: ICrewViewModel[]): ICrewViewModel[] {
return crew.sort((a, b) => {
if (a.job === "Director") {
return -1;
} else if (b.job === "Director") {
return 1;
} else {
return 0;
}
});
}
} }

@ -1,137 +1,138 @@
<div *ngIf="movie" class="left-panel-details"> <div *ngIf="movie" class="left-panel-details">
<div class="rating medium-font"> <div class="rating medium-font">
<span *ngIf="movie.voteAverage" <span *ngIf="movie.voteAverage" matTooltip="{{ 'MediaDetails.Votes' | translate }} {{ movie.voteCount | thousandShort: 1 }}">
matTooltip="{{'MediaDetails.Votes' | translate }} {{movie.voteCount | thousandShort: 1}}"> <img class="rating-small" src="{{ baseUrl }}/images/tmdb-logo.svg" /> {{ movie.voteAverage | number: '1.0-1' }}/10
<img class="rating-small" src="{{baseUrl}}/images/tmdb-logo.svg"> {{movie.voteAverage | number:'1.0-1'}}/10 </span>
</span> <span *ngIf="ratings?.critics_rating && ratings?.critics_score">
<span *ngIf="ratings?.critics_rating && ratings?.critics_score"> <img
<img class="rating-small" class="rating-small"
src="{{baseUrl}}/images/{{ratings.critics_rating === 'Rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg'}}"> src="{{ baseUrl }}/images/{{ ratings.critics_rating === 'Rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg' }}"
{{ratings.critics_score}}% />
</span> {{ ratings.critics_score }}%
<span *ngIf="ratings?.audience_rating && ratings?.audience_score"> </span>
<img class="rating-small" <span *ngIf="ratings?.audience_rating && ratings?.audience_score">
src="{{baseUrl}}/images/{{ratings.audience_rating === 'Upright' ? 'rotten-audience-fresh.svg' : 'rotten-audience-rotten.svg'}}"> <img
{{ratings.audience_score}}% class="rating-small"
</span> src="{{ baseUrl }}/images/{{ ratings.audience_rating === 'Upright' ? 'rotten-audience-fresh.svg' : 'rotten-audience-rotten.svg' }}"
</div> />
<div *ngIf="streams?.length > 0" class="streaming-on"> {{ ratings.audience_score }}%
<hr> </span>
<span class="label">{{'MediaDetails.StreamingOn' | translate }}</span> </div>
<div> <div *ngIf="streams?.length > 0" class="streaming-on">
<span *ngFor="let stream of streams"> <hr />
<img class="stream-small" [matTooltip]="stream.streamingProvider" src="https://image.tmdb.org/t/p/original{{stream.logo}}"> <span class="label">{{ 'MediaDetails.StreamingOn' | translate }}</span>
</span> <div>
</div> <span *ngFor="let stream of streams">
</div> <img class="stream-small" [matTooltip]="stream.streamingProvider" src="https://image.tmdb.org/t/p/original{{ stream.logo }}" />
<hr> </span>
<div> </div>
<span class="label">{{'MediaDetails.Status' | translate }}</span> </div>
<span id="status"> {{ this.movie.status | translateStatus }}</span> <hr />
</div> <div>
<div> <span class="label">{{ 'MediaDetails.Status' | translate }}</span>
<span class="label">{{'MediaDetails.Availability' | translate }}</span> <span id="status"> {{ this.movie.status | translateStatus }}</span>
<span *ngIf="movie.available || movie.available4K"> {{'Common.Available' | translate}}</span> </div>
<span *ngIf="!movie.available && !movie.available4K"> {{'Common.NotAvailable' | translate}}</span> <div>
</div> <span class="label">{{ 'MediaDetails.Availability' | translate }}</span>
<div *ngIf="(!movie.available && movie.requested) || (!movie.available4K && movie.has4KRequest)"> <span *ngIf="movie.available || movie.available4K"> {{ 'Common.Available' | translate }}</span>
<span class="label">{{'MediaDetails.RequestStatus' | translate }}</span> <span *ngIf="!movie.available && !movie.available4K"> {{ 'Common.NotAvailable' | translate }}</span>
<div>{{getStatus(movie) | translate}}</div> </div>
</div> <div *ngIf="(!movie.available && movie.requested) || (!movie.available4K && movie.has4KRequest)">
<span class="label">{{ 'MediaDetails.RequestStatus' | translate }}</span>
<div *ngIf="request"> <div>{{ getStatus(movie) | translate }}</div>
<span class="label">{{'MediaDetails.RequestedBy' | translate }}</span> </div>
<span id="requestedByInfo"> {{request.requestedUser.userAlias}}</span>
</div> <div *ngIf="request">
<span class="label">{{ 'MediaDetails.RequestedBy' | translate }}</span>
<div *ngIf="request"> <span id="requestedByInfo"> {{ request.requestedUser.userAlias }}</span>
<span class="label">{{'MediaDetails.RequestDate' | translate }}</span> </div>
<span *ngIf="request.requestedDate < request.requestedDate4k"> {{request.requestedDate4k | amUserLocale | amDateFormat: 'LL'}}</span>
<span *ngIf="request.requestedDate > request.requestedDate4k"> {{request.requestedDate | amUserLocale | amDateFormat: 'LL'}}</span> <div *ngIf="request">
</div> <span class="label">{{ 'MediaDetails.RequestDate' | translate }}</span>
<span *ngIf="request.requestedDate < request.requestedDate4k"> {{ request.requestedDate4k | dfnsFormat: 'PP' }}</span>
<div *ngIf="request && request.source !== RequestSource.Ombi"> <span *ngIf="request.requestedDate > request.requestedDate4k"> {{ request.requestedDate | dfnsFormat: 'PP' }}</span>
<span class="label">{{'MediaDetails.RequestSource' | translate }}</span> </div>
{{RequestSource[request.source]}}
</div> <div *ngIf="request && request.source !== RequestSource.Ombi">
<span class="label">{{ 'MediaDetails.RequestSource' | translate }}</span>
<div *ngIf="request && ( request.deniedReason || request.deniedReason4K )"> {{ RequestSource[request.source] }}
<span class="label">{{'MediaDetails.DeniedReason' | translate }}</span> </div>
<div *ngIf="request.deniedReason">
<span id="deniedReasonInfo">{{request.deniedReason}}</span> <div *ngIf="request && (request.deniedReason || request.deniedReason4K)">
</div> <span class="label">{{ 'MediaDetails.DeniedReason' | translate }}</span>
<div *ngIf="request.deniedReason4K"> <div *ngIf="request.deniedReason">
<span id="deniedReasonInfo4K">{{request.deniedReason4K}}</span> <span id="deniedReasonInfo">{{ request.deniedReason }}</span>
</div> </div>
</div> <div *ngIf="request.deniedReason4K">
<span id="deniedReasonInfo4K">{{ request.deniedReason4K }}</span>
</div>
<div *ngIf="movie.quality"> </div>
<span class="label">{{'MediaDetails.Quality' | translate }}</span>
<div>{{movie.quality | quality}}</div> <div *ngIf="movie.quality">
</div> <span class="label">{{ 'MediaDetails.Quality' | translate }}</span>
<div>{{ movie.quality | quality }}</div>
<div *ngIf="movie.available4K"> </div>
<span class="label">{{'MediaDetails.Quality' | translate }}&nbsp;</span>
<span>{{"4K" | quality}}</span> <div *ngIf="movie.available4K">
</div> <span class="label">{{ 'MediaDetails.Quality' | translate }}&nbsp;</span>
<span>{{ '4K' | quality }}</span>
<div *ngIf="advancedOptions && request && request.rootPathOverrideTitle"> </div>
<span class="label">{{'MediaDetails.RootFolderOverride' | translate }}</span>
<div>{{request.rootPathOverrideTitle}}</div> <div *ngIf="advancedOptions && request && request.rootPathOverrideTitle">
</div> <span class="label">{{ 'MediaDetails.RootFolderOverride' | translate }}</span>
<div *ngIf="advancedOptions && request && request.qualityOverrideTitle"> <div>{{ request.rootPathOverrideTitle }}</div>
<span class="label">{{'MediaDetails.QualityOverride' | translate }}</span> </div>
<div>{{request.qualityOverrideTitle}}</div> <div *ngIf="advancedOptions && request && request.qualityOverrideTitle">
</div> <span class="label">{{ 'MediaDetails.QualityOverride' | translate }}</span>
<div>{{ request.qualityOverrideTitle }}</div>
</div>
<hr> <hr />
<span class="label">{{'MediaDetails.TheatricalRelease' | translate }}</span> <span class="label">{{ 'MediaDetails.TheatricalRelease' | translate }}</span>
{{movie.releaseDate | amUserLocale | amDateFormat: 'LL': 'mediumDate'}} {{ movie.releaseDate | dfnsFormat: 'PP' }}
<div *ngIf="movie.digitalReleaseDate"> <div *ngIf="movie.digitalReleaseDate">
<span class="label">{{'MediaDetails.DigitalRelease' | translate }}</span> <span class="label">{{ 'MediaDetails.DigitalRelease' | translate }}</span>
{{movie.digitalReleaseDate | amUserLocale | amDateFormat: 'LL': 'mediumDate'}} {{ movie.digitalReleaseDate | dfnsFormat: 'PP' }}
</div> </div>
<div *ngIf="movie.voteCount"> <div *ngIf="movie.voteCount">
<span class="label">{{'MediaDetails.Votes' | translate }}</span> <span class="label">{{ 'MediaDetails.Votes' | translate }}</span>
{{movie.voteCount | thousandShort: 1}} {{ movie.voteCount | thousandShort: 1 }}
</div> </div>
<div> <div>
<span class="label">{{'MediaDetails.Runtime' | translate }}</span> <span class="label">{{ 'MediaDetails.Runtime' | translate }}</span>
{{'MediaDetails.Minutes' | translate:{runtime: movie.runtime} }} {{ 'MediaDetails.Minutes' | translate: { runtime: movie.runtime } }}
</div> </div>
<div *ngIf="movie.revenue"> <div *ngIf="movie.revenue">
<span class="label">{{'MediaDetails.Revenue' | translate }}</span> <span class="label">{{ 'MediaDetails.Revenue' | translate }}</span>
{{movie.revenue | currency: 'USD'}} {{ movie.revenue | currency: 'USD' }}
</div> </div>
<div *ngIf="movie.budget"> <div *ngIf="movie.budget">
<span class="label">{{'MediaDetails.Budget' | translate }}</span> <span class="label">{{ 'MediaDetails.Budget' | translate }}</span>
{{movie.budget | currency: 'USD'}} {{ movie.budget | currency: 'USD' }}
</div> </div>
<hr /> <hr />
<div class="genre-button-container" *ngIf="movie.genres"> <div class="genre-button-container" *ngIf="movie.genres">
<span class="label">{{'MediaDetails.GenresLabel' | translate }}</span> <span class="label">{{ 'MediaDetails.GenresLabel' | translate }}</span>
<div> <div>
<mat-chip-list> <mat-chip-list>
<mat-chip selected *ngFor="let genre of movie.genres"> <mat-chip selected *ngFor="let genre of movie.genres">
{{genre.name}} {{ genre.name }}
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
</div> </div>
<hr /> <hr />
<div class="keyword-button-container" *ngIf="movie?.keywords?.keywordsValue?.length > 0"> <div class="keyword-button-container" *ngIf="movie?.keywords?.keywordsValue?.length > 0">
<span class="label">{{'MediaDetails.Keywords' | translate }}</span> <span class="label">{{ 'MediaDetails.Keywords' | translate }}</span>
<mat-chip-list> <mat-chip-list>
<mat-chip selected *ngFor="let keyword of movie.keywords.keywordsValue"> <mat-chip selected *ngFor="let keyword of movie.keywords.keywordsValue">
{{keyword.name}} {{ keyword.name }}
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
</div>

@ -1,21 +1,19 @@
<section id="summary-wrapper">
<div class="full-screenshot enabled" [style.background-image]="background"></div>
<div class="full-screenshot enabled overlay"></div>
<section id="summary-wrapper"> <div class="container summary">
<div class="full-screenshot enabled" [style.background-image]="background"></div> <div class="container title-top-banner">
<div class="full-screenshot enabled overlay"></div> <div class="row">
<div class="mobile-top-text">
<h1 id="mediaTitle" class="large-text">
{{ title }}
<span *ngIf="releaseDateFormat" class="grey-text"> ({{ releaseDate | dfnsFormat: 'P' }})</span>
</h1>
<div class="container summary"> <h2 class="tagline">{{ tagline }}</h2>
<div class="container title-top-banner"> </div>
<div class="row"> </div>
<div </div>
class="mobile-top-text"> </div>
<h1 id="mediaTitle" class="large-text">{{title}} </section>
<span *ngIf="releaseDateFormat" class="grey-text">
({{releaseDate | amLocal | amDateFormat: 'YYYY'}})</span>
</h1>
<h2 class="tagline">{{tagline}}</h2>
</div>
</div>
</div>
</div>
</section>

@ -1,82 +1,85 @@
<div class="left-panel-details"> <div class="left-panel-details">
<div> <div>
<div class="rating medium-font"> <div class="rating medium-font">
<span *ngIf="tv.rating"> <span *ngIf="tv.rating">
<img class="rating-small" src="{{baseUrl}}/images/tmdb-logo.svg"> {{tv.rating * 10 | number : '1.2-2'}}% <img class="rating-small" src="{{ baseUrl }}/images/tmdb-logo.svg" /> {{ tv.rating * 10 | number: '1.2-2' }}%
</span> </span>
<span *ngIf="ratings?.score && ratings?.class"> <span *ngIf="ratings?.score && ratings?.class">
<img class="rating-small" src="{{baseUrl}}/images/{{ratings.class === 'rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg'}}"> {{ratings.score}}% <img class="rating-small" src="{{ baseUrl }}/images/{{ ratings.class === 'rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg' }}" />
</span> {{ ratings.score }}%
</div> </span>
<div *ngIf="streams?.length > 0" id="streamingContainer" class="streaming-on-container"> </div>
<hr> <div *ngIf="streams?.length > 0" id="streamingContainer" class="streaming-on-container">
<div class="streaming-on-content"> <hr />
<span class="label">{{'MediaDetails.StreamingOn' | translate }}</span> <div class="streaming-on-content">
<div> <span class="label">{{ 'MediaDetails.StreamingOn' | translate }}</span>
<span *ngFor="let stream of sortBy('order')"> <div>
<img class="stream-small" id="stream{{stream.streamingProvider}}" [matTooltip]="stream.streamingProvider" src="https://image.tmdb.org/t/p/original{{stream.logo}}"> <span *ngFor="let stream of sortBy('order')">
</span> <img
</div> class="stream-small"
</div> id="stream{{ stream.streamingProvider }}"
</div> [matTooltip]="stream.streamingProvider"
<hr> src="https://image.tmdb.org/t/p/original{{ stream.logo }}"
<div *ngIf="tv.status"> />
<span class="label">{{'MediaDetails.Status' | translate }}</span> </span>
<span id="status"> {{ tv.status | translateStatus }}</span> </div>
</div> </div>
<span class="label">{{'MediaDetails.FirstAired' | translate }}</span> </div>
{{tv.firstAired | amLocal | amUserLocale | amDateFormat: 'LL' }} <hr />
</div> <div *ngIf="tv.status">
<span class="label">{{ 'MediaDetails.Status' | translate }}</span>
<span id="status"> {{ tv.status | translateStatus }}</span>
</div>
<span class="label">{{ 'MediaDetails.FirstAired' | translate }}</span>
{{ tv.firstAired | dfnsFormat: 'PP' }}
</div>
<div *ngIf="seasonCount"> <div *ngIf="seasonCount">
<span class="label">{{'MediaDetails.Seasons' | translate }}</span> <span class="label">{{ 'MediaDetails.Seasons' | translate }}</span>
{{seasonCount}} {{ seasonCount }}
</div> </div>
<div *ngIf="totalEpisodes"> <div *ngIf="totalEpisodes">
<span class="label">{{'MediaDetails.Episodes' | translate }}</span> <span class="label">{{ 'MediaDetails.Episodes' | translate }}</span>
{{totalEpisodes}} {{ totalEpisodes }}
</div> </div>
<div *ngIf="advancedOptions && request?.rootPathOverrideTitle"> <div *ngIf="advancedOptions && request?.rootPathOverrideTitle">
<span class="label">{{'MediaDetails.RootFolderOverride' | translate }}</span> <span class="label">{{ 'MediaDetails.RootFolderOverride' | translate }}</span>
<div>{{request.rootPathOverrideTitle}}</div> <div>{{ request.rootPathOverrideTitle }}</div>
</div> </div>
<div *ngIf="advancedOptions && request?.qualityOverrideTitle"> <div *ngIf="advancedOptions && request?.qualityOverrideTitle">
<span class="label">{{'MediaDetails.QualityOverride' | translate }}</span> <span class="label">{{ 'MediaDetails.QualityOverride' | translate }}</span>
<div>{{request.qualityOverrideTitle}}</div> <div>{{ request.qualityOverrideTitle }}</div>
</div> </div>
<div> <div>
<span class="label">{{'MediaDetails.Runtime' | translate }}</span> <span class="label">{{ 'MediaDetails.Runtime' | translate }}</span>
{{'MediaDetails.Minutes' | translate:{ runtime: tv.runtime} }} {{ 'MediaDetails.Minutes' | translate: { runtime: tv.runtime } }}
</div> </div>
<div *ngIf="tv.network"> <div *ngIf="tv.network">
<span class="label">{{'MediaDetails.Network' | translate }}</span> <span class="label">{{ 'MediaDetails.Network' | translate }}</span>
{{tv.network.name}} {{ tv.network.name }}
</div> </div>
<div class="genre-button-container" *ngIf="tv.genres">
<span class="label">{{ 'MediaDetails.GenresLabel' | translate }}</span>
<div>
<mat-chip-list>
<mat-chip selected *ngFor="let genre of tv.genres">
{{ genre.name }}
</mat-chip>
</mat-chip-list>
</div>
</div>
<div class="genre-button-container" *ngIf="tv.genres"> <hr />
<span class="label">{{'MediaDetails.GenresLabel' | translate }}</span> <div class="keyword-button-container" *ngIf="tv?.keywords?.keywordsValue?.length > 0">
<div> <span class="label">{{ 'MediaDetails.Keywords' | translate }}</span>
<mat-chip-list> <mat-chip-list>
<mat-chip selected *ngFor="let genre of tv.genres"> <mat-chip selected *ngFor="let keyword of tv.keywords.keywordsValue">
{{genre.name}} {{ keyword.name }}
</mat-chip> </mat-chip>
</mat-chip-list> </mat-chip-list>
</div> </div>
</div>
<hr />
<div class="keyword-button-container" *ngIf="tv?.keywords?.keywordsValue?.length > 0">
<span class="label">{{'MediaDetails.Keywords' | translate }}</span>
<mat-chip-list>
<mat-chip selected *ngFor="let keyword of tv.keywords.keywordsValue">
{{keyword.name}}
</mat-chip>
</mat-chip-list>
</div>
</div> </div>

@ -1,69 +1,73 @@
<mat-tab-group>
<mat-tab-group> <mat-tab *ngFor="let season of tv.seasonRequests">
<mat-tab *ngFor="let season of tv.seasonRequests"> <ng-template mat-tab-label>
<div attr.data-test="classStatus{{ season.seasonNumber }}" class="{{ getStatusClass(season) }} top-right">
<ng-template mat-tab-label> <span>{{ 'Requests.Season' | translate }} {{ season.seasonNumber }}</span>
<div attr.data-test="classStatus{{season.seasonNumber}}" class="{{getStatusClass(season)}} top-right"> </div>
<span>{{ 'Requests.Season' | translate }} {{season.seasonNumber}}</span> </ng-template>
</div>
</ng-template> <mat-card *ngIf="season.overview" class="mat-elevation-z8">
<mat-card-content>
<mat-card *ngIf="season.overview" class="mat-elevation-z8"> <p>{{ season.overview }}</p>
<mat-card-content> </mat-card-content>
<p>{{season.overview}}</p> </mat-card>
</mat-card-content>
</mat-card> <table mat-table [dataSource]="season.episodes" class="mat-elevation-z8">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<table mat-table [dataSource]="season.episodes" class="mat-elevation-z8"> <mat-checkbox
attr.data-test="masterCheckbox{{ season.seasonNumber }}"
<ng-container matColumnDef="select"> *ngIf="isSeasonCheckable(season)"
<th mat-header-cell *matHeaderCellDef> (change)="$event ? masterToggle(season.episodes) : null"
<mat-checkbox attr.data-test="masterCheckbox{{season.seasonNumber}}" *ngIf="isSeasonCheckable(season)" (change)="$event ? masterToggle(season.episodes) : null" [checked]="selection.hasValue() && isAllSelected(season.episodes)"
[checked]="selection.hasValue() && isAllSelected(season.episodes)" [indeterminate]="selection.hasValue() && !isAllSelected(season.episodes)"
[indeterminate]="selection.hasValue() && !isAllSelected(season.episodes)"> >
</mat-checkbox> </mat-checkbox>
</th> </th>
<td mat-cell *matCellDef="let row"> <td mat-cell *matCellDef="let row">
<mat-checkbox attr.data-test="episodeCheckbox{{season.seasonNumber}}{{row.episodeNumber}}" *ngIf="!row.available && !row.requested && !row.approved" (click)="$event.stopPropagation()" <mat-checkbox
(change)="$event ? selection.toggle(row) : null" attr.data-test="episodeCheckbox{{ season.seasonNumber }}{{ row.episodeNumber }}"
[checked]="selection.isSelected(row)"> *ngIf="!row.available && !row.requested && !row.approved"
</mat-checkbox> (click)="$event.stopPropagation()"
</td> (change)="$event ? selection.toggle(row) : null"
</ng-container> [checked]="selection.isSelected(row)"
>
<ng-container matColumnDef="number"> </mat-checkbox>
<th mat-header-cell *matHeaderCellDef> # </th> </td>
<td mat-cell *matCellDef="let element"> {{element.episodeNumber}} </td> </ng-container>
</ng-container>
<ng-container matColumnDef="number">
<ng-container matColumnDef="title"> <th mat-header-cell *matHeaderCellDef>#</th>
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.GridTitle' | translate }} </th> <td mat-cell *matCellDef="let element">{{ element.episodeNumber }}</td>
<td mat-cell *matCellDef="let element"> {{element.title}} </td> </ng-container>
</ng-container>
<ng-container matColumnDef="title">
<ng-container matColumnDef="airDate"> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.GridTitle' | translate }}</th>
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.AirDate' | translate }} </th> <td mat-cell *matCellDef="let element">{{ element.title }}</td>
<td mat-cell *matCellDef="let element"> {{element.airDate | amLocal | amUserLocale | amDateFormat: 'L' }}</td> </ng-container>
</ng-container>
<ng-container matColumnDef="airDate">
<ng-container matColumnDef="status"> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.AirDate' | translate }}</th>
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.GridStatus' | translate }} </th> <td mat-cell *matCellDef="let element">{{ element.airDate | dfnsFormat: 'P' }}</td>
<td mat-cell *matCellDef="let ep"> </ng-container>
<div attr.data-test="episodeStatus{{season.seasonNumber}}{{ep.episodeNumber}}" class="{{getEpisodeStatusClass(ep)}} top-right">
<span>{{ep.requestStatus | translate}}</span> <ng-container matColumnDef="status">
</div> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.GridStatus' | translate }}</th>
</td> <td mat-cell *matCellDef="let ep">
</ng-container> <div
attr.data-test="episodeStatus{{ season.seasonNumber }}{{ ep.episodeNumber }}"
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> class="{{ getEpisodeStatusClass(ep) }} top-right"
<tr mat-row *matRowDef="let row; columns: displayedColumns;" >
(click)="selection.toggle(row)"> <span>{{ ep.requestStatus | translate }}</span>
</tr> </div>
</table> </td>
</mat-tab> </ng-container>
</mat-tab-group> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" (click)="selection.toggle(row)"></tr>
</table>
</mat-tab>
</mat-tab-group>
<!-- <div *ngIf="isAdmin"> <!-- <div *ngIf="isAdmin">
<button *ngIf="!request.approved" mat-raised-button color="accent" (click)="approve(request);"> {{ 'Common.Approve' | translate }}</button> <button *ngIf="!request.approved" mat-raised-button color="accent" (click)="approve(request);"> {{ 'Common.Approve' | translate }}</button>
@ -73,16 +77,15 @@
</div> </div>
--> -->
<button *ngIf="!tv.fullyAvailable && requestable" mat-fab color="accent" id="addFabBtn" class="floating-fab" [matMenuTriggerFor]="aboveMenu"> <button *ngIf="!tv.fullyAvailable && requestable" mat-fab color="accent" id="addFabBtn" class="floating-fab" [matMenuTriggerFor]="aboveMenu">
<i class="fas fa-plus fa-lg"></i> <i class="fas fa-plus fa-lg"></i>
<mat-menu #aboveMenu="matMenu" yPosition="above"> <mat-menu #aboveMenu="matMenu" yPosition="above">
<button id="requestAll" mat-menu-item (click)="requestAllSeasons()">{{ 'Search.TvShows.AllSeasons' | translate }}</button>
<button id="requestAll" mat-menu-item (click)="requestAllSeasons()">{{'Search.TvShows.AllSeasons' | translate }}</button>
<button id="requestFirst" mat-menu-item (click)="requestFirstSeason()">{{ 'Search.TvShows.FirstSeason' | translate }}</button> <button id="requestFirst" mat-menu-item (click)="requestFirstSeason()">{{ 'Search.TvShows.FirstSeason' | translate }}</button>
<button id="requestLatest" mat-menu-item (click)="requestLatestSeason()">{{ 'Search.TvShows.LatestSeason' | translate }}</button> <button id="requestLatest" mat-menu-item (click)="requestLatestSeason()">{{ 'Search.TvShows.LatestSeason' | translate }}</button>
<button id="requestSelected" mat-menu-item (click)="submitRequests()">{{ 'Common.Request' | translate }}</button> <button id="requestSelected" mat-menu-item (click)="submitRequests()">{{ 'Common.Request' | translate }}</button>
</mat-menu> </mat-menu>
</button>

@ -1,66 +1,69 @@
<mat-accordion class="mat-elevation-z8"> <mat-accordion class="mat-elevation-z8">
<mat-expansion-panel *ngFor="let request of tvRequest"> <mat-expansion-panel *ngFor="let request of tvRequest">
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
<div> {{ request.requestStatus | translate }}</div> <div>{{ request.requestStatus | translate }}</div>
</mat-panel-title> </mat-panel-title>
<mat-panel-description> <mat-panel-description>
{{'MediaDetails.RequestedByOn' | translate: { {{
user: request.requestedUser.userAlias, 'MediaDetails.RequestedByOn'
date: request.requestedDate | amLocal | amUserLocale | amDateFormat: 'LL' } }} | translate
<span *ngIf="request.denied"> - {{request.deniedReason}}</span> : {
<span *ngIf="request.source !== RequestSource.Ombi">&nbsp;{{'MediaDetails.RequestSource' | translate }} {{RequestSource[request.source]}}</span> user: request.requestedUser.userAlias,
</mat-panel-description> date: request.requestedDate | dfnsFormat: 'PP'
</mat-expansion-panel-header> }
}}
<span *ngIf="request.denied"> - {{ request.deniedReason }}</span>
<span *ngIf="request.source !== RequestSource.Ombi"
>&nbsp;{{ 'MediaDetails.RequestSource' | translate }} {{ RequestSource[request.source] }}</span
>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-tab-group>
<mat-tab *ngFor="let season of request.seasonRequests" label="{{ 'Requests.Season' | translate }} {{ season.seasonNumber }}">
<table mat-table [dataSource]="season.episodes" class="mat-elevation-z8">
<ng-container matColumnDef="number">
<th mat-header-cell *matHeaderCellDef>#</th>
<td mat-cell *matCellDef="let element">{{ element.episodeNumber }}</td>
</ng-container>
<mat-tab-group> <ng-container matColumnDef="title">
<mat-tab *ngFor="let season of request.seasonRequests" label="{{ 'Requests.Season' | translate }} {{season.seasonNumber}}"> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.GridTitle' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.title }}</td>
</ng-container>
<table mat-table [dataSource]="season.episodes" class="mat-elevation-z8"> <ng-container matColumnDef="airDate">
<th mat-header-cell *matHeaderCellDef>{{ 'Requests.AirDate' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.airDate | dfnsFormat: 'P' }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>{{ 'Requests.GridStatus' | translate }}</th>
<td mat-cell *matCellDef="let ep">
<span> {{ request.requestStatus | translate }} </span>
</td>
</ng-container>
<ng-container matColumnDef="number"> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<th mat-header-cell *matHeaderCellDef> # </th> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<td mat-cell *matCellDef="let element"> {{element.episodeNumber}} </td> </table>
</ng-container> </mat-tab>
</mat-tab-group>
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.GridTitle' | translate }} </th>
<td mat-cell *matCellDef="let element"> {{element.title}} </td>
</ng-container>
<ng-container matColumnDef="airDate">
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.AirDate' | translate }} </th>
<td mat-cell *matCellDef="let element"> {{element.airDate | amLocal | amUserLocale | amDateFormat: 'L' }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> {{ 'Requests.GridStatus' | translate }} </th>
<td mat-cell *matCellDef="let ep">
<span> {{ request.requestStatus | translate }} </span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</mat-tab>
</mat-tab-group>
<div *ngIf="isAdmin">
<button *ngIf="!request.approved" mat-raised-button color="accent" (click)="approve(request);"> {{ 'Common.Approve' | translate }}</button>
<button *ngIf="!request.available" mat-raised-button color="warn" (click)="changeAvailability(request, true);">{{ 'Requests.MarkAvailable' | translate }}</button>
<button *ngIf="request.available" mat-raised-button color="warn" (click)="changeAvailability(request, false);">{{ 'Requests.MarkUnavailable' | translate }}</button>
<button *ngIf="!request.denied" mat-raised-button color="danger" (click)="deny(request);">{{ 'Requests.Deny' | translate }}</button>
<button mat-raised-button color="accent" (click)="reProcessRequest(request);">{{ 'MediaDetails.ReProcessRequest' | translate }}</button>
</div>
<div *ngIf="isAdmin || manageOwnRequests">
<button mat-raised-button color="danger" (click)="delete(request);">{{ 'Requests.RequestPanel.Delete' | translate }}</button>
</div>
</mat-expansion-panel>
<div *ngIf="isAdmin">
<button *ngIf="!request.approved" mat-raised-button color="accent" (click)="approve(request)">{{ 'Common.Approve' | translate }}</button>
<button *ngIf="!request.available" mat-raised-button color="warn" (click)="changeAvailability(request, true)">
{{ 'Requests.MarkAvailable' | translate }}
</button>
<button *ngIf="request.available" mat-raised-button color="warn" (click)="changeAvailability(request, false)">
{{ 'Requests.MarkUnavailable' | translate }}
</button>
<button *ngIf="!request.denied" mat-raised-button color="danger" (click)="deny(request)">{{ 'Requests.Deny' | translate }}</button>
<button mat-raised-button color="accent" (click)="reProcessRequest(request)">{{ 'MediaDetails.ReProcessRequest' | translate }}</button>
</div>
<div *ngIf="isAdmin || manageOwnRequests">
<button mat-raised-button color="danger" (click)="delete(request)">{{ 'Requests.RequestPanel.Delete' | translate }}</button>
</div>
</mat-expansion-panel>
</mat-accordion> </mat-accordion>

@ -1,171 +1,228 @@
<div *ngIf="!tv" class="justify-content-md-center top-spacing loading-spinner"> <div *ngIf="!tv" class="justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner> <mat-spinner [color]="'accent'"></mat-spinner>
</div> </div>
<div *ngIf="tv" class="main-content-container"> <div *ngIf="tv" class="main-content-container">
<div *ngIf="tv.id === 0; else main"> <div *ngIf="tv.id === 0; else main">
<div class="small-middle-container no-info"> <div class="small-middle-container no-info">
<h1><i class="far fa-frown-o" aria-hidden="true"></i></h1> <h1><i class="far fa-frown-o" aria-hidden="true"></i></h1>
<h3> {{ 'MediaDetails.NotEnoughInfo' | translate }}</h3> <h3>{{ 'MediaDetails.NotEnoughInfo' | translate }}</h3>
</div> </div>
</div> </div>
<ng-template #main>
<ng-template #main> <div>
<top-banner
<div> [background]="tv.background"
[available]="tv.available"
<top-banner [background]="tv.background" [available]="tv.available" [title]="tv.title" [title]="tv.title"
[releaseDate]="tv.firstAired" [tagline]="tv.tagline"></top-banner> [releaseDate]="tv.firstAired"
<div class="social-icons-container"> [tagline]="tv.tagline"
<social-icons ></top-banner>
[homepage]="tv.homepage" <div class="social-icons-container">
[theMoviedbId]="tv.id" <social-icons
[hasTrailer]="tv.trailer" [homepage]="tv.homepage"
[twitter]="tv.externalIds?.twitterId" [theMoviedbId]="tv.id"
[facebook]="tv.externalIds?.facebookId" [hasTrailer]="tv.trailer"
[instagram]="tv.externalIds?.instagramId" [twitter]="tv.externalIds?.twitterId"
(openTrailer)="openDialog()" [facebook]="tv.externalIds?.facebookId"
[imdbId]="tv.imdbId" [instagram]="tv.externalIds?.instagramId"
[isAdmin]="isAdmin" (openTrailer)="openDialog()"
[canShowAdvanced]="showAdvanced && showRequest" [imdbId]="tv.imdbId"
[type]="requestType" [isAdmin]="isAdmin"
(onAdvancedOptions)="openAdvancedOptions()" [canShowAdvanced]="showAdvanced && showRequest"
> [type]="requestType"
</social-icons> (onAdvancedOptions)="openAdvancedOptions()"
</div> >
</social-icons>
<section id="info-wrapper"> </div>
<div class="small-middle-container">
<div class="row justify-content-center justify-content-sm-start header-container"> <section id="info-wrapper">
<div class="details-poster-container"> <div class="small-middle-container">
<media-poster [posterPath]=tv.images.original></media-poster> <div class="row justify-content-center justify-content-sm-start header-container">
</div> <div class="details-poster-container">
<!--Next to poster--> <media-poster [posterPath]="tv.images.original"></media-poster>
<div class="details-button-container"> </div>
<div class="col-12 media-row"> <!--Next to poster-->
<ng-container *ngIf="tv.fullyAvailable || tv.partlyAvailable"> <div class="details-button-container">
<a id="viewOnPlexButton" *ngIf="tv.plexUrl" href="{{tv.plexUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn plex"> <div class="col-12 media-row">
{{'Search.ViewOnPlex' | translate}} <ng-container *ngIf="tv.fullyAvailable || tv.partlyAvailable">
<i class="far fa-play-circle fa-2x"></i> <a
</a> id="viewOnPlexButton"
<a id="viewOnEmbyButton" *ngIf="tv.embyUrl" href="{{tv.embyUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn emby"> *ngIf="tv.plexUrl"
{{'Search.ViewOnEmby' | translate}} href="{{ tv.plexUrl }}"
<i class="far fa-play-circle fa-2x"></i> mat-raised-button
</a> target="_blank"
<a id="viewOnJellyfinButton" *ngIf="tv.jellyfinUrl" href="{{tv.jellyfinUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn jellyfin"> class="btn-spacing viewon-btn plex"
{{'Search.ViewOnJellyfin' | translate}} >
<i class="far fa-play-circle fa-2x"></i> {{ 'Search.ViewOnPlex' | translate }}
</a> <i class="far fa-play-circle fa-2x"></i>
</ng-container> </a>
<button *ngIf="(!tv.fullyAvailable || (tv.fullyAvailable && tv.partlyAvailable)) && !allEpisodesRequestedOrAvailable()" mat-raised-button id="requestBtn" class="btn-spacing" color="primary" <a
(click)="request()"><i class="fas fa-plus"></i> id="viewOnEmbyButton"
{{ 'Common.Request' | translate }}</button> *ngIf="tv.embyUrl"
href="{{ tv.embyUrl }}"
<button *ngIf="!tv.denied && allEpisodesRequestedOrAvailable()" mat-raised-button class="btn-spacing" color="warn" [disabled]> mat-raised-button
<i class="fas fa-check"></i> target="_blank"
{{ 'Common.Requested' | translate }}</button> class="btn-spacing viewon-btn emby"
>
<button *ngIf="tv.fullyAvailable && !tv.partlyAvailable" id="availableBtn" mat-raised-button class="btn-spacing" color="accent" {{ 'Search.ViewOnEmby' | translate }}
[disabled]> <i class="far fa-play-circle fa-2x"></i>
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button> </a>
<a
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button id="viewOnJellyfinButton"
class="btn-spacing" color="accent" [disabled]> *ngIf="tv.jellyfinUrl"
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button> href="{{ tv.jellyfinUrl }}"
mat-raised-button
<!-- There are unaired episodes--> target="_blank"
<button *ngIf="tv.partlyAvailable && tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button class="btn-spacing viewon-btn jellyfin"
class="btn-spacing" color="accent" [disabled]> >
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button> {{ 'Search.ViewOnJellyfin' | translate }}
<!-- end unaired episodes--> <i class="far fa-play-circle fa-2x"></i>
</a>
<button id="deniedButton" *ngIf="tv.denied" [matTooltip]="tv.deniedReason" mat-raised-button class="btn-spacing" color="warn"> </ng-container>
<i class="fas fa-times"></i> {{'Common.Denied' | translate }} <button
</button> *ngIf="(!tv.fullyAvailable || (tv.fullyAvailable && tv.partlyAvailable)) && !allEpisodesRequestedOrAvailable()"
mat-raised-button
<button mat-raised-button class="btn-spacing" color="danger" id="reportIssueBtn" *ngIf="issuesEnabled" (click)="issue()"> id="requestBtn"
<i class="fas fa-exclamation"></i> {{ class="btn-spacing"
'Requests.ReportIssue' | translate }}</button> color="primary"
(click)="request()"
</div> >
</div> <i class="fas fa-plus"></i> {{ 'Common.Request' | translate }}
</div> </button>
<div class="row"> <button
<div class="col-12 col-md-2"> *ngIf="!tv.denied && allEpisodesRequestedOrAvailable()"
<mat-card class="mat-elevation-z8 spacing-below"> mat-raised-button
<mat-card-content> class="btn-spacing"
<tv-information-panel [tv]="tv" [request]="showRequest" color="warn"
[advancedOptions]="showAdvanced"></tv-information-panel> [disabled]
</mat-card-content> >
</mat-card> <i class="fas fa-check"></i>
</div> {{ 'Common.Requested' | translate }}
<div class="col-12 col-md-10"> </button>
<div class="row">
<div class="col-12"> <button
<mat-card class="mat-elevation-z8 spacing-below"> *ngIf="tv.fullyAvailable && !tv.partlyAvailable"
<mat-card-content> id="availableBtn"
{{tv.overview}} mat-raised-button
</mat-card-content> class="btn-spacing"
</mat-card> color="accent"
</div> [disabled]
<div class="col-12"> >
<cast-carousel [cast]="tv.cast"></cast-carousel> <i class="fas fa-check"></i> {{ 'Common.Available' | translate }}
</div> </button>
</div>
</div> <button
</div> *ngIf="tv.partlyAvailable && !tv.fullyAvailable"
id="partiallyAvailableBtn"
mat-raised-button
<div class="row"> class="btn-spacing"
<div class="col-12 col-md-2"> color="accent"
[disabled]
<!--Just some space yo--> >
<i class="fas fa-check"></i> {{ 'Common.PartiallyAvailable' | translate }}
</div> </button>
<div class="col-12 col-md-10"> <!-- There are unaired episodes-->
<tv-request-grid id="requests-grid" [tvRequest]="tvRequest" [isAdmin]="isAdmin" [tv]="tv"></tv-request-grid> <button
</div> *ngIf="tv.partlyAvailable && tv.fullyAvailable"
id="partiallyAvailableBtn"
<div class="col-12 col-md-2"> mat-raised-button
class="btn-spacing"
<!--Just some space yo--> color="accent"
[disabled]
</div> >
<div class="col-12 col-md-10"> <i class="fas fa-check"></i> {{ 'Common.PartiallyAvailable' | translate }}
<div class="issuesPanel"> </button>
<issues-panel [providerId]="tv.id" [isAdmin]="isAdmin"></issues-panel> <!-- end unaired episodes-->
</div>
<mat-accordion id="requests-panel"> <button
<mat-expansion-panel> id="deniedButton"
<mat-expansion-panel-header> *ngIf="tv.denied"
<mat-panel-title> [matTooltip]="tv.deniedReason"
{{'Requests.Title' | translate}} mat-raised-button
</mat-panel-title> class="btn-spacing"
</mat-expansion-panel-header> color="warn"
<tv-requests-panel [tvRequest]="tvRequest" [isAdmin]="isAdmin" [manageOwnRequests]="manageOwnRequests"></tv-requests-panel> >
</mat-expansion-panel> <i class="fas fa-times"></i> {{ 'Common.Denied' | translate }}
</button>
</mat-accordion>
<button
</div> mat-raised-button
class="btn-spacing"
color="danger"
</div> id="reportIssueBtn"
*ngIf="issuesEnabled"
(click)="issue()"
>
</div> <i class="fas fa-exclamation"></i> {{ 'Requests.ReportIssue' | translate }}
</button>
</div>
</div>
</div>
<div class="bottom-page-gap"> <div class="row">
</div> <div class="col-12 col-md-2">
</section> <mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
</div> <tv-information-panel [tv]="tv" [request]="showRequest" [advancedOptions]="showAdvanced"></tv-information-panel>
</ng-template> </mat-card-content>
</mat-card>
</div>
<div class="col-12 col-md-10">
<div class="row">
<div class="col-12">
<mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
{{ tv.overview }}
</mat-card-content>
</mat-card>
</div>
<div class="col-12">
<cast-carousel [cast]="tv.cast"></cast-carousel>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-2">
<!--Just some space yo-->
</div>
<div class="col-12 col-md-10">
<tv-request-grid id="requests-grid" [tvRequest]="tvRequest" [isAdmin]="isAdmin" [tv]="tv"></tv-request-grid>
</div>
<div class="col-12 col-md-2">
<!--Just some space yo-->
</div>
<div class="col-12 col-md-10">
<div class="issuesPanel">
<issues-panel [providerId]="tv.id" [isAdmin]="isAdmin"></issues-panel>
</div>
<mat-accordion id="requests-panel">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'Requests.Title' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<tv-requests-panel
[tvRequest]="tvRequest"
[isAdmin]="isAdmin"
[manageOwnRequests]="manageOwnRequests"
></tv-requests-panel>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>
<div class="bottom-page-gap"></div>
</section>
</div>
</ng-template>
</div> </div>

@ -0,0 +1,11 @@
import { Pipe, PipeTransform } from '@angular/core';
import { orderBy as _orderBy } from 'lodash';
@Pipe({
name: 'orderBy',
})
export class OrderPipe<T> implements PipeTransform {
transform(data: T[], orderBy: string, direction: 'asc' | 'desc' = 'asc'): T[] {
return _orderBy(data, orderBy, direction);
}
}

@ -1,12 +0,0 @@
import { Pipe, PipeTransform } from "@angular/core";
import * as moment from "moment";
const momentConstructor = moment;
@Pipe({ name: "amUserLocale" })
export class UserLocalePipe implements PipeTransform {
transform(value: moment.MomentInput): moment.Moment {
const locale = navigator.language;
return momentConstructor(value).locale(locale);
}
}

@ -1,22 +1,21 @@
import { ModuleWithProviders, NgModule } from "@angular/core"; import { ModuleWithProviders, NgModule } from '@angular/core';
import { HumanizePipe } from "./HumanizePipe"; import { HumanizePipe } from './HumanizePipe';
import { TranslateStatusPipe } from "./TranslateStatus"; import { TranslateStatusPipe } from './TranslateStatus';
import { ThousandShortPipe } from "./ThousandShortPipe"; import { ThousandShortPipe } from './ThousandShortPipe';
import { SafePipe } from "./SafePipe"; import { SafePipe } from './SafePipe';
import { QualityPipe } from "./QualityPipe"; import { QualityPipe } from './QualityPipe';
import { UserLocalePipe } from "./UserLocalePipe"; import { OrderPipe } from './OrderPipe';
@NgModule({ @NgModule({
imports: [], imports: [],
declarations: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe, UserLocalePipe, TranslateStatusPipe ], declarations: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe, TranslateStatusPipe, OrderPipe],
exports: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe, UserLocalePipe, TranslateStatusPipe ], exports: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe, TranslateStatusPipe, OrderPipe],
}) })
export class PipeModule { export class PipeModule {
public static forRoot(): ModuleWithProviders<PipeModule> {
public static forRoot(): ModuleWithProviders<PipeModule> { return {
return { ngModule: PipeModule,
ngModule: PipeModule, providers: [],
providers: [], };
}; }
}
} }

@ -1,72 +1,128 @@
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<grid-spinner [loading]="isLoadingResults"></grid-spinner> <grid-spinner [loading]="isLoadingResults"></grid-spinner>
<!-- <div class="row"> -->
<div class="row justify-content-md-center top-spacing">
<div class="btn-group" role="group">
<button
type="button"
(click)="switchFilter(RequestFilter.All)"
[attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.AllRequests' | translate }}
</button>
<button
type="button"
(click)="switchFilter(RequestFilter.Pending)"
[attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.PendingRequests' | translate }}
</button>
<button
type="button"
(click)="switchFilter(RequestFilter.Processing)"
[attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.ProcessingRequests' | translate }}
</button>
<button
type="button"
(click)="switchFilter(RequestFilter.Available)"
[attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.AvailableRequests' | translate }}
</button>
<button
type="button"
(click)="switchFilter(RequestFilter.Denied)"
[attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.DeniedRequests' | translate }}
</button>
</div>
</div>
<!-- <div class="row"> --> <div class="row">
<div class="row justify-content-md-center top-spacing"> <div class="col-md-2 offset-md-10">
<div class="btn-group" role="group"> <mat-form-field>
<button type="button" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.AllRequests' | translate}}</button> <mat-select placeholder="{{ 'Requests.RequestsToDisplay' | translate }}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
<button type="button" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.PendingRequests' | translate}}</button> <mat-option value="10">10</mat-option>
<button type="button" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button <mat-option value="15">15</mat-option>
class="grow">{{'Requests.ProcessingRequests' | translate}}</button> <mat-option value="30">30</mat-option>
<button type="button" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button <mat-option value="100">100</mat-option>
class="grow">{{'Requests.AvailableRequests' | translate}}</button> </mat-select>
<button type="button" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.DeniedRequests' | translate}}</button> </mat-form-field>
</div> </div>
</div> </div>
<!-- </div> -->
<table
mat-table
[dataSource]="dataSource"
class="requests table"
matSort
[matSortActive]="defaultSort"
matSortDisableClear
[matSortDirection]="defaultOrder"
>
<ng-container matColumnDef="artistName">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.ArtistName' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.artistName }}</td>
</ng-container>
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.AlbumName' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.title }} ({{ element.releaseDate | dfnsFormat: 'P' }})</td>
</ng-container>
<div class="row"> <ng-container matColumnDef="requestedUser.requestedBy">
<div class="col-md-2 offset-md-10"> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.RequestedBy' | translate }}</th>
<mat-form-field> <td mat-cell *matCellDef="let element">{{ element.requestedUser?.userAlias }}</td>
<mat-select placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()"> </ng-container>
<mat-option value="10">10</mat-option>
<mat-option value="15">15</mat-option>
<mat-option value="30">30</mat-option>
<mat-option value="100">100</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<!-- </div> -->
<table mat-table [dataSource]="dataSource" class="requests table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder">
<ng-container matColumnDef="requestedDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestDate' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.requestedDate | dfnsFormat: 'PP' }}</td>
</ng-container>
<ng-container matColumnDef="requestStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestStatus' | translate }}</th>
<td mat-cell *matCellDef="let element">{{ element.requestStatus | translate }}</td>
</ng-container>
<ng-container matColumnDef="artistName"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.ArtistName' | translate}} </th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element"> {{element.artistName}} </td> <td mat-cell *matCellDef="let element">
</ng-container> <a mat-raised-button color="accent" [routerLink]="'/details/artist/' + element.foreignArtistId">{{
<ng-container matColumnDef="title"> 'Requests.Details' | translate
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.AlbumName' | translate}} </th> }}</a>
<td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: 'YYYY'}}) </td> <button
</ng-container> mat-raised-button
color="warn"
(click)="openOptions(element)"
*ngIf="isAdmin || (manageOwnRequests && element.requestedUser?.userName == userName)"
>
{{ 'Requests.Options' | translate }}
</button>
</td>
</ng-container>
<ng-container matColumnDef="requestedUser.requestedBy"> <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<td mat-cell *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td> </table>
</ng-container>
<ng-container matColumnDef="requestedDate"> <mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestDate' | translate}} </th>
<td mat-cell *matCellDef="let element"> {{element.requestedDate | amLocal | amUserLocale | amDateFormat: 'LL'}} </td>
</ng-container>
<ng-container matColumnDef="requestStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestStatus' | translate}} </th>
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element">
<a mat-raised-button color="accent" [routerLink]="'/details/artist/' + element.foreignArtistId">{{ 'Requests.Details' | translate}}</a>
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin || ( manageOwnRequests && element.requestedUser?.userName == userName )"> {{ 'Requests.Options' | translate}}</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
</div> </div>

@ -1,123 +1,198 @@
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<grid-spinner [loading]="isLoadingResults"></grid-spinner> <grid-spinner [loading]="isLoadingResults"></grid-spinner>
<!-- <div class="row"> -->
<!-- <div class="row"> --> <div class="row justify-content-md-center top-spacing">
<div class="row justify-content-md-center top-spacing"> <div class="btn-group" role="group">
<div class="btn-group" role="group"> <button
<button type="button" id="filterAll" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.AllRequests' | translate}}</button> type="button"
<button type="button" id="filterPending" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.PendingRequests' | translate}}</button> id="filterAll"
<button type="button" id="filterProcessing" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button (click)="switchFilter(RequestFilter.All)"
class="grow">{{'Requests.ProcessingRequests' | translate}}</button> [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'"
<button type="button" id="filterAvailable" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'"
class="grow">{{'Requests.AvailableRequests' | translate}}</button> mat-raised-button
<button type="button" id="filterDenied" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.DeniedRequests' | translate}}</button> class="grow"
</div> >
</div> {{ 'Requests.AllRequests' | translate }}
</button>
<div class="row"> <button
<div class="col-md-2 offset-md-10"> type="button"
<mat-form-field> id="filterPending"
<mat-select id="requestsToDisplayDropdown" placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()"> (click)="switchFilter(RequestFilter.Pending)"
<mat-option value="10">10</mat-option> [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'"
<mat-option value="15">15</mat-option> [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'"
<mat-option value="30">30</mat-option> mat-raised-button
<mat-option value="100">100</mat-option> class="grow"
</mat-select> >
</mat-form-field> {{ 'Requests.PendingRequests' | translate }}
</div> </button>
</div> <button
<!-- </div> --> type="button"
<table mat-table [dataSource]="dataSource" class="requests table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder"> id="filterProcessing"
(click)="switchFilter(RequestFilter.Processing)"
<ng-container matColumnDef="select" *ngIf="isAdmin"> [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'"
<th mat-header-cell *matHeaderCellDef> [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'"
<mat-checkbox id="adminMasterCheckbox" (change)="$event ? masterToggle() : null" mat-raised-button
[checked]="selection.hasValue() && isAllSelected()" class="grow"
[indeterminate]="selection.hasValue() && !isAllSelected()"> >
</mat-checkbox> {{ 'Requests.ProcessingRequests' | translate }}
</th> </button>
<td mat-cell *matCellDef="let row"> <button
<mat-checkbox id="adminMasterCheckbox{{row.id}}" (click)="$event.stopPropagation()" type="button"
(change)="$event ? selection.toggle(row) : null" id="filterAvailable"
[checked]="selection.isSelected(row)"> (click)="switchFilter(RequestFilter.Available)"
</mat-checkbox> [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'"
</td> [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'"
</ng-container> mat-raised-button
class="grow"
<ng-container matColumnDef="title"> >
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestsTitle' | translate}} </th> {{ 'Requests.AvailableRequests' | translate }}
<td mat-cell id="title{{element.id}}" *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: 'YYYY'}}) </td> </button>
</ng-container> <button
type="button"
<ng-container matColumnDef="requestedUser.requestedBy"> id="filterDenied"
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th> (click)="switchFilter(RequestFilter.Denied)"
<td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedByAlias ? element.requestedByAlias : element.requestedUser?.userAlias}} </td> [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'"
</ng-container> [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
<ng-container matColumnDef="requestedDate"> >
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestDate' | translate}} </th> {{ 'Requests.DeniedRequests' | translate }}
<td mat-cell id="requestedDate{{element.id}}" *matCellDef="let element"> {{getRequestDate(element) | amLocal | amUserLocale | amDateFormat: 'LL'}} </td> </button>
</ng-container> </div>
</div>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.Status' | translate}} </th> <div class="row">
<td mat-cell id="status{{element.id}}" *matCellDef="let element"> {{element.status |translateStatus }} </td> <div class="col-md-2 offset-md-10">
</ng-container> <mat-form-field>
<mat-select
<ng-container matColumnDef="has4kRequest"> id="requestsToDisplayDropdown"
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.Has4KRequest' | translate}} </th> placeholder="{{ 'Requests.RequestsToDisplay' | translate }}"
<td mat-cell id="has4kRequest{{element.id}}" *matCellDef="let element"> [(value)]="gridCount"
<i *ngIf="element.has4KRequest" class="fas fa-check"></i> (selectionChange)="ngAfterViewInit()"
<i *ngIf="!element.has4KRequest" class="fas fa-times"></i> >
</td> <mat-option value="10">10</mat-option>
</ng-container> <mat-option value="15">15</mat-option>
<mat-option value="30">30</mat-option>
<mat-option value="100">100</mat-option>
<ng-container matColumnDef="requestStatus"> </mat-select>
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestStatus' | translate}} </th> </mat-form-field>
<td mat-cell id="requestedStatus{{element.id}}" *matCellDef="let element"> {{element.requestStatus | translate}} </td> </div>
</ng-container> </div>
<!-- </div> -->
<ng-container matColumnDef="watchedByRequestedUser"> <table
<th mat-table
mat-header-cell [dataSource]="dataSource"
*matHeaderCellDef class="requests table"
disableClear matSort
matTooltip="{{ 'Requests.WatchedTooltip' | translate}}"> [matSortActive]="defaultSort"
{{ 'Requests.Watched' | translate}} matSortDisableClear
</th> [matSortDirection]="defaultOrder"
<td mat-cell id="watchedByRequestedUser{{element.id}}" *matCellDef="let element"> >
<mat-checkbox <ng-container matColumnDef="select" *ngIf="isAdmin">
[checked]="element.watchedByRequestedUser" <th mat-header-cell *matHeaderCellDef>
disabled="true" <mat-checkbox
matTooltip="{{'Requests.WatchedByUsersCount' | translate: {count: element.playedByUsersCount} }}"> id="adminMasterCheckbox"
</mat-checkbox> (change)="$event ? masterToggle() : null"
</td> [checked]="selection.hasValue() && isAllSelected()"
</ng-container> [indeterminate]="selection.hasValue() && !isAllSelected()"
>
<ng-container matColumnDef="actions"> </mat-checkbox>
<th mat-header-cell *matHeaderCellDef> </th> </th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let row">
<a id="detailsButton{{element.id}}" mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">{{ 'Requests.Details' | translate}}</a> <mat-checkbox
<a id="optionsButton{{element.id}}" mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin || ( manageOwnRequests && element.requestedUser?.userName == userName ) "> {{ 'Requests.Options' | translate}}</a> id="adminMasterCheckbox{{ row.id }}"
</td> (click)="$event.stopPropagation()"
</ng-container> (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> >
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </mat-checkbox>
</table> </td>
</ng-container>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestsTitle' | translate }}</th>
<td mat-cell id="title{{ element.id }}" *matCellDef="let element">{{ element.title }} ({{ element.releaseDate | dfnsFormat: 'P' }})</td>
</ng-container>
<ng-container matColumnDef="requestedUser.requestedBy">
<th mat-header-cell *matHeaderCellDef>{{ 'Requests.RequestedBy' | translate }}</th>
<td mat-cell id="requestedBy{{ element.id }}" *matCellDef="let element">
{{ element.requestedByAlias ? element.requestedByAlias : element.requestedUser?.userAlias }}
</td>
</ng-container>
<ng-container matColumnDef="requestedDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestDate' | translate }}</th>
<td mat-cell id="requestedDate{{ element.id }}" *matCellDef="let element">{{ getRequestDate(element) | dfnsFormat: 'PP' }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.Status' | translate }}</th>
<td mat-cell id="status{{ element.id }}" *matCellDef="let element">{{ element.status | translateStatus }}</td>
</ng-container>
<ng-container matColumnDef="has4kRequest">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.Has4KRequest' | translate }}</th>
<td mat-cell id="has4kRequest{{ element.id }}" *matCellDef="let element">
<i *ngIf="element.has4KRequest" class="fas fa-check"></i>
<i *ngIf="!element.has4KRequest" class="fas fa-times"></i>
</td>
</ng-container>
<ng-container matColumnDef="requestStatus">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestStatus' | translate }}</th>
<td mat-cell id="requestedStatus{{ element.id }}" *matCellDef="let element">{{ element.requestStatus | translate }}</td>
</ng-container>
<ng-container matColumnDef="watchedByRequestedUser">
<th mat-header-cell *matHeaderCellDef disableClear matTooltip="{{ 'Requests.WatchedTooltip' | translate }}">
{{ 'Requests.Watched' | translate }}
</th>
<td mat-cell id="watchedByRequestedUser{{ element.id }}" *matCellDef="let element">
<mat-checkbox
[checked]="element.watchedByRequestedUser"
disabled="true"
matTooltip="{{ 'Requests.WatchedByUsersCount' | translate: { count: element.playedByUsersCount } }}"
>
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<a id="detailsButton{{ element.id }}" mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">{{
'Requests.Details' | translate
}}</a>
<a
id="optionsButton{{ element.id }}"
mat-raised-button
color="warn"
(click)="openOptions(element)"
*ngIf="isAdmin || (manageOwnRequests && element.requestedUser?.userName == userName)"
>
{{ 'Requests.Options' | translate }}</a
>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
</div> </div>
<button id="bulkFab" *ngIf="selection.hasValue() && isAdmin" mat-fab color="accent" class="floating-fab" [matMenuTriggerFor]="aboveMenu"> <button id="bulkFab" *ngIf="selection.hasValue() && isAdmin" mat-fab color="accent" class="floating-fab" [matMenuTriggerFor]="aboveMenu">
<i class="fas fa-bars"></i></button> <i class="fas fa-bars"></i>
<mat-menu #aboveMenu="matMenu" yPosition="above"> </button>
<button id="approveFabButton" mat-menu-item (click)="bulkApprove()">{{'Requests.RequestPanel.Approve' | translate}}</button> <mat-menu #aboveMenu="matMenu" yPosition="above">
<button *ngIf="is4kEnabled" id="approve4kFabButton" mat-menu-item (click)="bulkApprove4K()">{{'Requests.RequestPanel.Approve4K' | translate}}</button> <button id="approveFabButton" mat-menu-item (click)="bulkApprove()">{{ 'Requests.RequestPanel.Approve' | translate }}</button>
<button id="denyFabButton" mat-menu-item (click)="bulkDeny()">{{'Requests.RequestPanel.Deny' | translate}}</button> <button *ngIf="is4kEnabled" id="approve4kFabButton" mat-menu-item (click)="bulkApprove4K()">
<button *ngIf="is4kEnabled" id="deny4kFabButton" mat-menu-item (click)="bulkDeny4K()">{{'Requests.RequestPanel.Deny4K' | translate}}</button> {{ 'Requests.RequestPanel.Approve4K' | translate }}
<button id="deleteFabButton" mat-menu-item (click)="bulkDelete()">{{'Requests.RequestPanel.Delete' | translate}}</button> </button>
</mat-menu> <button id="denyFabButton" mat-menu-item (click)="bulkDeny()">{{ 'Requests.RequestPanel.Deny' | translate }}</button>
<button *ngIf="is4kEnabled" id="deny4kFabButton" mat-menu-item (click)="bulkDeny4K()">{{ 'Requests.RequestPanel.Deny4K' | translate }}</button>
<button id="deleteFabButton" mat-menu-item (click)="bulkDelete()">{{ 'Requests.RequestPanel.Delete' | translate }}</button>
</mat-menu>

@ -1,89 +1,152 @@
<div class="mat-elevation-z8"> <div class="mat-elevation-z8">
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
<grid-spinner [loading]="isLoadingResults"></grid-spinner> <div class="row justify-content-md-center top-spacing">
<div class="btn-group" role="group">
<button
type="button"
id="filterAll"
(click)="switchFilter(RequestFilter.All)"
[attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.AllRequests' | translate }}
</button>
<button
type="button"
id="filterPending"
(click)="switchFilter(RequestFilter.Pending)"
[attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.PendingRequests' | translate }}
</button>
<button
type="button"
id="filterProcessing"
(click)="switchFilter(RequestFilter.Processing)"
[attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.ProcessingRequests' | translate }}
</button>
<button
type="button"
id="filterAvailable"
(click)="switchFilter(RequestFilter.Available)"
[attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.AvailableRequests' | translate }}
</button>
<button
type="button"
id="filterDenied"
(click)="switchFilter(RequestFilter.Denied)"
[attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'"
[ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'"
mat-raised-button
class="grow"
>
{{ 'Requests.DeniedRequests' | translate }}
</button>
</div>
</div>
<div class="row">
<div class="col-md-2 offset-md-10">
<mat-form-field>
<mat-select
id="requestsToDisplayDropdown"
placeholder="{{ 'Requests.RequestsToDisplay' | translate }}"
[(value)]="gridCount"
(selectionChange)="ngAfterViewInit()"
>
<mat-option value="10">10</mat-option>
<mat-option value="15">15</mat-option>
<mat-option value="30">30</mat-option>
<mat-option value="100">100</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="row justify-content-md-center top-spacing"> <table
<div class="btn-group" role="group"> mat-table
<button type="button" id="filterAll" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.AllRequests' | translate}}</button> [dataSource]="dataSource"
<button type="button" id="filterPending" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.PendingRequests' | translate}}</button> class="requests table"
<button type="button" id="filterProcessing" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button matSort
class="grow">{{'Requests.ProcessingRequests' | translate}}</button> [matSortActive]="defaultSort"
<button type="button" id="filterAvailable" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button matSortDisableClear
class="grow">{{'Requests.AvailableRequests' | translate}}</button> [matSortDirection]="defaultOrder"
<button type="button" id="filterDenied" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="grow">{{'Requests.DeniedRequests' | translate}}</button> >
</div> <ng-container matColumnDef="series">
</div> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.RequestsTitle' | translate }}</th>
<td mat-cell id="title{{ element.id }}" *matCellDef="let element">{{ element.parentRequest.title }}</td>
</ng-container>
<div class="row"> <ng-container matColumnDef="requestedBy">
<div class="col-md-2 offset-md-10"> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.RequestedBy' | translate }}</th>
<mat-form-field> <td mat-cell id="requestedBy{{ element.id }}" *matCellDef="let element">
<mat-select id="requestsToDisplayDropdown" placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()"> {{ element.requestedByAlias ? element.requestedByAlias : element.requestedUser.userAlias }}
<mat-option value="10">10</mat-option> </td>
<mat-option value="15">15</mat-option> </ng-container>
<mat-option value="30">30</mat-option>
<mat-option value="100">100</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<table mat-table [dataSource]="dataSource" class="requests table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder"> <ng-container matColumnDef="requestedDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear>{{ 'Requests.RequestDate' | translate }}</th>
<td id="requestedDate{{ element.id }}" mat-cell *matCellDef="let element">
{{ element.requestedDate | dfnsFormat: 'PP' }}
</td>
</ng-container>
<ng-container matColumnDef="requestStatus">
<th mat-header-cell *matHeaderCellDef>{{ 'Requests.RequestStatus' | translate }}</th>
<td mat-cell id="requestedStatus{{ element.id }}" *matCellDef="let element">{{ element.requestStatus | translate }}</td>
</ng-container>
<ng-container matColumnDef="series"> <ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestsTitle' | translate}} </th> <th mat-header-cell *matHeaderCellDef>{{ 'Requests.Status' | translate }}</th>
<td mat-cell id="title{{element.id}}" *matCellDef="let element"> {{element.parentRequest.title}} </td> <td mat-cell id="status{{ element.id }}" *matCellDef="let element">
</ng-container> {{ element.parentRequest.status | translateStatus }}
</td>
</ng-container>
<ng-container matColumnDef="requestedBy"> <ng-container matColumnDef="watchedByRequestedUser">
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th> <th mat-header-cell *matHeaderCellDef disableClear matTooltip="{{ 'Requests.WatchedProgressTooltip' | translate }}">
<td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedByAlias ? element.requestedByAlias : element.requestedUser.userAlias}} </td> {{ 'Requests.Watched' | translate }}
</ng-container> </th>
<td mat-cell id="requestedUserPlayedProgress{{ element.id }}" *matCellDef="let element">
<mat-progress-bar mode="determinate" value="{{ element.requestedUserPlayedProgress }}" class="played-progress"></mat-progress-bar>
</td>
</ng-container>
<ng-container matColumnDef="requestedDate"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{'Requests.RequestDate' | translate}} </th> <th mat-header-cell *matHeaderCellDef></th>
<td id="requestedDate{{element.id}}" mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
{{element.requestedDate | amLocal | amUserLocale | amDateFormat: 'LL'}} <a
</td> id="detailsButton{{ element.id }}"
</ng-container> mat-raised-button
color="accent"
[routerLink]="'/details/tv/' + element.parentRequest.externalProviderId"
>{{ 'Requests.Details' | translate }}</a
>
<button id="optionsButton{{ element.id }}" mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">
{{ 'Requests.Options' | translate }}
</button>
</td>
</ng-container>
<ng-container matColumnDef="requestStatus"> <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestStatus' | translate}} </th> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<td mat-cell id="requestedStatus{{element.id}}" *matCellDef="let element"> {{element.requestStatus | translate}} </td> </table>
</ng-container>
<ng-container matColumnDef="status"> <mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
<th mat-header-cell *matHeaderCellDef> {{'Requests.Status' | translate}} </th>
<td mat-cell id="status{{element.id}}" *matCellDef="let element">
{{element.parentRequest.status | translateStatus }}
</td>
</ng-container>
<ng-container matColumnDef="watchedByRequestedUser">
<th
mat-header-cell
*matHeaderCellDef
disableClear
matTooltip="{{ 'Requests.WatchedProgressTooltip' | translate}}">
{{ 'Requests.Watched' | translate}}
</th>
<td mat-cell id="requestedUserPlayedProgress{{element.id}}" *matCellDef="let element">
<mat-progress-bar mode="determinate" value="{{element.requestedUserPlayedProgress}}" class="played-progress"></mat-progress-bar>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element">
<a id="detailsButton{{element.id}}" mat-raised-button color="accent" [routerLink]="'/details/tv/' + element.parentRequest.externalProviderId">{{'Requests.Details' | translate}}</a>
<button id="optionsButton{{element.id}}" mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">{{'Requests.Options' | translate}}</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
</div> </div>

@ -1,288 +1,316 @@
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" [placeholder]="'Search.Search' | translate" <input
(keyup)="search($event)"> type="text"
<span class="input-group-btn"> id="search"
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay"> class="form-control form-control-custom searchwidth"
<i class="fas fa-filter"></i> {{ 'Requests.Filter' | translate }} [placeholder]="'Search.Search' | translate"
</button> (keyup)="search($event)"
/>
<span class="input-group-btn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" <button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
aria-haspopup="true" aria-expanded="true"> <i class="fas fa-filter"></i> {{ 'Requests.Filter' | translate }}
<i class="fas fa-sort"></i> {{ 'Requests.Sort' | translate }} </button>
<span class="caret"></span>
</button> <button
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2"> class="btn btn-sm btn-primary-outline dropdown-toggle"
<li> type="button"
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | data-toggle="dropdown"
translate }} aria-haspopup="true"
aria-expanded="true"
</a> >
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ <i class="fas fa-sort"></i> {{ 'Requests.Sort' | translate }}
'Requests.SortRequestDateDesc' | translate }} <span class="caret"></span>
</button>
</a> <ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}} <li>
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }} </a>
</a> <a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }} </a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}} <a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate }} </a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate }} </a>
</a> <a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate }} </a>
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}} <a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate }} </a>
</li>
</a> </ul>
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}} </span>
</div>
</a>
</li>
</ul>
</span>
</div>
</div> </div>
<br /> <br />
<div> <div>
<div *ngFor="let request of movieRequests"> <div *ngFor="let request of movieRequests">
<div class="row"> <div class="row">
<div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div> <div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div> <div class="tint" style="background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.6) 100%)"></div>
<div class="col-sm-2 small-padding"> <div class="col-sm-2 small-padding">
<img class="img-responsive poster" src="{{ request.posterPath }}" alt="poster" />
<img class="img-responsive poster" src="{{request.posterPath}}" alt="poster"> </div>
</div> <div class="col-sm-5 small-padding">
<div>
<div class="col-sm-5 small-padding"> <a href="http://www.imdb.com/title/{{ request.imdbId }}/" target="_blank">
<div> <h4 class="request-title">{{ request.title }} ({{ request.releaseDate | dfnsFormat: 'P' }})</h4>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank"> </a>
<h4 class="request-title">{{request.title}} ({{request.releaseDate | amLocal | amDateFormat: </div>
'YYYY'}})</h4> <br />
</a> <div class="request-info">
</div> <div class="request-by">
<br /> <span>{{ 'Requests.RequestedBy' | translate }} </span>
<div class="request-info"> <span *ngIf="request.requestedByAlias">{{ request.requestedByAlias }}</span>
<div class="request-by"> <span *ngIf="!request.requestedByAlias">
<span>{{ 'Requests.RequestedBy' | translate }} </span> <span *ngIf="!isAdmin">{{ request.requestedUser.userName }}</span>
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span> <span *ngIf="isAdmin && request.requestedUser.alias">{{ request.requestedUser.alias }}</span>
<span *ngIf="!request.requestedByAlias"> <span *ngIf="isAdmin && !request.requestedUser.alias">{{ request.requestedUser.userName }}</span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span> </span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span> </div>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span> <div class="request-status">
</span> <span>{{ 'Requests.Status' | translate }} </span>
</div> <span class="label label-success" id="requestedStatusLabel">{{ request.status }}</span>
<div class="request-status"> </div>
<span>{{ 'Requests.Status' | translate }} </span>
<span class="label label-success" id="requestedStatusLabel">{{request.status}}</span> <div class="requested-status">
</div> <span>{{ 'Requests.RequestStatus' | translate }} </span>
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<div class="requested-status"> <span
<span>{{ 'Requests.RequestStatus' | translate }} </span> *ngIf="request.approved && !request.available"
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span> id="processingRequestLabel"
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" class="label label-info"
[translate]="'Common.ProcessingRequest'"></span> [translate]="'Common.ProcessingRequest'"
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span> ></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" <span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
class="label label-warning" [translate]="'Common.PendingApproval'"></span> <span
*ngIf="!request.approved && !request.availble && !request.denied"
</div> id="pendingApprovalLabel"
<div *ngIf="request.denied" id="requestDenied"> class="label label-warning"
{{ 'Requests.Denied' | translate }} [translate]="'Common.PendingApproval'"
<i style="color:red;" class="fas fa-check" pTooltip="{{request.deniedReason}}"></i> ></span>
</div>
</div> <div *ngIf="request.denied" id="requestDenied">
{{ 'Requests.Denied' | translate }}
<i style="color: red" class="fas fa-check" pTooltip="{{ request.deniedReason }}"></i>
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | </div>
amLocal | amUserLocale | amDateFormat: 'LL'} }}</div>
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | <div id="releaseDate">
translate: {date: request.digitalReleaseDate | amLocal | amUserLocale | amDateFormat: 'LL'} }}</div> {{ 'Requests.TheatricalRelease' | translate: { date: request.releaseDate | dfnsFormat: 'PP' } }}
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal </div>
| amUserLocale | amDateFormat: 'LL'}}</div> <div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">
<br /> {{ 'Requests.DigitalRelease' | translate: { date: request.digitalReleaseDate | dfnsFormat: 'PP' } }}
</div> </div>
<div *ngIf="isAdmin"> <div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{ request.requestedDate | dfnsFormat: 'PP' }}</div>
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | <br />
translate }} </div>
<span>{{request.qualityOverrideTitle}} </span> <div *ngIf="isAdmin">
</div> <div *ngIf="request.qualityOverrideTitle" class="quality-override">
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | {{ 'Requests.QualityOverride' | translate }}
translate }} <span>{{ request.qualityOverrideTitle }} </span>
<span>{{request.rootPathOverrideTitle}} </span> </div>
</div> <div *ngIf="request.rootPathOverrideTitle" class="root-override">
</div> {{ 'Requests.RootFolderOverride' | translate }}
<span>{{ request.rootPathOverrideTitle }} </span>
</div> </div>
<div class="col-sm-3 col-sm-push-3 small-padding"> </div>
<div class="row"> </div>
<div class="col-md-2 col-md-push-6"> <div class="col-sm-3 col-sm-push-3 small-padding">
<div class="row">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" <div class="col-md-2 col-md-push-6">
pTooltip="Subscribe for notifications"> <a
<i class="fas fa-rss"></i> *ngIf="request.showSubscribe && !request.subscribed"
</a> style="color: white"
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" (click)="subscribe(request)"
pTooltip="Unsubscribe notification"> pTooltip="Subscribe for notifications"
<i class="fas fa-rss"></i> >
</a> <i class="fas fa-rss"></i>
</div> </a>
</div> <a
<div *ngIf="isAdmin"> *ngIf="request.showSubscribe && request.subscribed"
<div *ngIf="!request.approved" id="approveBtn"> style="color: red"
<form> (click)="unSubscribe(request)"
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" pTooltip="Unsubscribe notification"
type="submit"> >
<i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }} <i class="fas fa-rss"></i>
</button> </a>
</form> </div>
</div>
<!--Radarr Root Folder--> <div *ngIf="isAdmin">
<div *ngIf="radarrRootFolders?.length > 1" class="btn-group btn-split" id="rootFolderBtn"> <div *ngIf="!request.approved" id="approveBtn">
<button type="button" class="btn btn-sm btn-warning-outline"> <form>
<i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }} <button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
</button> <i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" </button>
aria-haspopup="true" aria-expanded="false"> </form>
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span> <!--Radarr Root Folder-->
</button> <div *ngIf="radarrRootFolders?.length > 1" class="btn-group btn-split" id="rootFolderBtn">
<ul class="dropdown-menu"> <button type="button" class="btn btn-sm btn-warning-outline">
<li *ngFor="let folder of radarrRootFolders"> <i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
<a href="#" (click)="selectRootFolder(request, folder, $event)">{{folder.path}}</a> </button>
</li> <button
</ul> type="button"
</div> class="btn btn-warning-outline dropdown-toggle"
data-toggle="dropdown"
<!--Radarr Quality Profiles --> aria-haspopup="true"
<div *ngIf="radarrProfiles?.length > 1" class="btn-group btn-split" id="changeQualityBtn"> aria-expanded="false"
<button type="button" class="btn btn-sm btn-warning-outline"> >
<i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }} <span class="caret"></span>
</button> <span class="sr-only">Toggle Dropdown</span>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" </button>
aria-haspopup="true" aria-expanded="false"> <ul class="dropdown-menu">
<span class="caret"></span> <li *ngFor="let folder of radarrRootFolders">
<span class="sr-only">Toggle Dropdown</span> <a href="#" (click)="selectRootFolder(request, folder, $event)">{{ folder.path }}</a>
</button> </li>
<ul class="dropdown-menu"> </ul>
<li *ngFor="let profile of radarrProfiles"> </div>
<a href="#" (click)="selectQualityProfile(request, profile, $event)">{{profile.name}}</a>
</li> <!--Radarr Quality Profiles -->
</ul> <div *ngIf="radarrProfiles?.length > 1" class="btn-group btn-split" id="changeQualityBtn">
</div> <button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
<div *ngIf="!request.denied" id="denyBtn"> </button>
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"> <button
<i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }} type="button"
</button> class="btn btn-warning-outline dropdown-toggle"
</div> data-toggle="dropdown"
</div> aria-haspopup="true"
aria-expanded="false"
>
<form id="markBtnGroup"> <span class="caret"></span>
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" <span class="sr-only">Toggle Dropdown</span>
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"> </button>
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }} <ul class="dropdown-menu">
</button> <li *ngFor="let profile of radarrProfiles">
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" <a href="#" (click)="selectQualityProfile(request, profile, $event)">{{ profile.name }}</a>
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"> </li>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }} </ul>
</button> </div>
</form>
<div *ngIf="!request.denied" id="denyBtn">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
<i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}
</div> </button>
<div *ngIf="isAdmin || isRequestUser(request)"> </div>
<form> </div>
<button id="removeBtn" (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete">
<i class="fas fa-minus"></i> {{ 'Requests.Remove' | translate }} <form id="markBtnGroup">
</button> <button
</form> id="unavailableBtn"
</div> *ngIf="request.available"
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn"> (click)="changeAvailability(request, false)"
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" style="text-align: right"
aria-haspopup="true" aria-expanded="true"> value="false"
<i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }} class="btn btn-sm btn-info-outline change"
<span class="caret"></span> >
</button> <i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> </button>
<li *ngFor="let cat of issueCategories"> <button
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a> id="availableBtn"
</li> *ngIf="!request.available"
</ul> (click)="changeAvailability(request, true)"
</div> style="text-align: right"
value="true"
</div> class="btn btn-sm btn-success-outline change"
</div> >
<br /> <i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
<br /> </button>
</form>
</div>
<div *ngIf="isAdmin || isRequestUser(request)">
<form>
</div> <button
id="removeBtn"
<p-paginator [rows]="10" [totalRecords]="totalMovies" (onPageChange)="paginate($event)"></p-paginator> (click)="removeRequest(request)"
style="text-align: right"
class="btn btn-sm btn-danger-outline delete"
>
<i class="fas fa-minus"></i> {{ 'Requests.Remove' | translate }}
</button>
</form>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
<button
class="btn btn-sm btn-primary-outline dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
>
<i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, request)">{{ cat.value }}</a>
</li>
</ul>
</div>
</div>
</div>
<br />
<br />
</div>
<p-paginator [rows]="10" [totalRecords]="totalMovies" (onPageChange)="paginate($event)"></p-paginator>
</div> </div>
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false"> <p-dialog *ngIf="requestToDeny" header="Deny Request '{{ requestToDeny.title }}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span> <span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea> <textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<p-footer> <p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button> <button type="button" (click)="denyRequest()" label="Reject" class="btn btn-success">Deny</button>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button> <button type="button" (click)="denyDisplay = false" label="Close" class="btn btn-danger">Close</button>
</p-footer> </p-footer>
</p-dialog> </p-dialog>
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title" <issue-report
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report> [movie]="true"
[visible]="issuesBarVisible"
(visibleChange)="issuesBarVisible = $event"
[title]="issueRequest?.title"
[issueCategory]="issueCategorySelected"
[id]="issueRequest?.id"
[providerId]="issueProviderId"
></issue-report>
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small"> <p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
<h3>{{ 'Requests.Filter' | translate }}</h3> <h3>{{ 'Requests.Filter' | translate }}</h3>
<hr> <hr />
<div> <div>
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4> <h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)"> <input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)" />
<label for="Available">{{ 'Common.Available' | translate }}</label> <label for="Available">{{ 'Common.Available' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)"> <input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)" />
<label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label> <label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4> <h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)"> <input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)" />
<label for="approved">{{ 'Filter.Approved' | translate }}</label> <label for="approved">{{ 'Filter.Approved' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)"> <input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)" />
<label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label> <label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)"> <input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)" />
<label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label> <label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label>
</div> </div>
</div> </div>
</div> </div>
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)"> <button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)">
<i class="fas fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button> <i class="fas fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}
</button>
</p-sidebar> </p-sidebar>

@ -1,116 +1,107 @@
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" [placeholder]="'Search.Search' | translate" <input
(keyup)="search($event)"> type="text"
<span class="input-group-btn"> id="search"
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay"> class="form-control form-control-custom searchwidth"
<i class="fas fa-filter"></i> {{ 'Requests.Filter' | translate }} [placeholder]="'Search.Search' | translate"
</button> (keyup)="search($event)"
/>
<span class="input-group-btn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" <button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
aria-haspopup="true" aria-expanded="true"> <i class="fas fa-filter"></i> {{ 'Requests.Filter' | translate }}
<i class="fas fa-sort"></i> {{ 'Requests.Sort' | translate }} </button>
<span class="caret"></span>
</button> <button
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2"> class="btn btn-sm btn-primary-outline dropdown-toggle"
<li> type="button"
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | data-toggle="dropdown"
translate }} aria-haspopup="true"
aria-expanded="true"
</a> >
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ <i class="fas fa-sort"></i> {{ 'Requests.Sort' | translate }}
'Requests.SortRequestDateDesc' | translate }} <span class="caret"></span>
</button>
</a> <ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}} <li>
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }} </a>
</a> <a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }} </a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}} <a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate }} </a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate }} </a>
</a> <a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate }} </a>
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}} <a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate }} </a>
</li>
</a> </ul>
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}} </span>
</div>
</a>
</li>
</ul>
</span>
</div>
</div> </div>
<br /> <br />
<div class="col-md-12"> <div class="col-md-12">
<div *ngFor="let request of albumRequests" class="col-md-4"> <div *ngFor="let request of albumRequests" class="col-md-4">
<div class="row"> <div class="row">
<div class="album-bg backdrop" [style.background-image]="request.background"></div> <div class="album-bg backdrop" [style.background-image]="request.background"></div>
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div> <div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.6) 100%)"></div>
<div class="col-sm-12 small-padding"> <div class="col-sm-12 small-padding">
<img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{request.disk}}" alt="poster"> <img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{ request.disk }}" alt="poster" />
</div> </div>
<div class="col-sm-12 small-padding">
<div class="col-sm-12 small-padding"> <div>
<div> <h4>
<h4> <a href="" target="_blank">
<a href="" target="_blank"> {{ request.title | truncate: 36 }}
{{request.title | truncate: 36}} </a>
</a> </h4>
<h5>
</h4> <a href="">
<h5> {{ request.artistName }}
<a href=""> </a>
{{request.artistName}} </h5>
</a> </div>
</h5>
</div> <div class="request-info">
<div class="request-by">
<div class="request-info"> <span>{{ 'Requests.RequestedBy' | translate }} </span>
<div class="request-by"> <span *ngIf="request.requestedByAlias">{{ request.requestedByAlias }}</span>
<span>{{ 'Requests.RequestedBy' | translate }} </span> <span *ngIf="!request.requestedByAlias">
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span> <span *ngIf="!isAdmin">{{ request.requestedUser.userName }}</span>
<span *ngIf="!request.requestedByAlias"> <span *ngIf="isAdmin && request.requestedUser.alias">{{ request.requestedUser.alias }}</span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span> <span *ngIf="isAdmin && !request.requestedUser.alias">{{ request.requestedUser.userName }}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span> </span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span> </div>
</span>
</div> <div class="requested-status">
<span>{{ 'Requests.RequestStatus' | translate }} </span>
<div class="requested-status"> <span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span>{{ 'Requests.RequestStatus' | translate }} </span> <span
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span> *ngIf="request.approved && !request.available"
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" id="processingRequestLabel"
[translate]="'Common.ProcessingRequest'"></span> class="label label-info"
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span> [translate]="'Common.ProcessingRequest'"
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"> ></span>
<i class="fas fa-info-circle"></i> <span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
</span> <span *ngIf="request.deniedReason" title="{{ request.deniedReason }}">
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" <i class="fas fa-info-circle"></i>
class="label label-warning" [translate]="'Common.PendingApproval'"></span> </span>
<span
</div> *ngIf="!request.approved && !request.availble && !request.denied"
<div *ngIf="request.denied" id="requestDenied"> id="pendingApprovalLabel"
{{ 'Requests.Denied' | translate }} class="label label-warning"
<i style="color:red;" class="fas fa-check" pTooltip="{{request.deniedReason}}"></i> [translate]="'Common.PendingApproval'"
></span>
</div> </div>
<div *ngIf="request.denied" id="requestDenied">
{{ 'Requests.Denied' | translate }}
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | amLocal | <i style="color: red" class="fas fa-check" pTooltip="{{ request.deniedReason }}"></i>
amDateFormat: 'LL'} }}</div> </div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal
| amDateFormat: 'LL'}}</div> <div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: { date: request.releaseDate | dfnsFormat: 'PP' } }}</div>
<br /> <div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{ request.requestedDate | dfnsFormat: 'PP' }}</div>
</div> <br />
<!-- <div *ngIf="isAdmin"> </div>
<!-- <div *ngIf="isAdmin">
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }} <div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<span>{{request.qualityOverrideTitle}} </span> <span>{{request.qualityOverrideTitle}} </span>
</div> </div>
@ -118,10 +109,9 @@
<span>{{request.rootPathOverrideTitle}} </span> <span>{{request.rootPathOverrideTitle}} </span>
</div> </div>
</div> --> </div> -->
</div>
</div> <div class="col-sm-12 small-padding">
<div class="col-sm-12 small-padding"> <!-- <div class="row">
<!-- <div class="row">
<div class="col-md-2 col-md-push-6"> <div class="col-md-2 col-md-push-6">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications"> <a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications">
@ -132,17 +122,16 @@
</a> </a>
</div> </div>
</div> --> </div> -->
<div *ngIf="isAdmin"> <div *ngIf="isAdmin">
<div *ngIf="!request.approved" id="approveBtn"> <div *ngIf="!request.approved" id="approveBtn">
<form class="col-md-6"> <form class="col-md-6">
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" <button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
type="submit"> <i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}
<i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }} </button>
</button> </form>
</form>
<!--Radarr Root Folder-->
<!--Radarr Root Folder--> <!-- <div *ngIf="radarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
<!-- <div *ngIf="radarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
<button type="button" class="btn btn-sm btn-warning-outline"> <button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }} <i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
</button> </button>
@ -157,8 +146,8 @@
</ul> </ul>
</div> --> </div> -->
<!--Radarr Quality Profiles --> <!--Radarr Quality Profiles -->
<!-- <div *ngIf="radarrProfiles" class="btn-group btn-split" id="changeQualityBtn"> <!-- <div *ngIf="radarrProfiles" class="btn-group btn-split" id="changeQualityBtn">
<button type="button" class="btn btn-sm btn-warning-outline"> <button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }} <i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
</button> </button>
@ -173,117 +162,129 @@
</ul> </ul>
</div> --> </div> -->
<div *ngIf="!request.denied" id="denyBtn" class="col-md-6"> <div *ngIf="!request.denied" id="denyBtn" class="col-md-6">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"> <button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
<i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }} <i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}
</button> </button>
</div> </div>
</div> </div>
<form id="markBtnGroup">
<form id="markBtnGroup"> <button
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" id="unavailableBtn"
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"> *ngIf="request.available"
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }} (click)="changeAvailability(request, false)"
</button> style="text-align: right"
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" value="false"
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"> class="btn btn-sm btn-info-outline change"
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }} >
</button> <i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
</form> </button>
<button
id="availableBtn"
*ngIf="!request.available"
</div> (click)="changeAvailability(request, true)"
<div *ngIf="isAdmin || isRequestUser(request)"> style="text-align: right"
<form id="removeBtn"> value="true"
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"> class="btn btn-sm btn-success-outline change"
<i class="fas fa-minus"></i> {{ 'Requests.Remove' | translate }} >
</button> <i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
</form> </button>
</div> </form>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn"> </div>
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" <div *ngIf="isAdmin || isRequestUser(request)">
aria-haspopup="true" aria-expanded="true"> <form id="removeBtn">
<i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }} <button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete">
<span class="caret"></span> <i class="fas fa-minus"></i> {{ 'Requests.Remove' | translate }}
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> </form>
<li *ngFor="let cat of issueCategories"> </div>
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a> <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
</li> <button
</ul> class="btn btn-sm btn-primary-outline dropdown-toggle"
</div> type="button"
data-toggle="dropdown"
</div> aria-haspopup="true"
</div> aria-expanded="true"
<br /> >
<br /> <i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
</div> <a [routerLink]="" (click)="reportIssue(cat, request)">{{ cat.value }}</a>
</li>
<p-paginator [rows]="10" [totalRecords]="totalAlbums" (onPageChange)="paginate($event)"></p-paginator> </ul>
</div>
</div>
</div>
<br />
<br />
</div>
<p-paginator [rows]="10" [totalRecords]="totalAlbums" (onPageChange)="paginate($event)"></p-paginator>
</div> </div>
<issue-report
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title" [movie]="true"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report> [visible]="issuesBarVisible"
(visibleChange)="issuesBarVisible = $event"
[title]="issueRequest?.title"
[issueCategory]="issueCategorySelected"
[id]="issueRequest?.id"
[providerId]="issueProviderId"
></issue-report>
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small"> <p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
<h3>{{ 'Requests.Filter' | translate }}</h3> <h3>{{ 'Requests.Filter' | translate }}</h3>
<hr> <hr />
<div> <div>
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4> <h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)"> <input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)" />
<label for="Available">{{ 'Common.Available' | translate }}</label> <label for="Available">{{ 'Common.Available' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)"> <input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)" />
<label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label> <label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4> <h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)"> <input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)" />
<label for="approved">{{ 'Filter.Approved' | translate }}</label> <label for="approved">{{ 'Filter.Approved' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)"> <input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)" />
<label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label> <label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)"> <input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)" />
<label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label> <label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label>
</div> </div>
</div> </div>
</div> </div>
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)"> <button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)">
<i class="fas fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button> <i class="fas fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}
</button>
</p-sidebar> </p-sidebar>
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{ requestToDeny.title }}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span>
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false"> <textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<span>Please enter a rejection reason, the user will be notified of this:</span> <p-footer>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea> <button type="button" (click)="denyRequest()" label="Reject" class="btn btn-success">Deny</button>
<p-footer> <button type="button" (click)="denyDisplay = false" label="Close" class="btn btn-danger">Close</button>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button> </p-footer>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button> </p-dialog>
</p-footer>
</p-dialog>

@ -1,128 +1,167 @@
<div *ngIf="childRequests"> <div *ngIf="childRequests">
<hr /> <hr />
<div *ngFor="let child of childRequests" class="clearfix"> <div *ngFor="let child of childRequests" class="clearfix">
<div class="col-md-12"> <div class="col-md-12">
<div class="col-md-2">
<span [translate]="'Requests.RequestedBy'"></span>
<span *ngIf="child.requestedByAlias">{{ child.requestedByAlias }}</span>
<span *ngIf="!child.requestedByAlias">
<span *ngIf="!isAdmin">{{ child.requestedUser.userName }}</span>
<span *ngIf="isAdmin && child.requestedUser.alias">{{ child.requestedUser.alias }}</span>
<span *ngIf="isAdmin && !child.requestedUser.alias">{{ child.requestedUser.userName }}</span>
</span>
</div>
<div class="col-md-2"> <div class="col-md-1 col-md-push-9">
<span [translate]="'Requests.RequestedBy'"></span> <button
<span *ngIf="child.requestedByAlias">{{child.requestedByAlias}}</span> id="subscribeBtn"
<span *ngIf="!child.requestedByAlias"> *ngIf="child.showSubscribe && !child.subscribed"
<span *ngIf="!isAdmin">{{child.requestedUser.userName}}</span> (click)="subscribe(child)"
<span *ngIf="isAdmin && child.requestedUser.alias">{{child.requestedUser.alias}}</span> class="btn btn-sm btn-primary-outline"
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span> pTooltip="Subscribe for notifications"
</span> type="submit"
</div> >
<i class="fas fa-rss"></i> Subscribe
</button>
<button
id="subscribeBtn"
*ngIf="child.showSubscribe && child.subscribed"
(click)="unSubscribe(child)"
class="btn btn-sm btn-danger-outline"
pTooltip="UnSubscribe for notifications"
type="submit"
>
<i class="fas fa-rss"></i> UnSubscribe
</button>
<div class="col-md-1 col-md-push-9"> <div *ngIf="isAdmin">
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)" <button
class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i id="approveBtn"
class="fas fa-rss"></i> Subscribe</button> *ngIf="child.canApprove && !child.approved"
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)" (click)="approve(child)"
class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i class="btn btn-sm btn-success-outline"
class="fas fa-rss"></i> UnSubscribe</button> type="submit"
>
<i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}
</button>
<button
id="unavailableBtn"
*ngIf="child.available"
(click)="changeAvailability(child, false)"
style="text-align: right"
value="false"
class="btn btn-sm btn-info-outline change"
>
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
</button>
<button
id="availableBtn"
*ngIf="!child.available"
(click)="changeAvailability(child, true)"
style="text-align: right"
value="true"
class="btn btn-sm btn-success-outline change"
>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
</button>
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny">
<i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}
</button>
</div>
<div *ngIf="isAdmin || isRequestUser(child)">
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny">
<i class="fas fa-times"></i> {{ 'Requests.Remove' | translate }}
</button>
</div>
</div>
</div>
<div class="col-md-12">
<ngb-tabset>
<div *ngFor="let season of child.seasonRequests">
<ngb-tab [id]="season.seasonNumber" [title]="season.seasonNumber">
<ng-template ngbTabContent>
<h2>{{ 'Requests.Season' | translate }} {{ season.seasonNumber }}</h2>
<div *ngIf="isAdmin"> <table class="table table-striped table-hover table-responsive table-condensed">
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" <thead>
type="submit"><i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}</button> <tr>
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" <th>
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fas fa-minus"></i> <a> # </a>
{{ 'Requests.MarkUnavailable' | translate }}</button> </th>
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right" <th>
value="true" class="btn btn-sm btn-success-outline change"><i class="fas fa-plus"></i> {{ <a>
'Requests.MarkAvailable' | translate }}</button> {{ 'Requests.GridTitle' | translate }}
</a>
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny"> </th>
<i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}</button> <th>
<a>
</div> {{ 'Requests.AirDate' | translate }}
<div *ngIf="isAdmin || isRequestUser(child)"> </a>
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i </th>
class="fas fa-times"></i> {{ 'Requests.Remove' | translate }}</button> <th>
</div> <a>
{{ 'Requests.GridStatus' | translate }}
</a>
</div> </th>
</div> </tr>
<div class="col-md-12"> </thead>
<ngb-tabset> <tbody>
<tr *ngFor="let ep of season.episodes">
<div *ngFor="let season of child.seasonRequests"> <td>
<ngb-tab [id]="season.seasonNumber" [title]="season.seasonNumber"> {{ ep.episodeNumber }}
<ng-template ngbTabContent> </td>
<h2>{{ 'Requests.Season' | translate }} {{season.seasonNumber}}</h2> <td>
{{ ep.title }}
<table class="table table-striped table-hover table-responsive table-condensed"> </td>
<thead> <td>
<tr> {{ ep.airDate | dfnsFormat: 'P' }}
<th> </td>
<a> <td>
# <span *ngIf="child.denied" class="label label-danger" id="deniedLabel" [translate]="'Common.Denied'">
</a> <i style="color: red" class="fas fa-check" pTooltip="{{ child.deniedReason }}"></i>
</th> </span>
<th> <span
<a> *ngIf="!child.denied && ep.available"
{{ 'Requests.GridTitle' | translate }} class="label label-success"
</a> id="availableLabel"
</th> [translate]="'Common.Available'"
<th> ></span>
<a> <span
{{ 'Requests.AirDate' | translate }} *ngIf="!child.denied && ep.approved && !ep.available"
</a> class="label label-info"
</th> id="processingRequestLabel"
<th> [translate]="'Common.ProcessingRequest'"
<a> ></span>
{{ 'Requests.GridStatus' | translate }} <div *ngIf="!child.denied && !ep.approved">
</a> <div *ngIf="!ep.available">
</th> <span
</tr> class="label label-warning"
</thead> id="pendingApprovalLabel"
<tbody> [translate]="'Common.PendingApproval'"
<tr *ngFor="let ep of season.episodes"> ></span>
<td> </div>
{{ep.episodeNumber}} </div>
</td> </td>
<td> </tr>
{{ep.title}} </tbody>
</td> </table>
<td> </ng-template>
{{ep.airDate | amLocal | amUserLocale | amDateFormat: 'L' }} </ngb-tab>
</td> </div>
<td> </ngb-tabset>
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel" </div>
[translate]="'Common.Denied'"> <br />
<i style="color:red;" class="fas fa-check" pTooltip="{{child.deniedReason}}"></i> <br />
</span> <hr />
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel" </div>
[translate]="'Common.Available'"></span>
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info"
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
<div *ngIf="!child.denied && !ep.approved">
<div *ngIf="!ep.available"><span class="label label-warning" id="pendingApprovalLabel"
[translate]="'Common.PendingApproval'"></span></div>
</div>
</td>
</tr>
</tbody>
</table>
</ng-template>
</ngb-tab>
</div>
</ngb-tabset>
</div>
<br />
<br />
<hr />
</div>
</div> </div>
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false"> <p-dialog *ngIf="requestToDeny" header="Deny Request '{{ requestToDeny.title }}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span> <span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea> <textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<p-footer> <p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button> <button type="button" (click)="denyRequest()" label="Reject" class="btn btn-success">Deny</button>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button> <button type="button" (click)="denyDisplay = false" label="Close" class="btn btn-danger">Close</button>
</p-footer> </p-footer>
</p-dialog> </p-dialog>

@ -1,112 +1,146 @@
<div class="form-group"> <div class="form-group">
<div> <div>
<input type="text" id="search" class="form-control form-control-custom" [placeholder]="'Common.Search' | translate" (keyup)="search($event)"> <input
</div> type="text"
id="search"
class="form-control form-control-custom"
[placeholder]="'Common.Search' | translate"
(keyup)="search($event)"
/>
</div>
</div> </div>
<br /> <br />
<div> <div>
<div *ngFor="let node of tvRequests.collection"> <div *ngFor="let node of tvRequests.collection">
<!--This is the section that holds the parent level results set--> <!--This is the section that holds the parent level results set-->
<div> <div>
<div class="row"> <div class="row">
<div class="myBg backdrop" [style.background-image]="node?.background"></div> <div class="myBg backdrop" [style.background-image]="node?.background"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div> <div class="tint" style="background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.6) 100%)"></div>
<div class="col-sm-2 small-padding"> <div class="col-sm-2 small-padding">
<img class="img-responsive poster" src="{{ node.posterPath || null }}" alt="poster" />
</div>
<img class="img-responsive poster" src="{{node.posterPath || null}}" alt="poster"> <div class="col-sm-5 small-padding">
<div>
<a href="http://www.imdb.com/title/{{ node.imdbId }}/" target="_blank">
<h4 class="request-title">{{ node.title }} ({{ node.releaseDate | dfnsFormat: 'P' }})</h4>
</a>
</div>
<br />
<div>
<span>Status: </span>
<span class="label label-success">{{ node.status }}</span>
</div>
</div> <div>Release Date: {{ node.releaseDate | dfnsFormat: 'PP' }}</div>
<div *ngIf="isAdmin">
<div *ngIf="node.qualityOverrideTitle" class="quality-override">
{{ 'Requests.QualityOverride' | translate }}
<span>{{ node.qualityOverrideTitle }} </span>
</div>
<div *ngIf="node.rootPathOverrideTitle" class="root-override">
{{ 'Requests.RootFolderOverride' | translate }}
<span>{{ node.rootPathOverrideTitle }} </span>
</div>
</div>
<div class="col-sm-5 small-padding"> <br />
<div> </div>
<a href="http://www.imdb.com/title/{{node.imdbId}}/" target="_blank"> <div class="col-sm-3 col-sm-push-3 small-padding">
<h4 class="request-title">{{node.title}} ({{node.releaseDate | amLocal| amUserLocale | amDateFormat: 'YYYY'}})</h4> <button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab(node, $event)">
</a> <i class="fas fa-plus"></i> View
</div> </button>
<br /> <div *ngIf="isAdmin">
<div> <!--Sonarr Root Folder-->
<span>Status: </span> <div *ngIf="sonarrRootFolders?.length > 1" class="btn-group btn-split" id="rootFolderBtn">
<span class="label label-success">{{node.status}}</span> <button type="button" class="btn btn-sm btn-warning-outline">
</div> <i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
</button>
<button
type="button"
class="btn btn-warning-outline dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of sonarrRootFolders">
<a href="#" (click)="selectRootFolder(node, folder, $event)">{{ folder.path }}</a>
</li>
</ul>
</div>
<!--Sonarr Quality Profiles -->
<div *ngIf="sonarrProfiles?.length > 1" class="btn-group btn-split" id="changeQualityBtn">
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
</button>
<button
type="button"
class="btn btn-warning-outline dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of sonarrProfiles">
<a href="#" (click)="selectQualityProfile(node, profile, $event)">{{ profile.name }}</a>
</li>
</ul>
</div>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issueBtn">
<button
class="btn btn-sm btn-primary-outline dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
>
<i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, node)">{{ cat.value }}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.open">
<tvrequests-children
[childRequests]="node.childRequests"
[isAdmin]="isAdmin"
[currentUser]="currentUser"
(requestDeleted)="childRequestDeleted($event)"
></tvrequests-children>
</div>
<div>Release Date: {{node.releaseDate | amLocal | amUserLocale | amDateFormat: 'LL'}}</div> <br />
<div *ngIf="isAdmin"> <br />
<div *ngIf="node.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }} </div>
<span>{{node.qualityOverrideTitle}} </span>
</div>
<div *ngIf="node.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{node.rootPathOverrideTitle}} </span>
</div>
</div>
<br /> <p-paginator [rows]="10" [totalRecords]="totalTv" (onPageChange)="paginate($event)"></p-paginator>
</div>
<div class="col-sm-3 col-sm-push-3 small-padding">
<button style="text-align: right" class="btn btn-sm btn-success-outline" (click)="openClosestTab(node,$event)">
<i class="fas fa-plus"></i> View</button>
<div *ngIf="isAdmin">
<!--Sonarr Root Folder-->
<div *ngIf="sonarrRootFolders?.length > 1" class="btn-group btn-split" id="rootFolderBtn">
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of sonarrRootFolders">
<a href="#" (click)="selectRootFolder(node, folder, $event)">{{folder.path}}</a>
</li>
</ul>
</div>
<!--Sonarr Quality Profiles -->
<div *ngIf="sonarrProfiles?.length > 1" class="btn-group btn-split" id="changeQualityBtn">
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fas fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of sonarrProfiles">
<a href="#" (click)="selectQualityProfile(node, profile, $event)">{{profile.name}}</a>
</li>
</ul>
</div>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issueBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fas fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, node)">{{cat.value}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.open">
<tvrequests-children [childRequests]="node.childRequests" [isAdmin]="isAdmin" [currentUser]="currentUser" (requestDeleted)="childRequestDeleted($event)"></tvrequests-children>
</div>
<br/>
<br/>
</div>
<p-paginator [rows]="10" [totalRecords]="totalTv" (onPageChange)="paginate($event)"></p-paginator>
</div> </div>
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title" [issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId" (visibleChange)="issuesBarVisible = $event;"></issue-report> <issue-report
[movie]="false"
[visible]="issuesBarVisible"
[title]="issueRequest?.title"
[issueCategory]="issueCategorySelected"
[id]="issueRequest?.id"
[providerId]="issueProviderId"
(visibleChange)="issuesBarVisible = $event"
></issue-report>

@ -1,28 +1,21 @@
<h1 mat-dialog-title><i class="fas fa-code-branch"></i> Latest Version: {{ data.updateVersionString }}</h1>
<h1 mat-dialog-title><i class="fas fa-code-branch"></i> Latest Version: {{data.updateVersionString}}</h1>
<mat-dialog-content> <mat-dialog-content>
<div [innerHTML]="data.changeLogs"> <div [innerHTML]="data.changeLogs"></div>
</div>
<div class="mat-table">
<div class="mat-header-row">
<div class="mat-header-cell">Binary</div>
<div class="mat-table"> <div class="mat-header-cell">Download</div>
<div class="mat-header-row"> </div>
<div class="mat-header-cell">Binary</div> <div *ngFor="let d of data.downloads" class="mat-row">
<div class="mat-header-cell">Download</div></div> <div class="mat-cell">{{ d.name }}</div>
<div *ngFor="let d of data.downloads" class="mat-row" > <div class="mat-cell"><a href="{{ d.url }}">Download</a></div>
<div class="mat-cell">{{d.name}}</div> </div>
<div class="mat-cell"><a href="{{d.url}}">Download</a></div> </div>
</div>
</div> <small>Updated at {{ data.updateDate | dfnsFormat: 'PP' }}</small>
<small>Updated at {{data.updateDate | amUserLocale | amDateFormat: 'LL' }}</small>
</mat-dialog-content> </mat-dialog-content>
<div mat-dialog-actions class="right-buttons"> <div mat-dialog-actions class="right-buttons">
<button mat-raised-button id="cancelButton" [mat-dialog-close]="" color="warn"><i class="fas fa-times"></i> Close</button> <button mat-raised-button id="cancelButton" [mat-dialog-close]="" color="warn"><i class="fas fa-times"></i> Close</button>
</div> </div>

@ -1,25 +1,29 @@
<div class='container'> <div class="container">
<div class='chatbox'> <div class="chatbox">
<div class='chatbox__user-list'> <div class="chatbox__user-list">
<h1>{{ "NavigationBar.UserManagement" | translate }}</h1> <h1>{{ 'NavigationBar.UserManagement' | translate }}</h1>
<div class='chatbox__user--active' *ngFor="let user of userList"> <div class="chatbox__user--active" *ngFor="let user of userList">
<p>{{user}}</p> <p>{{ user }}</p>
</div> </div>
</div> </div>
<div class="chatbox-message-box"> <div class="chatbox-message-box">
<div class="chatbox__messages" *ngFor="let m of messages"> <div class="chatbox__messages" *ngFor="let m of messages">
<div class="chatbox__messages__user-message"> <div class="chatbox__messages__user-message">
<div class="chatbox__messages__user-message--ind-message" [ngClass]="{'sender': m.chatType === ChatType.Sender, 'reciever':m.chatType === ChatType.Reciever }"> <div
<p class="name" *ngIf="m?.username">{{m.username}}</p> class="chatbox__messages__user-message--ind-message"
<br/> [ngClass]="{ sender: m.chatType === ChatType.Sender, reciever: m.chatType === ChatType.Reciever }"
<p class="message">{{m.message}}</p> >
<p class="timestamp">{{m.date | amFromUtc | amLocal | amUserLocale | amDateFormat: 'l LT'}}</p> <p class="name" *ngIf="m?.username">{{ m.username }}</p>
</div> <br />
</div> <p class="message">{{ m.message }}</p>
</div> <p class="timestamp">{{ m.date | dfnsFormat: 'Ppp' }}</p>
</div> </div>
<div class="form"> </div>
<input type="text" [(ngModel)]="currentMessage" [placeholder]="'Issues.EnterYourMessage' | translate"> </div>
<button mat-raised-button class="add-message" (click)="addMessage()">{{ "Issues.SendMessageButton" | translate }}</button> </div>
</div> <div class="form">
</div> <input type="text" [(ngModel)]="currentMessage" [placeholder]="'Issues.EnterYourMessage' | translate" />
<button mat-raised-button class="add-message" (click)="addMessage()">{{ 'Issues.SendMessageButton' | translate }}</button>
</div>
</div>
</div>

@ -1,74 +1,93 @@
<div mat-dialog-content class="background"> <div mat-dialog-content class="background">
<div *ngIf="!requestable"> <div *ngIf="!requestable">
{{'MediaDetails.EpisodeSelector.NoEpisodes' | translate}} {{ 'MediaDetails.EpisodeSelector.NoEpisodes' | translate }}
</div> </div>
<div *ngIf="requestable" class="row"> <div *ngIf="requestable" class="row">
<div class="col-12 action-buttons-right"> <div class="col-12 action-buttons-right">
<button id="episodeModalAllSeasons" (click)="requestAllSeasons()" color="primary" mat-raised-button class="btn-spacing" <button
matTooltip="{{'MediaDetails.EpisodeSelector.AllSeasonsTooltip' | translate}}">{{'Search.TvShows.AllSeasons' | translate }}</button> id="episodeModalAllSeasons"
(click)="requestAllSeasons()"
<button id="episodeModalFirstSeason" (click)="requestFirstSeason()" color="accent" mat-raised-button class="btn-spacing" color="primary"
matTooltip="{{'MediaDetails.EpisodeSelector.FirstSeasonTooltip' | translate}}">{{ 'Search.TvShows.FirstSeason' | translate }}</button> mat-raised-button
<button id="episodeModalLatestSeason" (click)="requestLatestSeason()" color="warn" mat-raised-button class="btn-spacing" class="btn-spacing"
matTooltip="{{'MediaDetails.EpisodeSelector.LatestSeasonTooltip' | translate}}">{{ 'Search.TvShows.LatestSeason' | translate }}</button> matTooltip="{{ 'MediaDetails.EpisodeSelector.AllSeasonsTooltip' | translate }}"
</div> >
</div> {{ 'Search.TvShows.AllSeasons' | translate }}
</button>
<div class="row">
<div class="col-12" *ngFor="let season of data.series.seasonRequests">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<mat-checkbox *ngIf="!season.seasonAvailable && isSeasonCheckable(season)" (click)="$event.stopPropagation();" (change)="seasonChanged($event, season)">
{{ 'MediaDetails.EpisodeSelector.SeasonNumber' | translate: { number: season.seasonNumber } }}</mat-checkbox>
<span *ngIf="season.seasonAvailable || !isSeasonCheckable(season)">{{ 'MediaDetails.EpisodeSelector.SeasonNumber' | translate: { number: season.seasonNumber } }}</span>
</mat-panel-title>
<mat-panel-description>
<!-- Description -->
</mat-panel-description>
</mat-expansion-panel-header>
<div class="row" *ngFor="let ep of season.episodes">
<div class="col-1">
<div *ngIf="!ep.available && !ep.requested && !ep.approved">
<mat-checkbox *ngIf="!ep.selected" [ngModel]="ep.selected" (click)="addRequest(ep)"></mat-checkbox>
<mat-checkbox *ngIf="ep.selected" [ngModel]="ep.selected" (click)="removeRequest(ep)"></mat-checkbox>
</div>
</div>
<div class="col-1">
{{ep.episodeNumber}}
</div>
<div class="col-3">
{{ep.title}}
</div>
<div class="col-2" *ngIf="ep.airDateDisplay != 'Unknown'">
{{ep.airDate | amLocal | amUserLocale | amDateFormat: 'L' }}
</div>
<div class="col-2" *ngIf="ep.airDateDisplay == 'Unknown'">
{{ep.airDateDisplay }}
</div>
<div class="col-3">
{{ep.requestStatus | translate}}
</div>
</div>
</mat-expansion-panel> <button
id="episodeModalFirstSeason"
(click)="requestFirstSeason()"
color="accent"
mat-raised-button
class="btn-spacing"
matTooltip="{{ 'MediaDetails.EpisodeSelector.FirstSeasonTooltip' | translate }}"
>
{{ 'Search.TvShows.FirstSeason' | translate }}
</button>
<button
id="episodeModalLatestSeason"
(click)="requestLatestSeason()"
color="warn"
mat-raised-button
class="btn-spacing"
matTooltip="{{ 'MediaDetails.EpisodeSelector.LatestSeasonTooltip' | translate }}"
>
{{ 'Search.TvShows.LatestSeason' | translate }}
</button>
</div>
</div>
<div class="row">
<div class="col-12" *ngFor="let season of data.series.seasonRequests">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<mat-checkbox
*ngIf="!season.seasonAvailable && isSeasonCheckable(season)"
(click)="$event.stopPropagation()"
(change)="seasonChanged($event, season)"
>
{{ 'MediaDetails.EpisodeSelector.SeasonNumber' | translate: { number: season.seasonNumber } }}</mat-checkbox
>
<span *ngIf="season.seasonAvailable || !isSeasonCheckable(season)">{{
'MediaDetails.EpisodeSelector.SeasonNumber' | translate: { number: season.seasonNumber }
}}</span>
</mat-panel-title>
<mat-panel-description>
<!-- Description -->
</mat-panel-description>
</mat-expansion-panel-header>
<div class="row" *ngFor="let ep of season.episodes">
</div> <div class="col-1">
<div *ngIf="!ep.available && !ep.requested && !ep.approved">
<mat-checkbox *ngIf="!ep.selected" [ngModel]="ep.selected" (click)="addRequest(ep)"></mat-checkbox>
</div> <mat-checkbox *ngIf="ep.selected" [ngModel]="ep.selected" (click)="removeRequest(ep)"></mat-checkbox>
</div>
</div>
<div class="col-1">
{{ ep.episodeNumber }}
</div>
<div class="col-3">
{{ ep.title }}
</div>
<div class="col-2" *ngIf="ep.airDateDisplay != 'Unknown'">
{{ ep.airDate | dfnsFormat: 'P' }}
</div>
<div class="col-2" *ngIf="ep.airDateDisplay == 'Unknown'">
{{ ep.airDateDisplay }}
</div>
<div class="col-3">
{{ ep.requestStatus | translate }}
</div>
</div>
</mat-expansion-panel>
</div>
</div>
</div> </div>
<div mat-dialog-actions *ngIf="requestable"> <div mat-dialog-actions *ngIf="requestable">
<div class="action-buttons-right"> <div class="action-buttons-right">
<button (click)="submitRequests()" mat-raised-button class="btn-spacing btn-orange">{{ 'Common.Request' | translate }}</button>
<button (click)="submitRequests()" mat-raised-button class="btn-spacing btn-orange">{{ </div>
'Common.Request' | translate }}</button>
</div>
</div> </div>

@ -1,141 +1,141 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-request-dialog.component"; import { AdminRequestDialogComponent } from './admin-request-dialog/admin-request-dialog.component';
import { AdvancedSearchDialogComponent } from "./advanced-search-dialog/advanced-search-dialog.component"; import { AdvancedSearchDialogComponent } from './advanced-search-dialog/advanced-search-dialog.component';
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component"; import { DetailsGroupComponent } from '../issues/components/details-group/details-group.component';
import { EpisodeRequestComponent } from "./episode-request/episode-request.component"; import { EpisodeRequestComponent } from './episode-request/episode-request.component';
import { GenreSelectComponent } from "./components/genre-select/genre-select.component"; import { GenreSelectComponent } from './components/genre-select/genre-select.component';
import { InputSwitchModule } from "primeng/inputswitch"; import { InputSwitchModule } from 'primeng/inputswitch';
import { IssuesReportComponent } from "./issues-report.component"; import { IssuesReportComponent } from './issues-report.component';
import { KeywordSearchComponent } from "./components/keyword-search/keyword-search.component"; import { KeywordSearchComponent } from './components/keyword-search/keyword-search.component';
import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { MatAutocompleteModule } from '@angular/material/autocomplete';
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 { MatCheckboxModule } from "@angular/material/checkbox"; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from "@angular/material/chips"; import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from "@angular/material/dialog"; import { MatDialogModule } from '@angular/material/dialog';
import { MatExpansionModule } from "@angular/material/expansion"; import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from "@angular/material/input"; import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
import {MatMenuModule} from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatNativeDateModule } from '@angular/material/core'; import { MatNativeDateModule } from '@angular/material/core';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatProgressBarModule } from "@angular/material/progress-bar"; import { MatProgressBarModule } from '@angular/material/progress-bar';
import {MatRadioModule} from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav'; import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import { MatStepperModule } from '@angular/material/stepper'; import { MatStepperModule } from '@angular/material/stepper';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from "@angular/material/tabs"; import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatTreeModule } from '@angular/material/tree'; import { MatTreeModule } from '@angular/material/tree';
import { MomentModule } from "ngx-moment"; import { NgModule } from '@angular/core';
import { NgModule } from "@angular/core"; import { PipeModule } from '../pipes/pipe.module';
import { PipeModule } from "../pipes/pipe.module"; import { RoleModule } from './role-directive/role.module';
import { RoleModule } from "./role-directive/role.module"; import { SidebarModule } from 'primeng/sidebar';
import { SidebarModule } from "primeng/sidebar"; import { TranslateModule } from '@ngx-translate/core';
import { TranslateModule } from "@ngx-translate/core"; import { TruncateModule } from '@yellowspot/ng-truncate';
import { TruncateModule } from "@yellowspot/ng-truncate"; import { WatchProvidersSelectComponent } from './components/watch-providers-select/watch-providers-select.component';
import { WatchProvidersSelectComponent } from "./components/watch-providers-select/watch-providers-select.component"; import { DateFnsModule } from 'ngx-date-fns';
@NgModule({ @NgModule({
declarations: [ declarations: [
IssuesReportComponent, IssuesReportComponent,
EpisodeRequestComponent, EpisodeRequestComponent,
DetailsGroupComponent, DetailsGroupComponent,
AdminRequestDialogComponent, AdminRequestDialogComponent,
AdvancedSearchDialogComponent, AdvancedSearchDialogComponent,
KeywordSearchComponent, KeywordSearchComponent,
GenreSelectComponent, GenreSelectComponent,
WatchProvidersSelectComponent, WatchProvidersSelectComponent,
], ],
imports: [ imports: [
RoleModule, RoleModule,
SidebarModule, SidebarModule,
ReactiveFormsModule, ReactiveFormsModule,
FormsModule, FormsModule,
CommonModule, CommonModule,
InputSwitchModule, InputSwitchModule,
TruncateModule, TruncateModule,
MomentModule, MatCardModule,
MatCardModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatProgressBarModule,
MatProgressBarModule, MatAutocompleteModule,
MatAutocompleteModule, MatInputModule,
MatInputModule, MatTabsModule,
MatTabsModule, MatRadioModule,
MatRadioModule, MatButtonModule,
MatButtonModule, MatNativeDateModule,
MatNativeDateModule, MatChipsModule,
MatChipsModule, MatIconModule,
MatIconModule, MatMenuModule,
MatMenuModule, MatSidenavModule,
MatSidenavModule, MatListModule,
MatListModule, MatToolbarModule,
MatToolbarModule, MatCheckboxModule,
MatCheckboxModule, TranslateModule,
TranslateModule, MatExpansionModule,
MatExpansionModule, MatDialogModule,
MatDialogModule, MatTooltipModule,
MatTooltipModule, MatSelectModule,
MatSelectModule, MatPaginatorModule,
MatPaginatorModule, MatSortModule,
MatSortModule, MatTreeModule,
MatTreeModule, MatStepperModule,
MatStepperModule, MatSnackBarModule,
MatSnackBarModule, PipeModule,
PipeModule, ],
], exports: [
exports: [ RoleModule,
RoleModule, TranslateModule,
TranslateModule, CommonModule,
CommonModule, FormsModule,
FormsModule, TranslateModule,
TranslateModule, SidebarModule,
SidebarModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatProgressBarModule,
MatProgressBarModule, IssuesReportComponent,
IssuesReportComponent, EpisodeRequestComponent,
EpisodeRequestComponent, AdminRequestDialogComponent,
AdminRequestDialogComponent, AdvancedSearchDialogComponent,
AdvancedSearchDialogComponent, GenreSelectComponent,
GenreSelectComponent, KeywordSearchComponent,
KeywordSearchComponent, WatchProvidersSelectComponent,
WatchProvidersSelectComponent, DetailsGroupComponent,
DetailsGroupComponent, TruncateModule,
TruncateModule, InputSwitchModule,
InputSwitchModule, MatTreeModule,
MatTreeModule, MatCardModule,
MomentModule,MatCardModule, MatInputModule,
MatInputModule, MatTabsModule,
MatTabsModule, MatChipsModule,
MatChipsModule, MatButtonModule,
MatButtonModule, MatNativeDateModule,
MatNativeDateModule, MatIconModule,
MatIconModule, MatMenuModule,
MatMenuModule, MatSnackBarModule,
MatSnackBarModule, MatSidenavModule,
MatSidenavModule, MatSelectModule,
MatSelectModule, MatListModule,
MatListModule, MatToolbarModule,
MatToolbarModule, MatTooltipModule,
MatTooltipModule, MatAutocompleteModule,
MatAutocompleteModule, MatCheckboxModule,
MatCheckboxModule, MatExpansionModule,
MatExpansionModule, MatDialogModule,
MatDialogModule, MatTableModule,
MatTableModule, MatPaginatorModule,
MatPaginatorModule, MatSortModule,
MatSortModule, MatStepperModule,
MatStepperModule, MatSlideToggleModule,
MatSlideToggleModule, DateFnsModule,
], ],
}) })
export class SharedModule {} export class SharedModule {}

@ -1,191 +1,201 @@
<div class="small-middle-container"> <div class="small-middle-container">
<div class="buttons"> <div class="buttons">
<a type="button" mat-raised-button color="primary" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</a>
<a type="button" mat-raised-button color="primary" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</a> <button
type="button"
<button type="button" style="float:right;" mat-raised-button color="primary" (click)="showBulkEdit = !showBulkEdit" [disabled]="this.selection.selected.length <= 0">Bulk Edit</button> style="float: right"
mat-raised-button
</div> color="primary"
<div class="content" > (click)="showBulkEdit = !showBulkEdit"
<table mat-table *ngIf="dataSource" [dataSource]="dataSource" matSort class="mat-elevation-z8"> [disabled]="this.selection.selected.length <= 0"
>
<ng-container matColumnDef="select"> Bulk Edit
<th mat-header-cell *matHeaderCellDef> </button>
<mat-checkbox (change)="$event ? masterToggle() : null" </div>
[checked]="selection.hasValue() && isAllSelected()" <div class="content">
[indeterminate]="selection.hasValue() && !isAllSelected()" <table mat-table *ngIf="dataSource" [dataSource]="dataSource" matSort class="mat-elevation-z8">
[aria-label]="checkboxLabel()"> <ng-container matColumnDef="select">
</mat-checkbox> <th mat-header-cell *matHeaderCellDef>
</th> <mat-checkbox
<td mat-cell *matCellDef="let row"> (change)="$event ? masterToggle() : null"
<mat-checkbox (click)="$event.stopPropagation()" [checked]="selection.hasValue() && isAllSelected()"
(change)="$event ? selection.toggle(row) : null" [indeterminate]="selection.hasValue() && !isAllSelected()"
[checked]="selection.isSelected(row)" [aria-label]="checkboxLabel()"
[aria-label]="checkboxLabel(row)"> >
</mat-checkbox> </mat-checkbox>
</td> </th>
</ng-container> <td mat-cell *matCellDef="let row">
<mat-checkbox
<ng-container matColumnDef="username"> (click)="$event.stopPropagation()"
<th mat-header-cell *matHeaderCellDef mat-sort-header> Username </th> (change)="$event ? selection.toggle(row) : null"
<td mat-cell *matCellDef="let element"> {{element.userName}} </td> [checked]="selection.isSelected(row)"
</ng-container> [aria-label]="checkboxLabel(row)"
>
<ng-container matColumnDef="alias"> </mat-checkbox>
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th> </td>
<td mat-cell *matCellDef="let element"> {{element.alias}} </td> </ng-container>
</ng-container>
<ng-container matColumnDef="username">
<ng-container matColumnDef="email"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Username</th>
<th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th> <td mat-cell *matCellDef="let element">{{ element.userName }}</td>
<td mat-cell *matCellDef="let element"> {{element.emailAddress}} </td> </ng-container>
</ng-container>
<ng-container matColumnDef="alias">
<ng-container matColumnDef="remainingRequests"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Alias</th>
<th mat-header-cell *matHeaderCellDef> Requests Remaining </th> <td mat-cell *matCellDef="let element">{{ element.alias }}</td>
<td mat-cell *matCellDef="let u"> </ng-container>
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.hasLimit">
{{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }} <ng-container matColumnDef="email">
</div> <th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit"> <td mat-cell *matCellDef="let element">{{ element.emailAddress }}</td>
{{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }} </ng-container>
</div>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.hasLimit"> <ng-container matColumnDef="remainingRequests">
{{'UserManagment.MusicRemaining' | translate: {remaining: u.musicRequestQuota.remaining, total: u.musicRequestLimit} }} <th mat-header-cell *matHeaderCellDef>Requests Remaining</th>
</div> <td mat-cell *matCellDef="let u">
</td> <div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.hasLimit">
</ng-container> {{ 'UserManagment.MovieRemaining' | translate: { remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit } }}
</div>
<ng-container matColumnDef="nextRequestDue"> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit">
<th mat-header-cell *matHeaderCellDef> Next Request Due </th> {{ 'UserManagment.TvRemaining' | translate: { remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit } }}
<td mat-cell *matCellDef="let u"> </div>
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit"> <div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.hasLimit">
{{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | amLocal | amUserLocale | amDateFormat: 'l')} }} {{ 'UserManagment.MusicRemaining' | translate: { remaining: u.musicRequestQuota.remaining, total: u.musicRequestLimit } }}
</div> </div>
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit"> </td>
{{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | amLocal | amUserLocale | amDateFormat: 'l')} }} </ng-container>
</div>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.remaining != u.musicRequestLimit"> <ng-container matColumnDef="nextRequestDue">
{{'UserManagment.MusicDue' | translate: {date: (u.musicRequestQuota.nextRequest | amLocal | amUserLocale | amDateFormat: 'l')} }} <th mat-header-cell *matHeaderCellDef>Next Request Due</th>
</div> <td mat-cell *matCellDef="let u">
</td> <div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit">
</ng-container> {{ 'UserManagment.MovieDue' | translate: { date: (u.movieRequestQuota.nextRequest | dfnsFormat: 'P') } }}
<ng-container matColumnDef="lastLoggedIn"> </div>
<th mat-header-cell *matHeaderCellDef mat-sort-header> Last Logged In </th> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit">
<td mat-cell *matCellDef="let u"> {{ 'UserManagment.TvDue' | translate: { date: (u.episodeRequestQuota.nextRequest | dfnsFormat: 'P') } }}
<span *ngIf="u.lastLoggedIn"> </div>
{{u.lastLoggedIn | amFromUtc | amLocal | amUserLocale | amDateFormat: 'l LT'}} <div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.remaining != u.musicRequestLimit">
</span> {{ 'UserManagment.MusicDue' | translate: { date: (u.musicRequestQuota.nextRequest | dfnsFormat: 'P') } }}
<span *ngIf="!u.lastLoggedIn"> </div>
Not logged in yet! </td>
</span> </td> </ng-container>
</ng-container> <ng-container matColumnDef="lastLoggedIn">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Last Logged In</th>
<ng-container matColumnDef="userType"> <td mat-cell *matCellDef="let u">
<th mat-header-cell *matHeaderCellDef mat-sort-header> User Type </th> <span *ngIf="u.lastLoggedIn">
<td mat-cell *matCellDef="let u"> {{ u.lastLoggedIn | dfnsFormat: 'Ppp' }}
<span *ngIf="u.userType === 1">Local User</span> </span>
<span *ngIf="u.userType === 2">Plex User</span> <span *ngIf="!u.lastLoggedIn"> Not logged in yet! </span>
<span *ngIf="u.userType === 3">Emby User</span> </td>
<span *ngIf="u.userType === 4">Emby Connect User</span> </ng-container>
<span *ngIf="u.userType === 5">Jellyfin User</span>
</td> <ng-container matColumnDef="userType">
</ng-container> <th mat-header-cell *matHeaderCellDef mat-sort-header>User Type</th>
<td mat-cell *matCellDef="let u">
<ng-container matColumnDef="roles"> <span *ngIf="u.userType === 1">Local User</span>
<th mat-header-cell *matHeaderCellDef> Roles </th> <span *ngIf="u.userType === 2">Plex User</span>
<td mat-cell *matCellDef="let element"> <span *ngIf="u.userType === 3">Emby User</span>
<div *ngFor="let claim of element.claims"> <span *ngIf="u.userType === 4">Emby Connect User</span>
<span *ngIf="claim.enabled">{{claim.value}}</span> <span *ngIf="u.userType === 5">Jellyfin User</span>
</div> </td>
</td> </ng-container>
</ng-container>
<ng-container matColumnDef="roles">
<ng-container matColumnDef="actions"> <th mat-header-cell *matHeaderCellDef>Roles</th>
<th mat-header-cell *matHeaderCellDef> </th> <td mat-cell *matCellDef="let element">
<td mat-cell *matCellDef="let u"> <div *ngFor="let claim of element.claims">
<a id="edit{{u.userName}}" mat-raised-button color="accent" [routerLink]="['/usermanagement/user/' + u.id]">Edit</a> <span *ngIf="claim.enabled">{{ claim.value }}</span>
<button *ngIf="!u.hasLoggedIn" mat-raised-button color="accent" (click)="welcomeEmail(u)" [disabled]="!applicationUrl"><i class="far fa-paper-plane"></i> Welcome</button> </div>
</td> </td>
</ng-container> </ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <ng-container matColumnDef="actions">
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <th mat-header-cell *matHeaderCellDef></th>
</table> <td mat-cell *matCellDef="let u">
<a id="edit{{ u.userName }}" mat-raised-button color="accent" [routerLink]="['/usermanagement/user/' + u.id]">Edit</a>
<!-- Table --> <button *ngIf="!u.hasLoggedIn" mat-raised-button color="accent" (click)="welcomeEmail(u)" [disabled]="!applicationUrl">
<i class="far fa-paper-plane"></i> Welcome
</button>
</td>
<p-sidebar [(visible)]="showBulkEdit" position="right" [modal]="false" [style]="{width:'40em'}"> </ng-container>
<div>
<div *ngFor="let c of availableClaims"> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<div class="form-group"> <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<div class="checkbox"> </table>
<mat-slide-toggle id="create{{c.value}}" [(ngModel)]="c.enabled" [attr.name]="'create' + c.value">
<small>{{c.value | humanize}}</small></mat-slide-toggle> <!-- Table -->
</div>
</div> <p-sidebar [(visible)]="showBulkEdit" position="right" [modal]="false" [style]="{ width: '40em' }">
</div> <div>
</div> <div *ngFor="let c of availableClaims">
<div class="row"> <div class="form-group">
<div class="col-6"> <div class="checkbox">
<mat-form-field appearance="outline" class="full"> <mat-slide-toggle id="create{{ c.value }}" [(ngModel)]="c.enabled" [attr.name]="'create' + c.value">
<mat-label>Movie Request Limit</mat-label> <small>{{ c.value | humanize }}</small></mat-slide-toggle
<input matInput id="movieRequestLimit" name="movieRequestLimit" [(ngModel)]="bulkMovieLimit"> >
</mat-form-field></div> </div>
<div class="col-6"> </div>
<mat-label>Movie Request Limit Type</mat-label> </div>
<mat-select id="movieRequestLimitType" [(value)]="movieRequestLimitType"> </div>
<mat-option *ngFor="let value of requestLimitTypes" [value]="value"> <div class="row">
{{RequestLimitType[value]}} <div class="col-6">
</mat-option> <mat-form-field appearance="outline" class="full">
</mat-select> <mat-label>Movie Request Limit</mat-label>
</div></div> <input matInput id="movieRequestLimit" name="movieRequestLimit" [(ngModel)]="bulkMovieLimit" />
<div class="row"> </mat-form-field>
<div class="col-6"> </div>
<mat-form-field appearance="outline" class="full"> <div class="col-6">
<mat-label>Episode Request Limit</mat-label> <mat-label>Movie Request Limit Type</mat-label>
<input matInput id="episodeRequestLimit" name="episodeRequestLimit" [(ngModel)]="bulkEpisodeLimit"> <mat-select id="movieRequestLimitType" [(value)]="movieRequestLimitType">
</mat-form-field> <mat-option *ngFor="let value of requestLimitTypes" [value]="value">
</div> {{ RequestLimitType[value] }}
<div class="col-6"> </mat-option>
<mat-label>Episode Request Limit Type</mat-label> </mat-select>
<mat-select id="episodeRequestLimitType" [(value)]="episodeRequestLimitType"> </div>
<mat-option *ngFor="let value of requestLimitTypes" [value]="value"> </div>
{{RequestLimitType[value]}} <div class="row">
</mat-option> <div class="col-6">
</mat-select> <mat-form-field appearance="outline" class="full">
</div> <mat-label>Episode Request Limit</mat-label>
</div> <input matInput id="episodeRequestLimit" name="episodeRequestLimit" [(ngModel)]="bulkEpisodeLimit" />
<div class="row"> </mat-form-field>
<div class="col-6"> </div>
<mat-form-field appearance="outline" class="full"> <div class="col-6">
<mat-label>Music Request Limit</mat-label> <mat-label>Episode Request Limit Type</mat-label>
<input matInput id="musicRequestLimit" name="musicRequestLimit" [(ngModel)]="bulkMusicLimit"> <mat-select id="episodeRequestLimitType" [(value)]="episodeRequestLimitType">
</mat-form-field> <mat-option *ngFor="let value of requestLimitTypes" [value]="value">
</div> {{ RequestLimitType[value] }}
<div class="col-6"> </mat-option>
<mat-label>Music Request Limit Type</mat-label> </mat-select>
<mat-select id="musicRequestLimitType" [(value)]="musicRequestLimitType"> </div>
<mat-option *ngFor="let value of requestLimitTypes" [value]="value"> </div>
{{RequestLimitType[value]}} <div class="row">
</mat-option> <div class="col-6">
</mat-select> <mat-form-field appearance="outline" class="full">
</div> <mat-label>Music Request Limit</mat-label>
</div> <input matInput id="musicRequestLimit" name="musicRequestLimit" [(ngModel)]="bulkMusicLimit" />
<mat-form-field appearance="outline" class="full"> </mat-form-field>
<mat-label [translate]="'UserPreferences.StreamingCountry'"></mat-label> </div>
<mat-select [(value)]="bulkStreaming"> <div class="col-6">
<mat-option *ngFor="let value of countries" [value]="value"> <mat-label>Music Request Limit Type</mat-label>
{{value}} <mat-select id="musicRequestLimitType" [(value)]="musicRequestLimitType">
</mat-option> <mat-option *ngFor="let value of requestLimitTypes" [value]="value">
</mat-select> {{ RequestLimitType[value] }}
</mat-form-field> </mat-option>
</mat-select>
</div>
<button type="button" mat-raised-button color="primary" (click)="bulkUpdate()">Update Users</button> </div>
</p-sidebar> <mat-form-field appearance="outline" class="full">
</div> <mat-label [translate]="'UserPreferences.StreamingCountry'"></mat-label>
<mat-select [(value)]="bulkStreaming">
<mat-option *ngFor="let value of countries" [value]="value">
{{ value }}
</mat-option>
</mat-select>
</mat-form-field>
<button type="button" mat-raised-button color="primary" (click)="bulkUpdate()">Update Users</button>
</p-sidebar>
</div>
</div> </div>

@ -1,53 +1,40 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IdentityService, PlexService, RadarrService, SonarrService } from "../services"; import { IdentityService, PlexService, RadarrService, SonarrService } from '../services';
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from "../auth/auth.guard"; import { AuthGuard } from '../auth/auth.guard';
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { ConfirmDialogModule } from "primeng/confirmdialog"; import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { MultiSelectModule } from "primeng/multiselect"; import { MultiSelectModule } from 'primeng/multiselect';
import { NgModule } from "@angular/core"; import { NgModule } from '@angular/core';
import { OrderModule } from "ngx-order-pipe"; import { PipeModule } from '../pipes/pipe.module';
import { PipeModule } from "../pipes/pipe.module"; import { SharedModule } from '../shared/shared.module';
import { SharedModule } from "../shared/shared.module"; import { SidebarModule } from 'primeng/sidebar';
import { SidebarModule } from "primeng/sidebar"; import { TooltipModule } from 'primeng/tooltip';
import { TooltipModule } from "primeng/tooltip"; import { UserManagementComponent } from './usermanagement.component';
import { UserManagementComponent } from "./usermanagement.component"; import { UserManagementUserComponent } from './usermanagement-user.component';
import { UserManagementUserComponent } from "./usermanagement-user.component";
const routes: Routes = [ const routes: Routes = [
{ path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, { path: '', component: UserManagementComponent, canActivate: [AuthGuard] },
{ path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] }, { path: 'user', component: UserManagementUserComponent, canActivate: [AuthGuard] },
{ path: "user/:id", component: UserManagementUserComponent, canActivate: [AuthGuard] }, { path: 'user/:id', component: UserManagementUserComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
MultiSelectModule, MultiSelectModule,
PipeModule, PipeModule,
ConfirmDialogModule, ConfirmDialogModule,
TooltipModule, TooltipModule,
OrderModule, SidebarModule,
SidebarModule, SharedModule,
SharedModule, ],
], declarations: [UserManagementComponent, UserManagementUserComponent],
declarations: [ exports: [RouterModule],
UserManagementComponent, providers: [IdentityService, PlexService, RadarrService, SonarrService],
UserManagementUserComponent,
],
exports: [
RouterModule,
],
providers: [
IdentityService,
PlexService,
RadarrService,
SonarrService,
],
}) })
export class UserManagementModule { } export class UserManagementModule {}

@ -1,39 +1,23 @@
import { NgModule } from "@angular/core"; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from '@angular/router';
import { OrderModule } from "ngx-order-pipe"; import { OverlayPanelModule } from 'primeng/overlaypanel';
import { OverlayPanelModule } from "primeng/overlaypanel"; import { TabViewModule } from 'primeng/tabview';
import { TabViewModule } from "primeng/tabview";
import { VoteService } from "../services"; import { VoteService } from '../services';
import { AuthGuard } from "../auth/auth.guard"; import { AuthGuard } from '../auth/auth.guard';
import { SharedModule as OmbiShared } from "../shared/shared.module"; import { SharedModule as OmbiShared } from '../shared/shared.module';
import { VoteComponent } from "./vote.component"; import { VoteComponent } from './vote.component';
const routes: Routes = [ const routes: Routes = [{ path: '', component: VoteComponent, canActivate: [AuthGuard] }];
{ path: "", component: VoteComponent, canActivate: [AuthGuard] },
];
@NgModule({ @NgModule({
imports: [ imports: [RouterModule.forChild(routes), OmbiShared, TabViewModule, OverlayPanelModule],
RouterModule.forChild(routes), declarations: [VoteComponent],
OrderModule, exports: [RouterModule],
OmbiShared, providers: [VoteService],
TabViewModule,
OverlayPanelModule,
],
declarations: [
VoteComponent,
],
exports: [
RouterModule,
],
providers: [
VoteService,
],
}) })
export class VoteModule { } export class VoteModule {}

@ -1,5 +1,4 @@
/*************************************************************************************************** /***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/ */
import "core-js/es7/reflect"; import 'core-js/es7/reflect';
import "zone.js/dist/zone";

@ -16,7 +16,10 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types",
"./node_modules/@types",
"../node_modules/@types",
"../../node_modules/@types"
], ],
"lib": [ "lib": [
"es2017", "es2017",

@ -14,16 +14,21 @@
"declaration": false, "declaration": false,
"downlevelIteration": true, "downlevelIteration": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"useDefineForClassFields": false, "useDefineForClassFields": false,
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
"lib": [ "lib": [
"ES2022", "ES2022",
"dom" "dom",
"ESNext.Intl",
"ES2022.Intl"
] ]
}, },
"types": [
"node"
],
"typeRoots": [ "./node_modules/@types", "../node_modules/@types", "../../node_modules/@types" ],
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false, "enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true, "strictInjectionParameters": true,

File diff suppressed because it is too large Load Diff

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Api.Radarr; using Ombi.Api.Radarr;
using Ombi.Api.Radarr.Models; using Ombi.Api.Radarr.Models;
using Ombi.Api.Radarr.Models.V3;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
@ -77,7 +78,7 @@ namespace Ombi.Controllers.V1.External
{ {
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri)); return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
} }
return null; return Ok(new List<RadarrV3QualityProfile>());
} }
/// <summary> /// <summary>
@ -94,7 +95,7 @@ namespace Ombi.Controllers.V1.External
{ {
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri)); return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
} }
return null; return Ok(new List<RadarrV3QualityProfile>());
} }
/// <summary> /// <summary>

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi.csproj", "{41251D1C-AAFD-4B5B-9510-BB50B9C223EA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{41251D1C-AAFD-4B5B-9510-BB50B9C223EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41251D1C-AAFD-4B5B-9510-BB50B9C223EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41251D1C-AAFD-4B5B-9510-BB50B9C223EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41251D1C-AAFD-4B5B-9510-BB50B9C223EA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {291EBBD3-9266-41E3-9E52-A50070394075}
EndGlobalSection
EndGlobal
Loading…
Cancel
Save