mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-14 02:26:55 -07:00
changes
This commit is contained in:
parent
5b49d03f85
commit
f3d4b8a67c
16 changed files with 914 additions and 340 deletions
16
Ombi/Ombi.Core/Engine/ITvRequestEngine.cs
Normal file
16
Ombi/Ombi.Core/Engine/ITvRequestEngine.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public interface ITvRequestEngine
|
||||
{
|
||||
Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position);
|
||||
Task RemoveTvRequest(int requestId);
|
||||
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
|
||||
Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search);
|
||||
Task<TvRequestModel> UpdateTvRequest(TvRequestModel request);
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using Ombi.Store.Entities;
|
|||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public interface IRequestEngine
|
||||
public interface IMovieRequestEngine
|
||||
{
|
||||
Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model);
|
||||
bool ShouldAutoApprove(RequestType requestType);
|
|
@ -17,17 +17,15 @@ using Ombi.Notifications.Models;
|
|||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public class RequestEngine : BaseMediaEngine, IRequestEngine
|
||||
public class MovieRequestEngine : BaseMediaEngine, IMovieRequestEngine
|
||||
{
|
||||
public RequestEngine(IMovieDbApi movieApi, ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
|
||||
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
|
||||
{
|
||||
MovieApi = movieApi;
|
||||
TvApi = tvApi;
|
||||
NotificationService = notificationService;
|
||||
}
|
||||
private IMovieDbApi MovieApi { get; }
|
||||
private INotificationService NotificationService { get; }
|
||||
private ITvMazeApi TvApi { get; }
|
||||
public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model)
|
||||
{
|
||||
var movieInfo = await MovieApi.GetMovieInformation(model.Id);
|
||||
|
@ -161,41 +159,7 @@ namespace Ombi.Core.Engine
|
|||
return null;
|
||||
}
|
||||
|
||||
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
|
||||
{
|
||||
|
||||
var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id);
|
||||
DateTime.TryParse(showInfo.premiered, out DateTime firstAir);
|
||||
|
||||
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
|
||||
// For some reason the poster path is always http
|
||||
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
|
||||
var model = new TvRequestModel
|
||||
{
|
||||
Type = RequestType.TvShow,
|
||||
Overview = showInfo.summary.RemoveHtml(),
|
||||
PosterPath = posterPath,
|
||||
Title = showInfo.name,
|
||||
ReleaseDate = firstAir,
|
||||
Status = showInfo.status,
|
||||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUsers = new List<string> { Username },
|
||||
Issues = IssueState.None,
|
||||
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
||||
TvDbId = tv.Id.ToString(),
|
||||
ProviderId = tv.Id,
|
||||
SeasonsNumbersRequested = tv.SeasonNumbersRequested,
|
||||
RequestAll = tv.RequestAll
|
||||
};
|
||||
|
||||
|
||||
var existingRequest = await TvRequestService.CheckRequestAsync(model.Id);
|
||||
existingRequest?.ChildRequests.Add(model);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private IEnumerable<EpisodesModel> GetListDifferences(IEnumerable<EpisodesModel> existing, IEnumerable<EpisodesModel> request)
|
||||
{
|
||||
var newRequest = request
|
192
Ombi/Ombi.Core/Engine/TvRequestEngine.cs
Normal file
192
Ombi/Ombi.Core/Engine/TvRequestEngine.cs
Normal file
|
@ -0,0 +1,192 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications;
|
||||
using Ombi.Notifications.Models;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine
|
||||
{
|
||||
public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
|
||||
{
|
||||
TvApi = tvApi;
|
||||
NotificationService = notificationService;
|
||||
}
|
||||
private INotificationService NotificationService { get; }
|
||||
private ITvMazeApi TvApi { get; }
|
||||
|
||||
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
|
||||
{
|
||||
|
||||
var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id);
|
||||
DateTime.TryParse(showInfo.premiered, out DateTime firstAir);
|
||||
|
||||
// For some reason the poster path is always http
|
||||
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
|
||||
var model = new TvRequestModel
|
||||
{
|
||||
Type = RequestType.TvShow,
|
||||
Overview = showInfo.summary.RemoveHtml(),
|
||||
PosterPath = posterPath,
|
||||
Title = showInfo.name,
|
||||
ReleaseDate = firstAir,
|
||||
Status = showInfo.status,
|
||||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUsers = new List<string> { Username },
|
||||
Issues = IssueState.None,
|
||||
ImdbId = showInfo.externals?.imdb ?? string.Empty,
|
||||
TvDbId = tv.Id.ToString(),
|
||||
ProviderId = tv.Id,
|
||||
SeasonsNumbersRequested = tv.SeasonNumbersRequested,
|
||||
RequestAll = tv.RequestAll
|
||||
};
|
||||
|
||||
|
||||
var existingRequest = await TvRequestService.CheckRequestAsync(model.Id);
|
||||
if (existingRequest != null)
|
||||
{
|
||||
return await AddExistingRequest(model, existingRequest);
|
||||
}
|
||||
|
||||
// This is a new request
|
||||
return await AddRequest(model);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position)
|
||||
{
|
||||
var allRequests = await TvRequestService.GetAllAsync(count, position);
|
||||
return allRequests;
|
||||
}
|
||||
public async Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search)
|
||||
{
|
||||
var allRequests = await TvRequestService.GetAllAsync();
|
||||
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase));
|
||||
return results;
|
||||
}
|
||||
public async Task<TvRequestModel> UpdateTvRequest(TvRequestModel request)
|
||||
{
|
||||
var allRequests = await TvRequestService.GetAllAsync();
|
||||
var results = allRequests.FirstOrDefault(x => x.Id == request.Id);
|
||||
|
||||
results.Approved = request.Approved;
|
||||
results.Available = request.Available;
|
||||
results.Denied = request.Denied;
|
||||
results.DeniedReason = request.DeniedReason;
|
||||
results.AdminNote = request.AdminNote;
|
||||
results.ImdbId = request.ImdbId;
|
||||
results.IssueId = request.IssueId;
|
||||
results.Issues = request.Issues;
|
||||
results.OtherMessage = request.OtherMessage;
|
||||
results.Overview = request.Overview;
|
||||
results.PosterPath = request.PosterPath;
|
||||
results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List<string>();
|
||||
|
||||
var model = TvRequestService.UpdateRequest(results);
|
||||
return model;
|
||||
}
|
||||
|
||||
public async Task RemoveTvRequest(int requestId)
|
||||
{
|
||||
await TvRequestService.DeleteRequestAsync(requestId);
|
||||
}
|
||||
|
||||
private async Task<RequestEngineResult> AddExistingRequest(TvRequestModel newRequest, TvRequestModel existingRequest)
|
||||
{
|
||||
var episodeDifference = new List<EpisodesModel>();
|
||||
if (existingRequest.HasChildRequests)
|
||||
{
|
||||
// Let's check if this has already been requested as a child!
|
||||
foreach (var children in existingRequest.ChildRequests)
|
||||
{
|
||||
var difference = GetListDifferences(children.Episodes, newRequest.Episodes).ToList();
|
||||
if (difference.Any())
|
||||
{
|
||||
episodeDifference = difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (episodeDifference.Any())
|
||||
{
|
||||
// This is where there are some episodes that have been requested, but this list contains the 'new' requests
|
||||
newRequest.Episodes = episodeDifference;
|
||||
}
|
||||
|
||||
existingRequest.ChildRequests.Add(newRequest);
|
||||
|
||||
TvRequestService.UpdateRequest(existingRequest);
|
||||
|
||||
if (ShouldAutoApprove(RequestType.TvShow))
|
||||
{
|
||||
// TODO Auto Approval Code
|
||||
}
|
||||
return await AddRequest(newRequest);
|
||||
}
|
||||
|
||||
private IEnumerable<EpisodesModel> GetListDifferences(IEnumerable<EpisodesModel> existing, IEnumerable<EpisodesModel> request)
|
||||
{
|
||||
var newRequest = request
|
||||
.Select(r =>
|
||||
new EpisodesModel
|
||||
{
|
||||
SeasonNumber = r.SeasonNumber,
|
||||
EpisodeNumber = r.EpisodeNumber
|
||||
}).ToList();
|
||||
|
||||
return newRequest.Except(existing);
|
||||
}
|
||||
|
||||
private async Task<RequestEngineResult> AddRequest(TvRequestModel model)
|
||||
{
|
||||
await TvRequestService.AddRequestAsync(model);
|
||||
|
||||
if (ShouldSendNotification(model.Type))
|
||||
{
|
||||
var notificationModel = new NotificationModel
|
||||
{
|
||||
Title = model.Title,
|
||||
User = Username,
|
||||
DateTime = DateTime.Now,
|
||||
NotificationType = NotificationType.NewRequest,
|
||||
RequestType = model.Type,
|
||||
ImgSrc = model.PosterPath
|
||||
};
|
||||
|
||||
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait());
|
||||
}
|
||||
|
||||
//var limit = await RequestLimitRepo.GetAllAsync();
|
||||
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
|
||||
//if (usersLimit == null)
|
||||
//{
|
||||
// await RequestLimitRepo.InsertAsync(new RequestLimit
|
||||
// {
|
||||
// Username = Username,
|
||||
// RequestType = model.Type,
|
||||
// FirstRequestDate = DateTime.UtcNow,
|
||||
// RequestCount = 1
|
||||
// });
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// usersLimit.RequestCount++;
|
||||
// await RequestLimitRepo.UpdateAsync(usersLimit);
|
||||
//}
|
||||
|
||||
return new RequestEngineResult { RequestAdded = true };
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Ombi.Core.Models.Requests
|
|||
|
||||
public string ImdbId { get; set; }
|
||||
public string TvDbId { get; set; }
|
||||
public bool RequestAll { get; set; }
|
||||
public bool RequestAll { get; set; }
|
||||
public List<int> SeasonsNumbersRequested { get; set; }
|
||||
public List<EpisodesModel> Episodes { get; set; }
|
||||
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace Ombi.DependencyInjection
|
|||
public static IServiceCollection RegisterEngines(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IMovieEngine, MovieSearchEngine>();
|
||||
services.AddTransient<IRequestEngine, RequestEngine>();
|
||||
services.AddTransient<IMovieRequestEngine, MovieRequestEngine>();
|
||||
services.AddTransient<ITvRequestEngine, TvRequestEngine>();
|
||||
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
|
||||
return services;
|
||||
}
|
||||
|
|
|
@ -11,49 +11,77 @@ namespace Ombi.Controllers
|
|||
[Authorize]
|
||||
public class RequestController : BaseV1ApiController
|
||||
{
|
||||
public RequestController(IRequestEngine engine)
|
||||
public RequestController(IMovieRequestEngine engine, ITvRequestEngine tvRequestEngine)
|
||||
{
|
||||
RequestEngine = engine;
|
||||
MovieRequestEngine = engine;
|
||||
TvRequestEngine = tvRequestEngine;
|
||||
}
|
||||
|
||||
private IRequestEngine RequestEngine { get; }
|
||||
|
||||
private IMovieRequestEngine MovieRequestEngine { get; }
|
||||
private ITvRequestEngine TvRequestEngine { get; }
|
||||
|
||||
[HttpGet("movie/{count:int}/{position:int}", Name = "GetRequestsByCount")]
|
||||
|
||||
[HttpGet("movie/{count:int}/{position:int}")]
|
||||
public async Task<IEnumerable<MovieRequestModel>> GetRequests(int count, int position)
|
||||
{
|
||||
return await RequestEngine.GetMovieRequests(count, position);
|
||||
return await MovieRequestEngine.GetMovieRequests(count, position);
|
||||
}
|
||||
|
||||
[HttpPost("movie")]
|
||||
public async Task<RequestEngineResult> RequestMovie([FromBody]SearchMovieViewModel movie)
|
||||
{
|
||||
return await RequestEngine.RequestMovie(movie);
|
||||
return await MovieRequestEngine.RequestMovie(movie);
|
||||
}
|
||||
|
||||
//[HttpPost("tv")]
|
||||
//public async Task<RequestEngineResult> RequestTv([FromBody]SearchTvShowViewModel tv)
|
||||
//{
|
||||
// return await RequestEngine.RequestMovie();
|
||||
//}
|
||||
|
||||
[HttpGet("movie/search/{searchTerm}")]
|
||||
public async Task<IEnumerable<MovieRequestModel>> Search(string searchTerm)
|
||||
{
|
||||
|
||||
return await RequestEngine.SearchMovieRequest(searchTerm);
|
||||
|
||||
return await MovieRequestEngine.SearchMovieRequest(searchTerm);
|
||||
}
|
||||
|
||||
[HttpDelete("movie/{requestId:int}")]
|
||||
public async Task DeleteRequest(int requestId)
|
||||
{
|
||||
await RequestEngine.RemoveMovieRequest(requestId);
|
||||
await MovieRequestEngine.RemoveMovieRequest(requestId);
|
||||
}
|
||||
|
||||
[HttpPut("movie")]
|
||||
public async Task<MovieRequestModel> UpdateRequest([FromBody]MovieRequestModel model)
|
||||
{
|
||||
return await RequestEngine.UpdateMovieRequest(model);
|
||||
return await MovieRequestEngine.UpdateMovieRequest(model);
|
||||
}
|
||||
|
||||
[HttpGet("tv/{count:int}/{position:int}")]
|
||||
public async Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position)
|
||||
{
|
||||
return await TvRequestEngine.GetTvRequests(count, position);
|
||||
}
|
||||
|
||||
[HttpPost("tv")]
|
||||
public async Task<RequestEngineResult> RequestTv([FromBody]SearchTvShowViewModel tv)
|
||||
{
|
||||
return await TvRequestEngine.RequestTvShow(tv);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("tv/search/{searchTerm}")]
|
||||
public async Task<IEnumerable<TvRequestModel>> SearchTv(string searchTerm)
|
||||
{
|
||||
|
||||
return await TvRequestEngine.SearchTvRequest(searchTerm);
|
||||
}
|
||||
|
||||
[HttpDelete("tv/{requestId:int}")]
|
||||
public async Task DeleteTvRequest(int requestId)
|
||||
{
|
||||
await TvRequestEngine.RemoveTvRequest(requestId);
|
||||
}
|
||||
|
||||
[HttpPut("tv")]
|
||||
public async Task<TvRequestModel> UpdateRequest([FromBody]TvRequestModel model)
|
||||
{
|
||||
return await TvRequestEngine.UpdateTvRequest(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,28 @@
|
|||
<Content Update="wwwroot\app\interfaces\ISearchTvResult.ts">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\movierequests - Copy.component.html">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\tvrequests.component.js">
|
||||
<DependentUpon>tvrequests.component.ts</DependentUpon>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\movierequests - Copy.component.js">
|
||||
<DependentUpon>movierequests - Copy.component.ts</DependentUpon>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\tvrequests.component.ts">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\request - Copy.component.js">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\request - Copy.component.js.map">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\requests\request - Copy.component.ts">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app\search\moviesearch - Copy.component.html">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
|
|
|
@ -11,10 +11,16 @@ import { HttpModule } from '@angular/http';
|
|||
import { InfiniteScrollModule } from 'ngx-infinite-scroll'
|
||||
|
||||
// Components
|
||||
// Search
|
||||
import { SearchComponent } from './search/search.component';
|
||||
import { MovieSearchComponent } from './search/moviesearch.component';
|
||||
import { TvSearchComponent } from './search/tvsearch.component';
|
||||
|
||||
// Request
|
||||
import { RequestComponent } from './requests/request.component';
|
||||
import { MovieRequestsComponent } from './requests/movierequests.component';
|
||||
import { TvRequestsComponent } from './requests/tvrequests.component';
|
||||
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { LandingPageComponent } from './landingpage/landingpage.component';
|
||||
import { UserManagementComponent } from './usermanagement/usermanagement.component';
|
||||
|
@ -75,7 +81,9 @@ const routes: Routes = [
|
|||
MovieSearchComponent,
|
||||
TvSearchComponent,
|
||||
LandingPageComponent,
|
||||
UserManagementComponent
|
||||
UserManagementComponent,
|
||||
MovieRequestsComponent,
|
||||
TvRequestsComponent
|
||||
],
|
||||
providers: [
|
||||
SearchService,
|
||||
|
|
178
Ombi/Ombi/wwwroot/app/requests/movierequests.component.html
Normal file
178
Ombi/Ombi/wwwroot/app/requests/movierequests.component.html
Normal file
|
@ -0,0 +1,178 @@
|
|||
<div class="form-group">
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollThrottle]="100"
|
||||
(scrolled)="loadMore()">
|
||||
|
||||
|
||||
<div *ngFor="let request of movieRequests">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
|
||||
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 ">
|
||||
<div>
|
||||
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
|
||||
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<span>Status: </span>
|
||||
<span class="label label-success">{{request.status}}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Request status: </span>
|
||||
<span *ngIf="request.available" class="label label-success">Available</span>
|
||||
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
|
||||
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied">
|
||||
Denied: <i style="color:red;" class="fa fa-check"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>Release Date: {{request.releaseDate | date}}</div>
|
||||
<br/>
|
||||
|
||||
<!--{{#if_eq type "tv"}}
|
||||
{{#if episodes}}
|
||||
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
|
||||
{{else}}
|
||||
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
|
||||
|
||||
{{/if}}
|
||||
{{/if_eq}}-->
|
||||
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
|
||||
|
||||
<div>Requested Date: {{request.requestedDate | date}}</div>
|
||||
<!--{{#if admin}}
|
||||
{{#if currentRootPath}}
|
||||
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div>
|
||||
{{#if_eq issueId 0}}
|
||||
@*Nothing*@
|
||||
{{else}}
|
||||
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
|
||||
{{/if_eq}}
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="col-sm-3 col-sm-push-3">
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div *ngIf="request.hasQualities" class="btn-group btn-split">
|
||||
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
|
||||
<button type="button" class="btn btn-success-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">
|
||||
{{#each qualities}}
|
||||
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>-->
|
||||
</div>
|
||||
|
||||
|
||||
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
|
||||
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
|
||||
{{#if_eq hasRootFolders true}}
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each rootFolders}}
|
||||
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if_eq}}
|
||||
</form>-->
|
||||
|
||||
|
||||
|
||||
<div *ngIf="!request.denied">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
|
||||
<input name="reason" type="text" hidden="hidden"/>
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
|
||||
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
|
||||
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div class="dropdown">
|
||||
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
|
||||
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
|
||||
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
|
||||
<li><a issue-select="3">@UI.Issues_Playback</a></li>
|
||||
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
109
Ombi/Ombi/wwwroot/app/requests/movierequests.component.ts
Normal file
109
Ombi/Ombi/wwwroot/app/requests/movierequests.component.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { RequestService } from '../services/request.service';
|
||||
import { IdentityService } from '../services/identity.service';
|
||||
|
||||
import { IMovieRequestModel } from '../interfaces/IRequestModel';
|
||||
|
||||
@Component({
|
||||
selector: 'movie-requests',
|
||||
moduleId: module.id,
|
||||
templateUrl: './movierequests.component.html'
|
||||
})
|
||||
export class MovieRequestsComponent implements OnInit {
|
||||
constructor(private requestService: RequestService, private identityService: IdentityService) {
|
||||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
.subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
this.resetSearch();
|
||||
return;
|
||||
}
|
||||
this.requestService.searchMovieRequests(this.searchText).subscribe(m => this.movieRequests = m);
|
||||
});
|
||||
}
|
||||
|
||||
movieRequests: IMovieRequestModel[];
|
||||
|
||||
searchChanged: Subject<string> = new Subject<string>();
|
||||
searchText: string;
|
||||
|
||||
isAdmin : boolean;
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad : number;
|
||||
|
||||
ngOnInit() {
|
||||
this.amountToLoad = 5;
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadMore() {
|
||||
this.requestService.getMovieRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
|
||||
this.movieRequests.push.apply(this.movieRequests, x);
|
||||
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
|
||||
});
|
||||
}
|
||||
|
||||
search(text: any) {
|
||||
this.searchChanged.next(text.target.value);
|
||||
}
|
||||
|
||||
removeRequest(request: IMovieRequestModel) {
|
||||
this.requestService.removeMovieRequest(request);
|
||||
this.removeRequestFromUi(request);
|
||||
}
|
||||
|
||||
changeAvailability(request: IMovieRequestModel, available: boolean) {
|
||||
request.available = available;
|
||||
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
approve(request: IMovieRequestModel) {
|
||||
request.approved = true;
|
||||
request.denied = false;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
deny(request: IMovieRequestModel) {
|
||||
request.approved = false;
|
||||
request.denied = true;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
private updateRequest(request: IMovieRequestModel) {
|
||||
this.requestService.updateMovieRequest(request).subscribe(x => request = x);
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getMovieRequests(this.amountToLoad, 0).subscribe(x => this.movieRequests = x);
|
||||
this.isAdmin = this.identityService.hasRole("Admin");
|
||||
}
|
||||
|
||||
private resetSearch() {
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IMovieRequestModel) {
|
||||
var index = this.movieRequests.indexOf(key, 0);
|
||||
if (index > -1) {
|
||||
this.movieRequests.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,180 +1,25 @@
|
|||
<h1 id="searchTitle">Requests</h1>
|
||||
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
||||
|
||||
|
||||
|
||||
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a id="movieTabButton" aria-controls="home" role="tab" data-toggle="tab" (click)="selectTab()"><i class="fa fa-film"></i> Movies</a>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTab()"><i class="fa fa-television"></i> TV Shows</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div [hidden]="!showMovie">
|
||||
<movie-requests></movie-requests>
|
||||
</div>
|
||||
<div [hidden]="!showTv">
|
||||
<tv-requests></tv-requests>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollThrottle]="100"
|
||||
(scrolled)="loadMore()">
|
||||
|
||||
|
||||
<div *ngFor="let request of movieRequests">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
|
||||
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 ">
|
||||
<div>
|
||||
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
|
||||
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<span>Status: </span>
|
||||
<span class="label label-success">{{request.status}}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Request status: </span>
|
||||
<span *ngIf="request.available" class="label label-success">Available</span>
|
||||
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
|
||||
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied">
|
||||
Denied: <i style="color:red;" class="fa fa-check"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>Release Date: {{request.releaseDate | date}}</div>
|
||||
<br/>
|
||||
|
||||
<!--{{#if_eq type "tv"}}
|
||||
{{#if episodes}}
|
||||
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
|
||||
{{else}}
|
||||
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
|
||||
|
||||
{{/if}}
|
||||
{{/if_eq}}-->
|
||||
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
|
||||
|
||||
<div>Requested Date: {{request.requestedDate | date}}</div>
|
||||
<!--{{#if admin}}
|
||||
{{#if currentRootPath}}
|
||||
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div>
|
||||
{{#if_eq issueId 0}}
|
||||
@*Nothing*@
|
||||
{{else}}
|
||||
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
|
||||
{{/if_eq}}
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="col-sm-3 col-sm-push-3">
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div *ngIf="request.hasQualities" class="btn-group btn-split">
|
||||
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
|
||||
<button type="button" class="btn btn-success-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">
|
||||
{{#each qualities}}
|
||||
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>-->
|
||||
</div>
|
||||
|
||||
|
||||
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
|
||||
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
|
||||
{{#if_eq hasRootFolders true}}
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each rootFolders}}
|
||||
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if_eq}}
|
||||
</form>-->
|
||||
|
||||
|
||||
|
||||
<div *ngIf="!request.denied">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
|
||||
<input name="reason" type="text" hidden="hidden"/>
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
|
||||
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
|
||||
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div class="dropdown">
|
||||
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
|
||||
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
|
||||
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
|
||||
<li><a issue-select="3">@UI.Issues_Playback</a></li>
|
||||
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,110 +1,18 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { RequestService } from '../services/request.service';
|
||||
import { IdentityService } from '../services/identity.service';
|
||||
|
||||
import { IMovieRequestModel, ITvRequestModel } from '../interfaces/IRequestModel';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ombi',
|
||||
moduleId: module.id,
|
||||
templateUrl: './request.component.html'
|
||||
})
|
||||
export class RequestComponent implements OnInit {
|
||||
constructor(private requestService: RequestService, private identityService: IdentityService) {
|
||||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
.subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
this.resetSearch();
|
||||
return;
|
||||
}
|
||||
this.requestService.searchRequests(this.searchText).subscribe(x => this.movieRequests = x);
|
||||
});
|
||||
export class RequestComponent {
|
||||
|
||||
showMovie = true;
|
||||
showTv = false;
|
||||
|
||||
selectTab() {
|
||||
this.showMovie = !this.showMovie;
|
||||
this.showTv = !this.showTv;
|
||||
}
|
||||
|
||||
movieRequests: IMovieRequestModel[];
|
||||
tvRequests: ITvRequestModel[];
|
||||
|
||||
searchChanged: Subject<string> = new Subject<string>();
|
||||
searchText: string;
|
||||
|
||||
isAdmin : boolean;
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad : number;
|
||||
|
||||
ngOnInit() {
|
||||
this.amountToLoad = 5;
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadMore() {
|
||||
this.requestService.getRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
|
||||
this.movieRequests.push.apply(this.movieRequests, x);
|
||||
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
|
||||
});
|
||||
}
|
||||
|
||||
search(text: any) {
|
||||
this.searchChanged.next(text.target.value);
|
||||
}
|
||||
|
||||
removeRequest(request: IMovieRequestModel) {
|
||||
this.requestService.removeMovieRequest(request);
|
||||
this.removeRequestFromUi(request);
|
||||
}
|
||||
|
||||
changeAvailability(request: IMovieRequestModel, available: boolean) {
|
||||
request.available = available;
|
||||
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
approve(request: IMovieRequestModel) {
|
||||
request.approved = true;
|
||||
request.denied = false;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
deny(request: IMovieRequestModel) {
|
||||
request.approved = false;
|
||||
request.denied = true;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
private updateRequest(request: IMovieRequestModel) {
|
||||
this.requestService.updateRequest(request).subscribe(x => request = x);
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getRequests(this.amountToLoad, 0).subscribe(x => this.movieRequests = x);
|
||||
this.isAdmin = this.identityService.hasRole("Admin");
|
||||
}
|
||||
|
||||
private resetSearch() {
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IMovieRequestModel) {
|
||||
var index = this.movieRequests.indexOf(key, 0);
|
||||
if (index > -1) {
|
||||
this.movieRequests.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
178
Ombi/Ombi/wwwroot/app/requests/tvrequests.component.html
Normal file
178
Ombi/Ombi/wwwroot/app/requests/tvrequests.component.html
Normal file
|
@ -0,0 +1,178 @@
|
|||
<div class="form-group">
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div infinite-scroll
|
||||
[infiniteScrollDistance]="1"
|
||||
[infiniteScrollThrottle]="100"
|
||||
(scrolled)="loadMore()">
|
||||
|
||||
|
||||
<div *ngFor="let request of tvRequests">
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
|
||||
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-5 ">
|
||||
<div>
|
||||
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
|
||||
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<span>Status: </span>
|
||||
<span class="label label-success">{{request.status}}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>Request status: </span>
|
||||
<span *ngIf="request.available" class="label label-success">Available</span>
|
||||
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
|
||||
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied">
|
||||
Denied: <i style="color:red;" class="fa fa-check"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>Release Date: {{request.releaseDate | date}}</div>
|
||||
<br/>
|
||||
|
||||
<!--{{#if_eq type "tv"}}
|
||||
{{#if episodes}}
|
||||
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
|
||||
{{else}}
|
||||
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
|
||||
|
||||
{{/if}}
|
||||
{{/if_eq}}-->
|
||||
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
|
||||
|
||||
<div>Requested Date: {{request.requestedDate | date}}</div>
|
||||
<!--{{#if admin}}
|
||||
{{#if currentRootPath}}
|
||||
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<div>
|
||||
{{#if_eq issueId 0}}
|
||||
@*Nothing*@
|
||||
{{else}}
|
||||
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
|
||||
{{/if_eq}}
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="col-sm-3 col-sm-push-3">
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div *ngIf="request.hasQualities" class="btn-group btn-split">
|
||||
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
|
||||
<button type="button" class="btn btn-success-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">
|
||||
{{#each qualities}}
|
||||
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>-->
|
||||
</div>
|
||||
|
||||
|
||||
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
|
||||
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
|
||||
{{#if_eq hasRootFolders true}}
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each rootFolders}}
|
||||
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if_eq}}
|
||||
</form>-->
|
||||
|
||||
|
||||
|
||||
<div *ngIf="!request.denied">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
|
||||
<input name="reason" type="text" hidden="hidden"/>
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
|
||||
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
|
||||
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div class="dropdown">
|
||||
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
|
||||
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
|
||||
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
|
||||
<li><a issue-select="3">@UI.Issues_Playback</a></li>
|
||||
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
109
Ombi/Ombi/wwwroot/app/requests/tvrequests.component.ts
Normal file
109
Ombi/Ombi/wwwroot/app/requests/tvrequests.component.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { RequestService } from '../services/request.service';
|
||||
import { IdentityService } from '../services/identity.service';
|
||||
|
||||
import { ITvRequestModel } from '../interfaces/IRequestModel';
|
||||
|
||||
@Component({
|
||||
selector: 'tv-requests',
|
||||
moduleId: module.id,
|
||||
templateUrl: './tvrequests.component.html'
|
||||
})
|
||||
export class TvRequestsComponent implements OnInit {
|
||||
constructor(private requestService: RequestService, private identityService: IdentityService) {
|
||||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
.subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
this.resetSearch();
|
||||
return;
|
||||
}
|
||||
this.requestService.searchTvRequests(this.searchText).subscribe(m => this.tvRequests = m);
|
||||
});
|
||||
}
|
||||
|
||||
tvRequests: ITvRequestModel[];
|
||||
|
||||
searchChanged = new Subject<string>();
|
||||
searchText: string;
|
||||
|
||||
isAdmin : boolean;
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad : number;
|
||||
|
||||
ngOnInit() {
|
||||
this.amountToLoad = 5;
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
|
||||
|
||||
loadMore() {
|
||||
this.requestService.getTvRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
|
||||
this.tvRequests.push.apply(this.tvRequests, x);
|
||||
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
|
||||
});
|
||||
}
|
||||
|
||||
search(text: any) {
|
||||
this.searchChanged.next(text.target.value);
|
||||
}
|
||||
|
||||
removeRequest(request: ITvRequestModel) {
|
||||
this.requestService.removeTvRequest(request);
|
||||
this.removeRequestFromUi(request);
|
||||
}
|
||||
|
||||
changeAvailability(request: ITvRequestModel, available: boolean) {
|
||||
request.available = available;
|
||||
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
approve(request: ITvRequestModel) {
|
||||
request.approved = true;
|
||||
request.denied = false;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
deny(request: ITvRequestModel) {
|
||||
request.approved = false;
|
||||
request.denied = true;
|
||||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
private updateRequest(request: ITvRequestModel) {
|
||||
this.requestService.updateTvRequest(request).subscribe(x => request = x);
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getTvRequests(this.amountToLoad, 0).subscribe(x => this.tvRequests = x);
|
||||
this.isAdmin = this.identityService.hasRole("Admin");
|
||||
}
|
||||
|
||||
private resetSearch() {
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: ITvRequestModel) {
|
||||
var index = this.tvRequests.indexOf(key, 0);
|
||||
if (index > -1) {
|
||||
this.tvRequests.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ import { ServiceAuthHelpers } from './service.helpers';
|
|||
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
|
||||
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
|
||||
import { ISearchTvResult } from '../interfaces/ISearchTvResult';
|
||||
import { IMovieRequestModel } from '../interfaces/IRequestModel';
|
||||
import { IMovieRequestModel, ITvRequestModel } from '../interfaces/IRequestModel';
|
||||
|
||||
@Injectable()
|
||||
export class RequestService extends ServiceAuthHelpers {
|
||||
|
@ -22,11 +22,11 @@ export class RequestService extends ServiceAuthHelpers {
|
|||
return this.http.post(`${this.url}/TV/`, JSON.stringify(tv), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
getRequests(count: number, position: number): Observable<IMovieRequestModel[]> {
|
||||
getMovieRequests(count: number, position: number): Observable<IMovieRequestModel[]> {
|
||||
return this.http.get(`${this.url}/movie/${count}/${position}`).map(this.extractData);
|
||||
}
|
||||
|
||||
searchRequests(search: string): Observable<IMovieRequestModel[]> {
|
||||
searchMovieRequests(search: string): Observable<IMovieRequestModel[]> {
|
||||
return this.http.get(`${this.url}/movie/search/${search}`).map(this.extractData);
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,23 @@ export class RequestService extends ServiceAuthHelpers {
|
|||
this.http.delete(`${this.url}/movie/${request.id}`).map(this.extractData).subscribe();
|
||||
}
|
||||
|
||||
updateRequest(request: IMovieRequestModel): Observable<IMovieRequestModel> {
|
||||
updateMovieRequest(request: IMovieRequestModel): Observable<IMovieRequestModel> {
|
||||
return this.http.post(`${this.url}/movie/`, JSON.stringify(request), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
getTvRequests(count: number, position: number): Observable<ITvRequestModel[]> {
|
||||
return this.http.get(`${this.url}/tv/${count}/${position}`).map(this.extractData);
|
||||
}
|
||||
|
||||
searchTvRequests(search: string): Observable<ITvRequestModel[]> {
|
||||
return this.http.get(`${this.url}/tv/search/${search}`).map(this.extractData);
|
||||
}
|
||||
|
||||
removeTvRequest(request: ITvRequestModel) {
|
||||
this.http.delete(`${this.url}/tv/${request.id}`).map(this.extractData).subscribe();
|
||||
}
|
||||
|
||||
updateTvRequest(request: ITvRequestModel): Observable<ITvRequestModel> {
|
||||
return this.http.post(`${this.url}/tv/`, JSON.stringify(request), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue