mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-14 09:12:57 -07:00
Got a bit of the work done, needs some polish then tackle tv
This commit is contained in:
parent
873823017c
commit
c5f123b903
35 changed files with 356 additions and 820 deletions
|
@ -69,11 +69,8 @@ namespace Ombi.Core.Engine
|
||||||
var userDetails = await GetUser();
|
var userDetails = await GetUser();
|
||||||
var canRequestOnBehalf = false;
|
var canRequestOnBehalf = false;
|
||||||
|
|
||||||
if (model.RequestOnBehalf.HasValue())
|
var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
|
||||||
{
|
if (model.RequestOnBehalf.HasValue() && !isAdmin)
|
||||||
canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
|
|
||||||
|
|
||||||
if (!canRequestOnBehalf)
|
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
{
|
{
|
||||||
|
@ -82,6 +79,15 @@ namespace Ombi.Core.Engine
|
||||||
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
|
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((model.RootFolderOverride.HasValue || model.QualityPathOverride.HasValue) && !isAdmin)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = "You do not have the correct permissions!",
|
||||||
|
ErrorMessage = $"You do not have the correct permissions!"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestModel = new MovieRequests
|
var requestModel = new MovieRequests
|
||||||
|
@ -101,7 +107,9 @@ namespace Ombi.Core.Engine
|
||||||
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
|
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
|
||||||
Background = movieInfo.BackdropPath,
|
Background = movieInfo.BackdropPath,
|
||||||
LangCode = model.LanguageCode,
|
LangCode = model.LanguageCode,
|
||||||
RequestedByAlias = model.RequestedByAlias
|
RequestedByAlias = model.RequestedByAlias,
|
||||||
|
RootPathOverride = model.RootFolderOverride.GetValueOrDefault(),
|
||||||
|
QualityOverride = model.QualityPathOverride.GetValueOrDefault()
|
||||||
};
|
};
|
||||||
|
|
||||||
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||||
|
|
39
src/Ombi.Core/Models/Requests/BaseRequestOptions.cs
Normal file
39
src/Ombi.Core/Models/Requests/BaseRequestOptions.cs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#region Copyright
|
||||||
|
// /************************************************************************
|
||||||
|
// Copyright (c) 2018 Jamie Rees
|
||||||
|
// File: MovieRequestViewModel.cs
|
||||||
|
// Created By: Jamie Rees
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
// a copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
// the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// ************************************************************************/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models.Requests
|
||||||
|
{
|
||||||
|
|
||||||
|
public class BaseRequestOptions
|
||||||
|
{
|
||||||
|
public string RequestOnBehalf { get; set; }
|
||||||
|
public int? RootFolderOverride { get; set; }
|
||||||
|
public int? QualityPathOverride { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,11 +29,10 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Ombi.Core.Models.Requests
|
namespace Ombi.Core.Models.Requests
|
||||||
{
|
{
|
||||||
public class MovieRequestViewModel
|
public class MovieRequestViewModel : BaseRequestOptions
|
||||||
{
|
{
|
||||||
public int TheMovieDbId { get; set; }
|
public int TheMovieDbId { get; set; }
|
||||||
public string LanguageCode { get; set; } = "en";
|
public string LanguageCode { get; set; } = "en";
|
||||||
public string RequestOnBehalf { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is only set from a HTTP Header
|
/// This is only set from a HTTP Header
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Ombi.Core.Models.Requests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class TvRequestViewModelBase
|
public class TvRequestViewModelBase : BaseRequestOptions
|
||||||
{
|
{
|
||||||
public bool RequestAll { get; set; }
|
public bool RequestAll { get; set; }
|
||||||
public bool LatestSeason { get; set; }
|
public bool LatestSeason { get; set; }
|
||||||
|
@ -28,7 +28,5 @@ namespace Ombi.Core.Models.Requests
|
||||||
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
|
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string RequestedByAlias { get; set; }
|
public string RequestedByAlias { get; set; }
|
||||||
|
|
||||||
public string RequestOnBehalf { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ namespace Ombi.Core.Rule.Rules.Search
|
||||||
{
|
{
|
||||||
// If we have all the episodes for this season, then this season is available
|
// If we have all the episodes for this season, then this season is available
|
||||||
if (season.Episodes.All(x => x.Available))
|
if (season.Episodes.All(x => x.Available))
|
||||||
{yarn
|
{
|
||||||
season.SeasonAvailable = true;
|
season.SeasonAvailable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,6 @@ namespace Ombi.Helpers
|
||||||
public const string LidarrRootFolders = nameof(LidarrRootFolders);
|
public const string LidarrRootFolders = nameof(LidarrRootFolders);
|
||||||
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
|
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
|
||||||
public const string FanartTv = nameof(FanartTv);
|
public const string FanartTv = nameof(FanartTv);
|
||||||
|
public const string UsersDropdown = nameof(UsersDropdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,171 +0,0 @@
|
||||||
<div class="spinner-container">
|
|
||||||
<mat-spinner *ngIf="loading" [color]="'accent'"></mat-spinner>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!loading" mat-dialog-content class="background">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">
|
|
||||||
<a (click)="openDetails()">
|
|
||||||
<img id="cardImage" src="{{data.posterPath}}" class="poster" alt="{{data.title}}">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="col-4 offset-8 text-right" id="icons">
|
|
||||||
<span *ngIf="movie">
|
|
||||||
<a *ngIf="movie.plexUrl" class="media-icons" href="{{movie.plexUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnPlex' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
<a *ngIf="movie.embyUrl" class="media-icons" href="{{movie.embyUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnEmby' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
<a *ngIf="movie.jellyfinUrl" class="media-icons" href="{{movie.jellyfinUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnJellyfin' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span *ngIf="tv">
|
|
||||||
<a *ngIf="tv.plexUrl" class="media-icons" href="{{tv.plexUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnPlex' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
<a *ngIf="tv.embyUrl" class="media-icons" href="{{tv.embyUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnEmby' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
<a *ngIf="tv.jellyfinUrl" class="media-icons" href="{{tv.jellyfinUrl}}" target="_blank">
|
|
||||||
<i matTooltip=" {{'Search.ViewOnJellyfin' | translate}}"
|
|
||||||
class="fas fa-play-circle fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<a class="media-icons" (click)="close()">
|
|
||||||
<i class="fas fa-window-close fa-2x grow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<h3><strong>{{data.title}}</strong></h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row top-spacing details">
|
|
||||||
<div class="col-6">
|
|
||||||
<strong>{{'Discovery.CardDetails.Availability' | translate}}: </strong> <small>
|
|
||||||
<ng-template [ngIf]="data.available"><span class="label label-success" id="availableLabel"
|
|
||||||
[translate]="'Common.Available'"></span></ng-template>
|
|
||||||
<ng-template [ngIf]="!data.available"><span class="label label-success" id="availableLabel"
|
|
||||||
[translate]="'Common.NotAvailable'"></span></ng-template>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<strong *ngIf="movie">{{'Discovery.CardDetails.Studio' | translate}}: </strong>
|
|
||||||
<small *ngIf="movie">{{movie.productionCompanies[0].name}}</small>
|
|
||||||
<strong *ngIf="tv">{{'Discovery.CardDetails.Network' | translate}}: </strong>
|
|
||||||
<small *ngIf="tv && tv.network">{{tv.network.name}}</small>
|
|
||||||
<small *ngIf="tv && !tv.network">{{'Discovery.CardDetails.UnknownNetwork' | translate}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6" *ngIf="!data.available">
|
|
||||||
<strong>{{'Discovery.CardDetails.RequestStatus' | translate}}: </strong> <small>
|
|
||||||
<ng-template [ngIf]="data.approved && !data.available"><span class="label label-info"
|
|
||||||
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="data.requested && !data.approved && !data.available"><span
|
|
||||||
class="label label-warning" id="pendingApprovalLabel"
|
|
||||||
[translate]="'Common.PendingApproval'"></span></ng-template>
|
|
||||||
<ng-template [ngIf]="!data.requested && !data.available && !data.approved"><span
|
|
||||||
class="label label-danger" id="notRequestedLabel"
|
|
||||||
[translate]="'Common.NotRequested'"></span></ng-template>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<strong *ngIf="movie">{{'Discovery.CardDetails.Director' | translate}}: </strong>
|
|
||||||
<small *ngIf="movie">{{movie.credits.crew[0].name}}</small>
|
|
||||||
<strong *ngIf="tvCreator">Director: </strong>
|
|
||||||
<small *ngIf="tvCreator">{{tvCreator}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<strong *ngIf="movie">{{'Discovery.CardDetails.InCinemas' | translate}}: </strong>
|
|
||||||
<small *ngIf="movie">{{movie.releaseDate | amLocal | amDateFormat: 'LL'}}</small>
|
|
||||||
<strong *ngIf="tv">{{'Discovery.CardDetails.FirstAired' | translate}}: </strong>
|
|
||||||
<small *ngIf="tv">{{tv.firstAired | amLocal | amDateFormat: 'LL'}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<strong *ngIf="movie">{{'Discovery.CardDetails.Writer' | translate}}: </strong>
|
|
||||||
<small *ngIf="movie">{{movie.credits.crew[1].name}}</small>
|
|
||||||
<strong *ngIf="tv">{{'Discovery.CardDetails.ExecProducer' | translate}}: </strong>
|
|
||||||
<small *ngIf="tv">{{tvProducer}}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row top-spacing overview">
|
|
||||||
<div class="col-12">
|
|
||||||
{{data.overview}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div mat-dialog-actions>
|
|
||||||
<div class="action-buttons-right">
|
|
||||||
<div class="col-md-12" *ngIf="movie">
|
|
||||||
<button mat-raised-button class="btn-green btn-spacing" *ngIf="movie.available"> {{
|
|
||||||
'Common.Available' | translate }}</button>
|
|
||||||
<span *ngIf="!movie.available">
|
|
||||||
<span *ngIf="movie.requested || movie.approved; then requestedBtn else notRequestedBtn"></span>
|
|
||||||
<ng-template #requestedBtn>
|
|
||||||
<button mat-raised-button class="btn-spacing btn-orange" [disabled]><i class="fas fa-check"></i>
|
|
||||||
{{ 'Common.Requested' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #notRequestedBtn>
|
|
||||||
<button mat-raised-button class="btn-spacing" color="primary" (click)="request()">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-12" *ngIf="tv">
|
|
||||||
|
|
||||||
<div *ngIf="!tv.fullyAvailable" class="dropdown">
|
|
||||||
<button mat-raised-button class="btn-orange btn-spacing" type="button" (click)="request()">
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
{{ 'Common.Request' | translate }}
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button *ngIf="tv.fullyAvailable" mat-raised-button class="btn-spacing" color="accent" [disabled]>
|
|
||||||
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button>
|
|
||||||
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" mat-raised-button class="btn-spacing" color="accent"
|
|
||||||
[disabled]>
|
|
||||||
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button>
|
|
||||||
|
|
||||||
<span *ngIf="tv.available">
|
|
||||||
<a *ngIf="tv.plexUrl" mat-raised-button style="text-align: right" class="btn-spacing btn-greem"
|
|
||||||
href="{{tv.plexUrl}}" target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnPlex' |
|
|
||||||
translate}}</a>
|
|
||||||
<a *ngIf="tv.embyUrl" mat-raised-button class="btn-green btn-spacing" href="{{tv.embyUrl}}"
|
|
||||||
target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnEmby' |
|
|
||||||
translate}}</a>
|
|
||||||
<a *ngIf="tv.jellyfinUrl" mat-raised-button class="btn-green btn-spacing" href="{{tv.jellyfinUrl}}"
|
|
||||||
target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnJellyfin' |
|
|
||||||
translate}}</a>
|
|
||||||
</span>
|
|
||||||
<button mat-raised-button class="btn-green btn-spacing" (click)="openDetails()"> {{
|
|
||||||
'Common.ViewDetails' | translate }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,45 +0,0 @@
|
||||||
@import "~styles/variables.scss";
|
|
||||||
.poster {
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 2%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
padding: 2%;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: $backgroundTint;
|
|
||||||
div.dark & {
|
|
||||||
background: $backgroundTint-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.details strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h3 strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons-right {
|
|
||||||
width: 100%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-spacing {
|
|
||||||
margin-right: 1%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-icons {
|
|
||||||
color: $primary;
|
|
||||||
padding: 2%;
|
|
||||||
div.dark & {
|
|
||||||
color: $warn-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview {
|
|
||||||
height:300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
import { Component, Inject, OnInit, ViewEncapsulation } from "@angular/core";
|
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog";
|
|
||||||
import { IDiscoverCardResult } from "../../interfaces";
|
|
||||||
import { SearchV2Service, RequestService, MessageService } from "../../../services";
|
|
||||||
import { RequestType } from "../../../interfaces";
|
|
||||||
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
|
||||||
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
|
|
||||||
import { Router } from "@angular/router";
|
|
||||||
import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "discover-card-details",
|
|
||||||
templateUrl: "./discover-card-details.component.html",
|
|
||||||
styleUrls: ["./discover-card-details.component.scss"],
|
|
||||||
encapsulation: ViewEncapsulation.None,
|
|
||||||
})
|
|
||||||
export class DiscoverCardDetailsComponent implements OnInit {
|
|
||||||
|
|
||||||
public movie: ISearchMovieResultV2;
|
|
||||||
public tv: ISearchTvResultV2;
|
|
||||||
public tvCreator: string;
|
|
||||||
public tvProducer: string;
|
|
||||||
public loading: boolean;
|
|
||||||
public RequestType = RequestType;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public dialogRef: MatDialogRef<DiscoverCardDetailsComponent>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) public data: IDiscoverCardResult, private searchService: SearchV2Service, private dialog: MatDialog,
|
|
||||||
private requestService: RequestService, public messageService: MessageService, private router: Router) { }
|
|
||||||
|
|
||||||
public async ngOnInit() {
|
|
||||||
this.loading = true;
|
|
||||||
if (this.data.type === RequestType.movie) {
|
|
||||||
this.movie = await this.searchService.getFullMovieDetailsPromise(+this.data.id);
|
|
||||||
} else if (this.data.type === RequestType.tvShow) {
|
|
||||||
this.tv = await this.searchService.getTvInfo(+this.data.id);
|
|
||||||
const creator = this.tv.crew.filter(tv => {
|
|
||||||
return tv.type === "Creator";
|
|
||||||
})[0];
|
|
||||||
if (creator && creator.person) {
|
|
||||||
this.tvCreator = creator.person.name;
|
|
||||||
}
|
|
||||||
const crewResult = this.tv.crew.filter(tv => {
|
|
||||||
return tv.type === "Executive Producer";
|
|
||||||
})[0]
|
|
||||||
if (crewResult && crewResult.person) {
|
|
||||||
this.tvProducer = crewResult.person.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
this.dialogRef.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public openDetails() {
|
|
||||||
if (this.data.type === RequestType.movie) {
|
|
||||||
this.router.navigate(['/details/movie/', this.data.id]);
|
|
||||||
} else if (this.data.type === RequestType.tvShow) {
|
|
||||||
this.router.navigate(['/details/tv/', this.data.id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialogRef.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async request() {
|
|
||||||
this.loading = true;
|
|
||||||
if (this.data.type === RequestType.movie) {
|
|
||||||
const result = await this.requestService.requestMovie({ theMovieDbId: +this.data.id, languageCode: "", requestOnBehalf: null }).toPromise();
|
|
||||||
this.loading = false;
|
|
||||||
|
|
||||||
if (result.result) {
|
|
||||||
this.movie.requested = true;
|
|
||||||
this.messageService.send(result.message, "Ok");
|
|
||||||
} else {
|
|
||||||
this.messageService.send(result.errorMessage, "Ok");
|
|
||||||
}
|
|
||||||
} else if (this.data.type === RequestType.tvShow) {
|
|
||||||
this.dialog.open(EpisodeRequestComponent, { width: "700px", data: {series: this.tv }, panelClass: 'modal-panel' })
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
|
|
||||||
this.dialogRef.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,10 +3,10 @@ import { IDiscoverCardResult } from "../../interfaces";
|
||||||
import { RequestType } from "../../../interfaces";
|
import { RequestType } from "../../../interfaces";
|
||||||
import { MessageService, RequestService, SearchV2Service } from "../../../services";
|
import { MessageService, RequestService, SearchV2Service } from "../../../services";
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import { DiscoverCardDetailsComponent } from "./discover-card-details.component";
|
|
||||||
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
|
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
|
||||||
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
||||||
import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component";
|
import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component";
|
||||||
|
import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "discover-card",
|
selector: "discover-card",
|
||||||
|
@ -16,6 +16,7 @@ import { EpisodeRequestComponent } from "../../../shared/episode-request/episode
|
||||||
export class DiscoverCardComponent implements OnInit {
|
export class DiscoverCardComponent implements OnInit {
|
||||||
|
|
||||||
@Input() public result: IDiscoverCardResult;
|
@Input() public result: IDiscoverCardResult;
|
||||||
|
@Input() public isAdmin: boolean;
|
||||||
public RequestType = RequestType;
|
public RequestType = RequestType;
|
||||||
public hide: boolean;
|
public hide: boolean;
|
||||||
public fullyLoaded = false;
|
public fullyLoaded = false;
|
||||||
|
@ -40,10 +41,6 @@ export class DiscoverCardComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public openDetails(details: IDiscoverCardResult) {
|
|
||||||
this.dialog.open(DiscoverCardDetailsComponent, { width: "700px", data: details, panelClass: 'modal-panel' })
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getExtraTvInfo() {
|
public async getExtraTvInfo() {
|
||||||
// if (this.result.tvMovieDb) {
|
// if (this.result.tvMovieDb) {
|
||||||
this.tvSearchResult = await this.searchService.getTvInfoWithMovieDbId(+this.result.id);
|
this.tvSearchResult = await this.searchService.getTvInfoWithMovieDbId(+this.result.id);
|
||||||
|
@ -125,7 +122,10 @@ export class DiscoverCardComponent implements OnInit {
|
||||||
dia.afterClosed().subscribe(x => this.loading = false);
|
dia.afterClosed().subscribe(x => this.loading = false);
|
||||||
return;
|
return;
|
||||||
case RequestType.movie:
|
case RequestType.movie:
|
||||||
this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: null, requestOnBehalf: null }).subscribe(x => {
|
if (this.isAdmin) {
|
||||||
|
this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id }, panelClass: 'modal-panel' });
|
||||||
|
} else {
|
||||||
|
this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: null, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null }).subscribe(x => {
|
||||||
if (x.result) {
|
if (x.result) {
|
||||||
this.result.requested = true;
|
this.result.requested = true;
|
||||||
this.messageService.send(x.message, "Ok");
|
this.messageService.send(x.message, "Ok");
|
||||||
|
@ -137,6 +137,7 @@ export class DiscoverCardComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getExtraMovieInfo() {
|
private getExtraMovieInfo() {
|
||||||
if (!this.result.imdbid) {
|
if (!this.result.imdbid) {
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
|
|
||||||
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
|
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
|
||||||
<ng-template let-result pTemplate="item">
|
<ng-template let-result pTemplate="item">
|
||||||
<discover-card [result]="result"></discover-card>
|
<discover-card [isAdmin]="isAdmin" [result]="result"></discover-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-carousel>
|
</p-carousel>
|
|
@ -22,6 +22,7 @@ export class CarouselListComponent implements OnInit {
|
||||||
|
|
||||||
@Input() public discoverType: DiscoverType;
|
@Input() public discoverType: DiscoverType;
|
||||||
@Input() public id: string;
|
@Input() public id: string;
|
||||||
|
@Input() public isAdmin: boolean;
|
||||||
@ViewChild('carousel', {static: false}) carousel: Carousel;
|
@ViewChild('carousel', {static: false}) carousel: Carousel;
|
||||||
|
|
||||||
public DiscoverOption = DiscoverOption;
|
public DiscoverOption = DiscoverOption;
|
||||||
|
|
|
@ -34,7 +34,7 @@ export class DiscoverCollectionsComponent implements OnInit {
|
||||||
|
|
||||||
public async requestCollection() {
|
public async requestCollection() {
|
||||||
await this.collection.collection.forEach(async (movie) => {
|
await this.collection.collection.forEach(async (movie) => {
|
||||||
await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null}).toPromise();
|
await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null}).toPromise();
|
||||||
});
|
});
|
||||||
this.messageService.send("Requested Collection");
|
this.messageService.send("Requested Collection");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{{'Discovery.PopularTab' | translate}}</h2>
|
<h2>{{'Discovery.PopularTab' | translate}}</h2>
|
||||||
<div>
|
<div>
|
||||||
<carousel-list [id]="'popular'" [discoverType]="DiscoverType.Popular"></carousel-list>
|
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{{'Discovery.TrendingTab' | translate}}</h2>
|
<h2>{{'Discovery.TrendingTab' | translate}}</h2>
|
||||||
<div >
|
<div >
|
||||||
<carousel-list [id]="'trending'" [discoverType]="DiscoverType.Trending"></carousel-list>
|
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>{{'Discovery.UpcomingTab' | translate}}</h2>
|
<h2>{{'Discovery.UpcomingTab' | translate}}</h2>
|
||||||
<div>
|
<div>
|
||||||
<carousel-list [id]="'upcoming'" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="section">
|
<!-- <div class="section">
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { Component } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
import { DiscoverType } from "../carousel-list/carousel-list.component";
|
import { DiscoverType } from "../carousel-list/carousel-list.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./discover.component.html",
|
templateUrl: "./discover.component.html",
|
||||||
styleUrls: ["./discover.component.scss"],
|
styleUrls: ["./discover.component.scss"],
|
||||||
})
|
})
|
||||||
export class DiscoverComponent {
|
export class DiscoverComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
public DiscoverType = DiscoverType;
|
public DiscoverType = DiscoverType;
|
||||||
|
public isAdmin: boolean;
|
||||||
|
|
||||||
constructor() { }
|
constructor(private authService: AuthService) { }
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.isAdmin = this.authService.isAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
<!-- <div class="card-spacing" *ngIf="result">
|
|
||||||
<mat-card class="mat-elevation-z8 dark-card grow">
|
|
||||||
<a [routerLink]="result.type === RequestType.movie ? '/details/movie/' + result.id : '/details/tv/' + result.id">
|
|
||||||
<img id="cardImage" mat-card-image src="{{result.posterPath}}" class="card-poster" [ngClass]="getStatusClass()" alt="{{result.title}}">
|
|
||||||
</a>
|
|
||||||
<mat-card-content>
|
|
||||||
<h6 *ngIf="result.title.length <= 20">{{result.title}}</h6>
|
|
||||||
<h6 *ngIf="result.title.length > 20" matTooltip="{{result.title}}">{{result.title | truncate:20}}</h6>
|
|
||||||
<div class="fade-text">
|
|
||||||
<small class="overview-text">{{result.overview | truncate: 75}}</small>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
|
|
||||||
<div class="top-spacing">
|
|
||||||
<mat-card class="mat-elevation-z8 dark-card backdrop" [style.background-image]="result.background">
|
|
||||||
<div class="row main-container">
|
|
||||||
<div class="col-md-2 col-12">
|
|
||||||
<img src="{{result.posterPath}}" class="card-poster" alt="{{result.title}}">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8 col-12">
|
|
||||||
<div class="row">
|
|
||||||
<h1>{{result.title}}</h1>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<mat-chip-list>
|
|
||||||
<mat-chip *ngIf="result.available" class="available">
|
|
||||||
{{'Common.Available' | translate}}
|
|
||||||
</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="result.approved && !result.available" class="approved">
|
|
||||||
{{'Common.ProcessingRequest' | translate}}
|
|
||||||
</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="result.denied" class="denied">
|
|
||||||
{{'Common.RequestDenied' | translate}}
|
|
||||||
</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="result.requested && !result.approved && !result.available && !result.denied"
|
|
||||||
class="requested">
|
|
||||||
{{'Common.PendingApproval' | translate}}
|
|
||||||
</mat-chip>
|
|
||||||
<mat-chip *ngIf="movie && movie.plexUrl"> <a href="{{movie.plexUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnPlex' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
<mat-chip *ngIf="movie && movie.embyUrl"> <a href="{{movie.embyUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnEmby' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
<mat-chip *ngIf="movie && movie.jellyfinUrl"> <a href="{{movie.jellyfinUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnJellyfin' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="tv && tv.plexUrl"> <a href="{{tv.plexUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnPlex' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
<mat-chip *ngIf="tv &&tv.embyUrl"> <a href="{{movie.embyUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnEmby' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
<mat-chip *ngIf="tv &&tv.jellyfinUrl"> <a href="{{movie.jellyfinUrl}}" target="_blank">
|
|
||||||
<mat-icon style="color:white" matTooltip=" {{'Search.ViewOnJellyfin' | translate}}">
|
|
||||||
play_circle_outline</mat-icon>
|
|
||||||
</a></mat-chip>
|
|
||||||
</mat-chip-list>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<mat-chip-list class="top-spacing">
|
|
||||||
<mat-chip *ngIf="movie && movie.productionCompanies[0]?.name">
|
|
||||||
{{'Discovery.CardDetails.Studio' | translate}}: {{movie.productionCompanies[0].name}}
|
|
||||||
</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="tv && tv.network?.name">{{'Discovery.CardDetails.Network' | translate}}:
|
|
||||||
{{tv.network.name}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="movie && movie.credits?.crew[0]?.name">
|
|
||||||
{{'Discovery.CardDetails.Director' | translate}}: {{movie.credits.crew[0].name}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="tvCreator">Director: {{tvCreator}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="movie">{{'Discovery.CardDetails.InCinemas' | translate}}:
|
|
||||||
{{movie.releaseDate | amLocal | amDateFormat: 'LL'}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="tv">{{'Discovery.CardDetails.FirstAired' | translate}}:
|
|
||||||
{{tv.firstAired | amLocal | amDateFormat: 'LL'}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="movie && movie.credits?.crew[1]?.name">
|
|
||||||
{{'Discovery.CardDetails.Writer' | translate}}: {{movie.credits.crew[1].name}}</mat-chip>
|
|
||||||
|
|
||||||
<mat-chip *ngIf="tv">{{'Discovery.CardDetails.ExecProducer' | translate}}: {{tvProducer}}
|
|
||||||
</mat-chip>
|
|
||||||
|
|
||||||
</mat-chip-list>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<p class="overview top-spacing">{{result.overview}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-2 col-12">
|
|
||||||
<div style="float:right;">
|
|
||||||
<button mat-raised-button class="btn-green btn-spacing" (click)="openDetails()"> {{
|
|
||||||
'Common.ViewDetails' | translate }}</button>
|
|
||||||
<div *ngIf="movie">
|
|
||||||
<button mat-raised-button class="btn-green btn-spacing" *ngIf="movie.available"> {{
|
|
||||||
'Common.Available' | translate }}</button>
|
|
||||||
<span *ngIf="!movie.available">
|
|
||||||
<span
|
|
||||||
*ngIf="movie.requested || movie.approved; then requestedBtn else notRequestedBtn"></span>
|
|
||||||
<ng-template #requestedBtn>
|
|
||||||
<button mat-raised-button class="btn-spacing btn-orange" [disabled]><i
|
|
||||||
class="fas fa-check"></i>
|
|
||||||
{{ 'Common.Requested' | translate }}</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #notRequestedBtn>
|
|
||||||
<button mat-raised-button class="btn-spacing" color="primary" (click)="request()">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="tv">
|
|
||||||
|
|
||||||
<div *ngIf="!tv.fullyAvailable" class="dropdown">
|
|
||||||
<button mat-raised-button class="btn-orange btn-spacing" type="button" (click)="request()">
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
{{ 'Common.Request' | translate }}
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button *ngIf="tv.fullyAvailable" mat-raised-button class="btn-spacing" color="accent"
|
|
||||||
[disabled]>
|
|
||||||
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button>
|
|
||||||
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" mat-raised-button class="btn-spacing"
|
|
||||||
color="accent" [disabled]>
|
|
||||||
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button>
|
|
||||||
|
|
||||||
<span *ngIf="tv.available">
|
|
||||||
<a *ngIf="tv.plexUrl" mat-raised-button style="text-align: right"
|
|
||||||
class="btn-spacing btn-greem" href="{{tv.plexUrl}}" target="_blank"><i
|
|
||||||
class="far fa-eye"></i> {{'Search.ViewOnPlex' |
|
|
||||||
translate}}</a>
|
|
||||||
<a *ngIf="tv.embyUrl" mat-raised-button class="btn-green btn-spacing" href="{{tv.embyUrl}}"
|
|
||||||
target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnEmby' |
|
|
||||||
translate}}</a>
|
|
||||||
<a *ngIf="tv.jellyfinUrl" mat-raised-button class="btn-green btn-spacing" href="{{tv.jellyfinUrl}}"
|
|
||||||
target="_blank"><i class="far fa-eye"></i> {{'Search.ViewOnJellyfin' |
|
|
||||||
translate}}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
|
@ -1,137 +0,0 @@
|
||||||
$ombi-primary:#3f3f3f;
|
|
||||||
$card-background: #2b2b2b;
|
|
||||||
|
|
||||||
$blue: #1976D2;
|
|
||||||
$pink: #C2185B;
|
|
||||||
$green:#1DE9B6;
|
|
||||||
$orange:#F57C00;
|
|
||||||
|
|
||||||
.btn-blue {
|
|
||||||
background-color: $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-pink {
|
|
||||||
background-color: $pink;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-green {
|
|
||||||
background-color: $green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-orange {
|
|
||||||
background-color: $orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-spacing {
|
|
||||||
margin-top:10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cardImage {
|
|
||||||
border-radius: 5px 5px 0px 0px;
|
|
||||||
height: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark-card {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changed height to 100% to make all cards the same height
|
|
||||||
.top-spacing {
|
|
||||||
margin-top: 1%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-poster {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 8px 0px 0px 8px;
|
|
||||||
margin-top: -6.5%;
|
|
||||||
margin-bottom: -6.6%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
margin-left: -2%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.rating {
|
|
||||||
position: absolute;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
$border-width: 3px;
|
|
||||||
|
|
||||||
.available {
|
|
||||||
background-color: #1DE9B6 !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.approved {
|
|
||||||
background-color: #ff5722 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.requested {
|
|
||||||
background-color: #ffd740 !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.denied {
|
|
||||||
background-color: #C2185B !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notrequested {
|
|
||||||
background-color: #303030 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expand {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1025px) {
|
|
||||||
|
|
||||||
// Changed height to 100% to make all cards the same height
|
|
||||||
.grow {
|
|
||||||
transition: all .2s ease-in-out;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grow:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep mat-dialog-container.mat-dialog-container {
|
|
||||||
// background-color: $ombi-primary;
|
|
||||||
// color: white;
|
|
||||||
border-radius: 2%
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Title adjust for the Discover page */
|
|
||||||
.mat-card-content h6 {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Summary adjust for Discover page */
|
|
||||||
.small,
|
|
||||||
small {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 2000px) {
|
|
||||||
#cardImage {
|
|
||||||
height: 80%;
|
|
||||||
object-fit: cover;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.overview {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.backdrop {
|
|
||||||
background-position: 50% 33%;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
import { Component, OnInit, Input } from "@angular/core";
|
|
||||||
import { IDiscoverCardResult } from "../../interfaces";
|
|
||||||
import { RequestType, ISearchTvResult, ISearchMovieResult, ISearchMovieResultContainer } from "../../../interfaces";
|
|
||||||
import { ImageService, RequestService, SearchV2Service } from "../../../services";
|
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
|
||||||
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
|
|
||||||
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
|
||||||
import { EpisodeRequestComponent, EpisodeRequestData } from "../../../shared/episode-request/episode-request.component";
|
|
||||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
|
||||||
import { Router } from "@angular/router";
|
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "discover-grid",
|
|
||||||
templateUrl: "./discover-grid.component.html",
|
|
||||||
styleUrls: ["./discover-grid.component.scss"],
|
|
||||||
})
|
|
||||||
export class DiscoverGridComponent implements OnInit {
|
|
||||||
|
|
||||||
@Input() public result: IDiscoverCardResult;
|
|
||||||
public RequestType = RequestType;
|
|
||||||
public requesting: boolean;
|
|
||||||
|
|
||||||
public tv: ISearchTvResultV2;
|
|
||||||
public tvCreator: string;
|
|
||||||
public tvProducer: string;
|
|
||||||
public movie: ISearchMovieResultV2;
|
|
||||||
|
|
||||||
constructor(private searchService: SearchV2Service, private dialog: MatDialog,
|
|
||||||
private requestService: RequestService, private notification: MatSnackBar,
|
|
||||||
private router: Router, private sanitizer: DomSanitizer, private imageService: ImageService) { }
|
|
||||||
|
|
||||||
public ngOnInit() {
|
|
||||||
if (this.result.type == RequestType.tvShow) {
|
|
||||||
this.getExtraTvInfo();
|
|
||||||
}
|
|
||||||
if (this.result.type == RequestType.movie) {
|
|
||||||
this.getExtraMovieInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getExtraTvInfo() {
|
|
||||||
this.tv = await this.searchService.getTvInfo(+this.result.id);
|
|
||||||
this.setTvDefaults(this.tv);
|
|
||||||
this.updateTvItem(this.tv);
|
|
||||||
const creator = this.tv.crew.filter(tv => {
|
|
||||||
return tv.type === "Creator";
|
|
||||||
})[0];
|
|
||||||
if (creator && creator.person) {
|
|
||||||
this.tvCreator = creator.person.name;
|
|
||||||
}
|
|
||||||
const crewResult = this.tv.crew.filter(tv => {
|
|
||||||
return tv.type === "Executive Producer";
|
|
||||||
})[0]
|
|
||||||
if (crewResult && crewResult.person) {
|
|
||||||
this.tvProducer = crewResult.person.name;
|
|
||||||
}
|
|
||||||
this.setTvBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
public openDetails() {
|
|
||||||
if (this.result.type === RequestType.movie) {
|
|
||||||
this.router.navigate(['/details/movie/', this.result.id]);
|
|
||||||
} else if (this.result.type === RequestType.tvShow) {
|
|
||||||
this.router.navigate(['/details/tv/', this.result.id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getStatusClass(): string {
|
|
||||||
if (this.result.available) {
|
|
||||||
return "available";
|
|
||||||
}
|
|
||||||
if (this.result.approved) {
|
|
||||||
return "approved";
|
|
||||||
}
|
|
||||||
if (this.result.requested) {
|
|
||||||
return "requested";
|
|
||||||
}
|
|
||||||
return "notrequested";
|
|
||||||
}
|
|
||||||
|
|
||||||
private getExtraMovieInfo() {
|
|
||||||
this.searchService.getFullMovieDetails(+this.result.id)
|
|
||||||
.subscribe(m => {
|
|
||||||
this.movie = m;
|
|
||||||
this.updateMovieItem(m);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setMovieBackground()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setMovieBackground(): void {
|
|
||||||
this.result.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(" + "https://image.tmdb.org/t/p/original" + this.result.background + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
private setTvBackground(): void {
|
|
||||||
if (this.result.background != null) {
|
|
||||||
this.result.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(https://image.tmdb.org/t/p/original" + this.result.background + ")");
|
|
||||||
} else {
|
|
||||||
this.imageService.getTvBanner(+this.result.id).subscribe(x => {
|
|
||||||
if (x) {
|
|
||||||
this.result.background = this.sanitizer.bypassSecurityTrustStyle
|
|
||||||
("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(" + x + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateMovieItem(updated: ISearchMovieResultV2) {
|
|
||||||
this.result.url = "http://www.imdb.com/title/" + updated.imdbId + "/";
|
|
||||||
this.result.available = updated.available;
|
|
||||||
this.result.requested = updated.requested;
|
|
||||||
this.result.requested = updated.requestProcessing;
|
|
||||||
this.result.rating = updated.voteAverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private setTvDefaults(x: ISearchTvResultV2) {
|
|
||||||
if (!x.imdbId) {
|
|
||||||
x.imdbId = "https://www.tvmaze.com/shows/" + x.seriesId;
|
|
||||||
} else {
|
|
||||||
x.imdbId = "http://www.imdb.com/title/" + x.imdbId + "/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTvItem(updated: ISearchTvResultV2) {
|
|
||||||
this.result.title = updated.title;
|
|
||||||
this.result.id = updated.id;
|
|
||||||
this.result.available = updated.fullyAvailable;
|
|
||||||
this.result.posterPath = updated.banner;
|
|
||||||
this.result.requested = updated.requested;
|
|
||||||
this.result.url = updated.imdbId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async request() {
|
|
||||||
this.requesting = true;
|
|
||||||
if (this.result.type === RequestType.movie) {
|
|
||||||
const result = await this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: "", requestOnBehalf: null }).toPromise();
|
|
||||||
|
|
||||||
if (result.result) {
|
|
||||||
this.result.requested = true;
|
|
||||||
this.notification.open(result.message, "Ok");
|
|
||||||
} else {
|
|
||||||
this.notification.open(result.errorMessage, "Ok");
|
|
||||||
}
|
|
||||||
} else if (this.result.type === RequestType.tvShow) {
|
|
||||||
this.dialog.open(EpisodeRequestComponent, { width: "700px", data: <EpisodeRequestData>{ series: this.tv, requestOnBehalf: null }, panelClass: 'modal-panel' })
|
|
||||||
}
|
|
||||||
this.requesting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { DiscoverComponent } from "./discover/discover.component";
|
import { DiscoverComponent } from "./discover/discover.component";
|
||||||
import { DiscoverCardDetailsComponent } from "./card/discover-card-details.component";
|
|
||||||
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
||||||
import { DiscoverActorComponent } from "./actor/discover-actor.component";
|
import { DiscoverActorComponent } from "./actor/discover-actor.component";
|
||||||
import { DiscoverCardComponent } from "./card/discover-card.component";
|
import { DiscoverCardComponent } from "./card/discover-card.component";
|
||||||
import { Routes } from "@angular/router";
|
import { Routes } from "@angular/router";
|
||||||
import { AuthGuard } from "../../auth/auth.guard";
|
import { AuthGuard } from "../../auth/auth.guard";
|
||||||
import { SearchService, RequestService } from "../../services";
|
import { SearchService, RequestService, SonarrService, RadarrService } from "../../services";
|
||||||
import { MatDialog } from "@angular/material/dialog";
|
import { MatDialog } from "@angular/material/dialog";
|
||||||
import { DiscoverGridComponent } from "./grid/discover-grid.component";
|
|
||||||
import { DiscoverSearchResultsComponent } from "./search-results/search-results.component";
|
import { DiscoverSearchResultsComponent } from "./search-results/search-results.component";
|
||||||
import { CarouselListComponent } from "./carousel-list/carousel-list.component";
|
import { CarouselListComponent } from "./carousel-list/carousel-list.component";
|
||||||
import { RequestServiceV2 } from "../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
|
@ -16,10 +14,8 @@ import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
export const components: any[] = [
|
export const components: any[] = [
|
||||||
DiscoverComponent,
|
DiscoverComponent,
|
||||||
DiscoverCardComponent,
|
DiscoverCardComponent,
|
||||||
DiscoverCardDetailsComponent,
|
|
||||||
DiscoverCollectionsComponent,
|
DiscoverCollectionsComponent,
|
||||||
DiscoverActorComponent,
|
DiscoverActorComponent,
|
||||||
DiscoverGridComponent,
|
|
||||||
DiscoverSearchResultsComponent,
|
DiscoverSearchResultsComponent,
|
||||||
CarouselListComponent,
|
CarouselListComponent,
|
||||||
];
|
];
|
||||||
|
@ -29,6 +25,8 @@ export const providers: any[] = [
|
||||||
MatDialog,
|
MatDialog,
|
||||||
RequestService,
|
RequestService,
|
||||||
RequestServiceV2,
|
RequestServiceV2,
|
||||||
|
SonarrService,
|
||||||
|
RadarrService,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="discoverResults && discoverResults.length > 0" class="row full-height discoverResults col" >
|
<div *ngIf="discoverResults && discoverResults.length > 0" class="row full-height discoverResults col" >
|
||||||
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
|
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
|
||||||
<discover-card [result]="result"></discover-card>
|
<discover-card [isAdmin]="isAdmin" [result]="result"></discover-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!discoverResults || discoverResults.length === 0">
|
<div *ngIf="!discoverResults || discoverResults.length === 0">
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { SearchFilter } from "../../../my-nav/SearchFilter";
|
||||||
import { StorageService } from "../../../shared/storage/storage-service";
|
import { StorageService } from "../../../shared/storage/storage-service";
|
||||||
|
|
||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
|
import { AuthService } from "../../../auth/auth.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./search-results.component.html",
|
templateUrl: "./search-results.component.html",
|
||||||
|
@ -18,6 +19,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
public loadingFlag: boolean;
|
public loadingFlag: boolean;
|
||||||
public searchTerm: string;
|
public searchTerm: string;
|
||||||
public results: IMultiSearchResult[];
|
public results: IMultiSearchResult[];
|
||||||
|
public isAdmin: boolean;
|
||||||
|
|
||||||
public discoverResults: IDiscoverCardResult[] = [];
|
public discoverResults: IDiscoverCardResult[] = [];
|
||||||
|
|
||||||
|
@ -26,7 +28,8 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
constructor(private searchService: SearchV2Service,
|
constructor(private searchService: SearchV2Service,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private filterService: FilterService,
|
private filterService: FilterService,
|
||||||
private store: StorageService) {
|
private store: StorageService,
|
||||||
|
private authService: AuthService) {
|
||||||
this.route.params.subscribe((params: any) => {
|
this.route.params.subscribe((params: any) => {
|
||||||
this.searchTerm = params.searchTerm;
|
this.searchTerm = params.searchTerm;
|
||||||
this.clear();
|
this.clear();
|
||||||
|
@ -36,6 +39,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
|
|
||||||
public async ngOnInit() {
|
public async ngOnInit() {
|
||||||
this.loadingFlag = true;
|
this.loadingFlag = true;
|
||||||
|
this.isAdmin = this.authService.isAdmin();
|
||||||
|
|
||||||
this.filterService.onFilterChange.subscribe(async x => {
|
this.filterService.onFilterChange.subscribe(async x => {
|
||||||
if (!isEqual(this.filter, x)) {
|
if (!isEqual(this.filter, x)) {
|
||||||
|
|
|
@ -168,10 +168,9 @@ export interface IEpisodesRequests {
|
||||||
selected: boolean; // This is for the UI only
|
selected: boolean; // This is for the UI only
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMovieRequestModel {
|
export interface IMovieRequestModel extends BaseRequestOptions {
|
||||||
theMovieDbId: number;
|
theMovieDbId: number;
|
||||||
languageCode: string | undefined;
|
languageCode: string | undefined;
|
||||||
requestOnBehalf: string | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFilter {
|
export interface IFilter {
|
||||||
|
@ -187,3 +186,9 @@ export enum FilterType {
|
||||||
Processing = 4,
|
Processing = 4,
|
||||||
PendingApproval = 5,
|
PendingApproval = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BaseRequestOptions {
|
||||||
|
requestOnBehalf: string | undefined;
|
||||||
|
rootFolderOverride: number | undefined;
|
||||||
|
qualityPathOverride: number | undefined;
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { INewSeasonRequests } from "./IRequestModel";
|
import { BaseRequestOptions, INewSeasonRequests } from "./IRequestModel";
|
||||||
|
|
||||||
export interface ISearchTvResult {
|
export interface ISearchTvResult {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -47,12 +47,11 @@ export interface ITvRequestViewModelV2 extends ITvRequestViewModelBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface ITvRequestViewModelBase {
|
export interface ITvRequestViewModelBase extends BaseRequestOptions {
|
||||||
requestAll: boolean;
|
requestAll: boolean;
|
||||||
firstSeason: boolean;
|
firstSeason: boolean;
|
||||||
latestSeason: boolean;
|
latestSeason: boolean;
|
||||||
seasons: ISeasonsViewModel[];
|
seasons: ISeasonsViewModel[];
|
||||||
requestOnBehalf: string | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISeasonsViewModel {
|
export interface ISeasonsViewModel {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { MovieAdvancedOptionsComponent } from "./panels/movie-advanced-options/m
|
||||||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||||
import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component";
|
import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component";
|
||||||
import { forkJoin } from "rxjs";
|
import { forkJoin } from "rxjs";
|
||||||
|
import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./movie-details.component.html",
|
templateUrl: "./movie-details.component.html",
|
||||||
|
@ -84,7 +85,10 @@ export class MovieDetailsComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async request(userId?: string) {
|
public async request(userId?: string) {
|
||||||
const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null, requestOnBehalf: userId }).toPromise();
|
if (this.isAdmin) {
|
||||||
|
this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id }, panelClass: 'modal-panel' });
|
||||||
|
} else {
|
||||||
|
const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null, requestOnBehalf: userId, qualityPathOverride: 0, rootFolderOverride: 0 }).toPromise();
|
||||||
if (result.result) {
|
if (result.result) {
|
||||||
this.movie.requested = true;
|
this.movie.requested = true;
|
||||||
this.messageService.send(result.message, "Ok");
|
this.messageService.send(result.message, "Ok");
|
||||||
|
@ -92,6 +96,7 @@ export class MovieDetailsComponent {
|
||||||
this.messageService.send(result.errorMessage, "Ok");
|
this.messageService.send(result.errorMessage, "Ok");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public openDialog() {
|
public openDialog() {
|
||||||
this.dialog.open(YoutubeTrailerComponent, {
|
this.dialog.open(YoutubeTrailerComponent, {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { IStreamingData } from "../../../../interfaces/IStreams";
|
||||||
})
|
})
|
||||||
export class MovieInformationPanelComponent implements OnInit {
|
export class MovieInformationPanelComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private searchService: SearchV2Service, @Inject(APP_BASE_HREF) public baseUrl: string) { }
|
constructor(private searchService: SearchV2Service, @Inject(APP_BASE_HREF) public internalBaseUrl: string) { }
|
||||||
|
|
||||||
@Input() public movie: ISearchMovieResultV2;
|
@Input() public movie: ISearchMovieResultV2;
|
||||||
@Input() public request: IMovieRequests;
|
@Input() public request: IMovieRequests;
|
||||||
|
@ -22,7 +22,12 @@ export class MovieInformationPanelComponent implements OnInit {
|
||||||
public ratings: IMovieRatings;
|
public ratings: IMovieRatings;
|
||||||
public streams: IStreamingData[];
|
public streams: IStreamingData[];
|
||||||
|
|
||||||
|
public baseUrl: string;
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
|
if (this.internalBaseUrl.length > 1) {
|
||||||
|
this.baseUrl = this.internalBaseUrl;
|
||||||
|
}
|
||||||
this.searchService.getRottenMovieRatings(this.movie.title, +this.movie.releaseDate.toString().substring(0,4))
|
this.searchService.getRottenMovieRatings(this.movie.title, +this.movie.releaseDate.toString().substring(0,4))
|
||||||
.subscribe(x => this.ratings = x);
|
.subscribe(x => this.ratings = x);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,6 @@
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<button mat-button [mat-dialog-close]="" cdkFocusInitial>Close</button>
|
<button mat-raised-button [mat-dialog-close]="" color="warn">Close</button>
|
||||||
<button mat-button [mat-dialog-close]="data" cdkFocusInitial>Save</button>
|
<button mat-raised-button [mat-dialog-close]="data" color="accent" cdkFocusInitial>Save</button>
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, ViewEncapsulation, Input, OnInit } from "@angular/core";
|
import { APP_BASE_HREF } from "@angular/common";
|
||||||
|
import { Component, ViewEncapsulation, Input, OnInit, Inject } from "@angular/core";
|
||||||
import { ITvRequests } from "../../../../../interfaces";
|
import { ITvRequests } from "../../../../../interfaces";
|
||||||
import { ITvRatings } from "../../../../../interfaces/IRatings";
|
import { ITvRatings } from "../../../../../interfaces/IRatings";
|
||||||
import { ISearchTvResultV2 } from "../../../../../interfaces/ISearchTvResultV2";
|
import { ISearchTvResultV2 } from "../../../../../interfaces/ISearchTvResultV2";
|
||||||
|
@ -13,7 +14,7 @@ import { SearchV2Service } from "../../../../../services";
|
||||||
})
|
})
|
||||||
export class TvInformationPanelComponent implements OnInit {
|
export class TvInformationPanelComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private searchService: SearchV2Service) { }
|
constructor(private searchService: SearchV2Service, @Inject(APP_BASE_HREF) public internalBaseUrl: string) { }
|
||||||
|
|
||||||
@Input() public tv: ISearchTvResultV2;
|
@Input() public tv: ISearchTvResultV2;
|
||||||
@Input() public request: ITvRequests;
|
@Input() public request: ITvRequests;
|
||||||
|
@ -24,8 +25,12 @@ export class TvInformationPanelComponent implements OnInit {
|
||||||
public seasonCount: number;
|
public seasonCount: number;
|
||||||
public totalEpisodes: number = 0;
|
public totalEpisodes: number = 0;
|
||||||
public nextEpisode: any;
|
public nextEpisode: any;
|
||||||
|
public baseUrl: string;
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
if (this.internalBaseUrl.length > 1) {
|
||||||
|
this.baseUrl = this.internalBaseUrl;
|
||||||
|
}
|
||||||
this.searchService.getRottenTvRatings(this.tv.title, +this.tv.firstAired.toString().substring(0,4))
|
this.searchService.getRottenTvRatings(this.tv.title, +this.tv.firstAired.toString().substring(0,4))
|
||||||
.subscribe(x => this.ratings = x);
|
.subscribe(x => this.ratings = x);
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
<button *ngIf="tv.fullyAvailable && !tv.partlyAvailable" id="availableBtn" mat-raised-button class="btn-spacing" color="accent"
|
<button *ngIf="tv.fullyAvailable && !tv.partlyAvailable" id="availableBtn" mat-raised-button class="btn-spacing" color="accent"
|
||||||
[disabled]>
|
[disabled]>
|
||||||
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button>
|
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button>
|
||||||
|
|
||||||
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button
|
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button
|
||||||
class="btn-spacing" color="accent" [disabled]>
|
class="btn-spacing" color="accent" [disabled]>
|
||||||
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button>
|
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
|
||||||
|
|
||||||
|
<form [formGroup]="form" *ngIf="form">
|
||||||
|
<h1>Advanced Options</h1>
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
You can configure the request here, once requested it will be send to your DVR application and will be auto approved!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="max-width: 0; max-height: 0; overflow: hidden;">
|
||||||
|
<input autofocus="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User area -->
|
||||||
|
<h3>{{'MediaDetails.RequestOnBehalf' | translate }}</h3>
|
||||||
|
<mat-form-field class="example-full-width" appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{ 'MediaDetails.PleaseSelectUser' | translate}}</mat-label>
|
||||||
|
<input type="text"
|
||||||
|
matInput
|
||||||
|
formControlName="username"
|
||||||
|
[matAutocomplete]="auto">
|
||||||
|
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
||||||
|
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
|
||||||
|
{{displayFn(option)}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</mat-form-field>
|
||||||
|
<!-- End User area -->
|
||||||
|
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Sonarr -->
|
||||||
|
<div *ngIf="data.type === RequestType.tvShow">
|
||||||
|
<div>
|
||||||
|
<h3>Sonarr Overrides</h3>
|
||||||
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{'MediaDetails.QualityProfilesSelect' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="sonarrPathId">
|
||||||
|
<mat-option *ngFor="let profile of sonarrProfiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div >
|
||||||
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{'MediaDetails.RootFolderSelect' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="sonarrFolderId">
|
||||||
|
<mat-option *ngFor="let profile of sonarrRootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End Sonarr-->
|
||||||
|
|
||||||
|
<!-- Radarr -->
|
||||||
|
<div *ngIf="data.type === RequestType.movie">
|
||||||
|
<div>
|
||||||
|
<h3>Radarr Overrides</h3>
|
||||||
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{'MediaDetails.QualityProfilesSelect' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="radarrPathId">
|
||||||
|
<mat-option *ngFor="let profile of radarrProfiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{'MediaDetails.RootFolderSelect' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="radarrFolderId">
|
||||||
|
<mat-option *ngFor="let profile of radarrRootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End Radarr-->
|
||||||
|
|
||||||
|
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-raised-button [mat-dialog-close]="" color="warn">{{ 'Common.Cancel' | translate }}</button>
|
||||||
|
<button mat-raised-button (click)="submitRequest()" color="accent">{{ 'Common.Request' | translate }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
@import "~styles/variables.scss";
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: $accent;
|
||||||
|
border-color: $ombi-background-primary;
|
||||||
|
color:white;
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { Component, Inject, OnInit } from "@angular/core";
|
||||||
|
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { startWith, map } from "rxjs/operators";
|
||||||
|
import { IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUserDropdown, RequestType } from "../../interfaces";
|
||||||
|
import { IdentityService, MessageService, RadarrService, RequestService, SonarrService } from "../../services";
|
||||||
|
import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
|
|
||||||
|
export interface IAdminRequestDialogData {
|
||||||
|
type: RequestType,
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "admin-request-dialog",
|
||||||
|
templateUrl: "admin-request-dialog.component.html",
|
||||||
|
styleUrls: [ "admin-request-dialog.component.scss" ]
|
||||||
|
})
|
||||||
|
export class AdminRequestDialogComponent implements OnInit {
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<AdminRequestDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData,
|
||||||
|
private requestServiceV2: RequestServiceV2,
|
||||||
|
private notificationService: MessageService,
|
||||||
|
private identityService: IdentityService,
|
||||||
|
private sonarrService: SonarrService,
|
||||||
|
private radarrService: RadarrService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
private fb: FormBuilder
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public form: FormGroup;
|
||||||
|
public RequestType = RequestType;
|
||||||
|
|
||||||
|
public options: IUserDropdown[];
|
||||||
|
public filteredOptions: Observable<IUserDropdown[]>;
|
||||||
|
public userId: string;
|
||||||
|
|
||||||
|
public radarrEnabled: boolean;
|
||||||
|
|
||||||
|
|
||||||
|
public sonarrProfiles: ISonarrProfile[];
|
||||||
|
public sonarrRootFolders: ISonarrRootFolder[];
|
||||||
|
public radarrProfiles: IRadarrProfile[];
|
||||||
|
public radarrRootFolders: IRadarrRootFolder[];
|
||||||
|
|
||||||
|
public async ngOnInit() {
|
||||||
|
|
||||||
|
this.form = this.fb.group({
|
||||||
|
username: [null],
|
||||||
|
sonarrPathId: [null],
|
||||||
|
sonarrFolderId: [null],
|
||||||
|
radarrPathId: [null],
|
||||||
|
radarrFolderId: [null]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.options = await this.identityService.getUsersDropdown().toPromise();
|
||||||
|
|
||||||
|
this.filteredOptions = this.form.controls['username'].valueChanges.pipe(
|
||||||
|
startWith(""),
|
||||||
|
map((value) => this._filter(value))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.data.type === RequestType.tvShow) {
|
||||||
|
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => {
|
||||||
|
this.sonarrProfiles = c;
|
||||||
|
});
|
||||||
|
this.sonarrService.getRootFoldersWithoutSettings().subscribe(c => {
|
||||||
|
this.sonarrRootFolders = c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.data.type === RequestType.movie) {
|
||||||
|
this.radarrEnabled = await this.radarrService.isRadarrEnabled();
|
||||||
|
if (this.radarrEnabled) {
|
||||||
|
this.radarrService.getQualityProfilesFromSettings().subscribe(c => {
|
||||||
|
this.radarrProfiles = c;
|
||||||
|
});
|
||||||
|
this.radarrService.getRootFoldersFromSettings().subscribe(c => {
|
||||||
|
this.radarrRootFolders = c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public displayFn(user: IUserDropdown): string {
|
||||||
|
const username = user?.username ? user.username : "";
|
||||||
|
const email = user?.email ? `(${user.email})` : "";
|
||||||
|
return `${username} ${email}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filter(value: string | IUserDropdown): IUserDropdown[] {
|
||||||
|
const filterValue =
|
||||||
|
typeof value === "string"
|
||||||
|
? value.toLowerCase()
|
||||||
|
: value.username.toLowerCase();
|
||||||
|
|
||||||
|
return this.options.filter((option) =>
|
||||||
|
option.username.toLowerCase().includes(filterValue)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async submitRequest() {
|
||||||
|
const model = this.form.value;
|
||||||
|
if (this.data.type === RequestType.movie) {
|
||||||
|
this.requestService.requestMovie({
|
||||||
|
qualityPathOverride: model.radarrPathId,
|
||||||
|
requestOnBehalf: model.username?.id,
|
||||||
|
rootFolderOverride: model.radarrFolderId,
|
||||||
|
theMovieDbId: this.data.id,
|
||||||
|
languageCode: null
|
||||||
|
}).subscribe((x) => {
|
||||||
|
if (x.result) {
|
||||||
|
this.notificationService.send(x.message, "Ok");
|
||||||
|
} else {
|
||||||
|
this.notificationService.send(x.errorMessage, "Ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dialogRef.close();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
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 { MomentModule } from "ngx-moment";
|
import { MomentModule } from "ngx-moment";
|
||||||
|
@ -37,15 +37,18 @@ import { MatSlideToggleModule } from "@angular/material/slide-toggle";
|
||||||
import { MatTabsModule } from "@angular/material/tabs";
|
import { MatTabsModule } from "@angular/material/tabs";
|
||||||
import { EpisodeRequestComponent } from "./episode-request/episode-request.component";
|
import { EpisodeRequestComponent } from "./episode-request/episode-request.component";
|
||||||
import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component";
|
import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component";
|
||||||
|
import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-request-dialog.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
IssuesReportComponent,
|
IssuesReportComponent,
|
||||||
EpisodeRequestComponent,
|
EpisodeRequestComponent,
|
||||||
DetailsGroupComponent,
|
DetailsGroupComponent,
|
||||||
|
AdminRequestDialogComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
SidebarModule,
|
SidebarModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
InputSwitchModule,
|
InputSwitchModule,
|
||||||
|
@ -85,6 +88,7 @@ import { DetailsGroupComponent } from "../issues/components/details-group/detail
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
IssuesReportComponent,
|
IssuesReportComponent,
|
||||||
EpisodeRequestComponent,
|
EpisodeRequestComponent,
|
||||||
|
AdminRequestDialogComponent,
|
||||||
DetailsGroupComponent,
|
DetailsGroupComponent,
|
||||||
TruncateModule,
|
TruncateModule,
|
||||||
InputSwitchModule,
|
InputSwitchModule,
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
|
||||||
|
|
||||||
<mat-tab label="Preferences"> Coming Soon... </mat-tab>
|
<!-- <mat-tab label="Preferences"> Coming Soon... </mat-tab> -->
|
||||||
|
|
||||||
|
|
||||||
<mat-tab label="Mobile">
|
<mat-tab label="Mobile">
|
||||||
|
|
|
@ -142,8 +142,8 @@
|
||||||
background-color: $ombi-active;
|
background-color: $ombi-active;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr{
|
hr {
|
||||||
border-top: 1px solid $ombi-background-primary;
|
border-top: 1px solid $accent-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control{
|
.form-control{
|
||||||
|
|
|
@ -67,7 +67,8 @@ namespace Ombi.Controllers.V1
|
||||||
IMovieRequestEngine movieRequestEngine,
|
IMovieRequestEngine movieRequestEngine,
|
||||||
ITvRequestEngine tvRequestEngine,
|
ITvRequestEngine tvRequestEngine,
|
||||||
IMusicRequestEngine musicEngine,
|
IMusicRequestEngine musicEngine,
|
||||||
IUserDeletionEngine deletionEngine)
|
IUserDeletionEngine deletionEngine,
|
||||||
|
ICacheService cacheService)
|
||||||
{
|
{
|
||||||
UserManager = user;
|
UserManager = user;
|
||||||
Mapper = mapper;
|
Mapper = mapper;
|
||||||
|
@ -95,10 +96,13 @@ namespace Ombi.Controllers.V1
|
||||||
_userQualityProfiles = userProfiles;
|
_userQualityProfiles = userProfiles;
|
||||||
MusicRequestEngine = musicEngine;
|
MusicRequestEngine = musicEngine;
|
||||||
_deletionEngine = deletionEngine;
|
_deletionEngine = deletionEngine;
|
||||||
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OmbiUserManager UserManager { get; }
|
private OmbiUserManager UserManager { get; }
|
||||||
private readonly IUserDeletionEngine _deletionEngine;
|
private readonly IUserDeletionEngine _deletionEngine;
|
||||||
|
private readonly ICacheService _cacheService;
|
||||||
|
|
||||||
private RoleManager<IdentityRole> RoleManager { get; }
|
private RoleManager<IdentityRole> RoleManager { get; }
|
||||||
private IMapper Mapper { get; }
|
private IMapper Mapper { get; }
|
||||||
private IEmailProvider EmailProvider { get; }
|
private IEmailProvider EmailProvider { get; }
|
||||||
|
@ -289,8 +293,8 @@ namespace Ombi.Controllers.V1
|
||||||
[PowerUser]
|
[PowerUser]
|
||||||
public async Task<IEnumerable<UserViewModelDropdown>> GetAllUsersDropdown()
|
public async Task<IEnumerable<UserViewModelDropdown>> GetAllUsersDropdown()
|
||||||
{
|
{
|
||||||
var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser)
|
var users = await _cacheService.GetOrAdd(CacheKeys.UsersDropdown,
|
||||||
.ToListAsync();
|
async () => await UserManager.Users.Where(x => x.UserType != UserType.SystemUser).ToListAsync());
|
||||||
|
|
||||||
var model = new List<UserViewModelDropdown>();
|
var model = new List<UserViewModelDropdown>();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue