mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-14 02:26:55 -07:00
Got the album reques list in place
This commit is contained in:
parent
73353a6aa5
commit
6d339e7a1c
20 changed files with 544 additions and 51 deletions
|
@ -23,5 +23,7 @@ namespace Ombi.Core.Engine
|
|||
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
|
||||
Task<bool> UserHasRequest(string userId);
|
||||
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
|
||||
Task<RequestsViewModel<AlbumRequest>> GetRequestsByStatus(int count, int position, string sort, string sortOrder, RequestStatus available);
|
||||
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, string sort, string sortOrder);
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ using Ombi.Settings.Settings.Models;
|
|||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
|
@ -253,6 +254,28 @@ namespace Ombi.Core.Engine
|
|||
return allRequests;
|
||||
}
|
||||
|
||||
|
||||
private async Task CheckForSubscription(HideResult shouldHide, List<AlbumRequest> albumRequests)
|
||||
{
|
||||
var requestIds = albumRequests.Select(x => x.Id);
|
||||
var sub = await _subscriptionRepository.GetAll().Where(s =>
|
||||
s.UserId == shouldHide.UserId && requestIds.Contains(s.RequestId) && s.RequestType == RequestType.Movie)
|
||||
.ToListAsync();
|
||||
foreach (var x in albumRequests)
|
||||
{
|
||||
if (shouldHide.UserId == x.RequestedUserId)
|
||||
{
|
||||
x.ShowSubscribe = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
x.ShowSubscribe = true;
|
||||
var hasSub = sub.FirstOrDefault(r => r.RequestId == x.Id);
|
||||
x.Subscribed = hasSub != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x)
|
||||
{
|
||||
if (shouldHide.UserId == x.RequestedUserId)
|
||||
|
@ -500,6 +523,110 @@ namespace Ombi.Core.Engine
|
|||
return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!", RequestId = model.Id };
|
||||
}
|
||||
|
||||
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetRequestsByStatus(int count, int position, string sortProperty, string sortOrder, RequestStatus status)
|
||||
{
|
||||
var shouldHide = await HideFromOtherUsers();
|
||||
IQueryable<AlbumRequest> allRequests;
|
||||
if (shouldHide.Hide)
|
||||
{
|
||||
allRequests =
|
||||
MusicRepository.GetWithUser(shouldHide
|
||||
.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
allRequests =
|
||||
MusicRepository
|
||||
.GetWithUser();
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case RequestStatus.PendingApproval:
|
||||
allRequests = allRequests.Where(x => !x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value));
|
||||
break;
|
||||
case RequestStatus.ProcessingRequest:
|
||||
allRequests = allRequests.Where(x => x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value));
|
||||
break;
|
||||
case RequestStatus.Available:
|
||||
allRequests = allRequests.Where(x => x.Available);
|
||||
break;
|
||||
case RequestStatus.Denied:
|
||||
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var prop = TypeDescriptor.GetProperties(typeof(AlbumRequest)).Find(sortProperty, true);
|
||||
|
||||
if (sortProperty.Contains('.'))
|
||||
{
|
||||
// This is a navigation property currently not supported
|
||||
prop = TypeDescriptor.GetProperties(typeof(AlbumRequest)).Find("RequestedDate", true);
|
||||
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
|
||||
//var propType = firstProp.PropertyType;
|
||||
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
|
||||
}
|
||||
|
||||
// TODO fix this so we execute this on the server
|
||||
var requests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
|
||||
? allRequests.ToList().OrderBy(x => x.RequestedDate).ToList()
|
||||
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x)).ToList();
|
||||
var total = requests.Count();
|
||||
requests = requests.Skip(position).Take(count).ToList();
|
||||
|
||||
await CheckForSubscription(shouldHide, requests);
|
||||
return new RequestsViewModel<AlbumRequest>
|
||||
{
|
||||
Collection = requests,
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, string sortProperty, string sortOrder)
|
||||
{
|
||||
var shouldHide = await HideFromOtherUsers();
|
||||
IQueryable<AlbumRequest> allRequests;
|
||||
if (shouldHide.Hide)
|
||||
{
|
||||
allRequests =
|
||||
MusicRepository.GetWithUser(shouldHide
|
||||
.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
allRequests =
|
||||
MusicRepository
|
||||
.GetWithUser();
|
||||
}
|
||||
|
||||
var prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(sortProperty, true);
|
||||
|
||||
if (sortProperty.Contains('.'))
|
||||
{
|
||||
// This is a navigation property currently not supported
|
||||
prop = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find("RequestedDate", true);
|
||||
//var properties = sortProperty.Split(new []{'.'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
//var firstProp = TypeDescriptor.GetProperties(typeof(MovieRequests)).Find(properties[0], true);
|
||||
//var propType = firstProp.PropertyType;
|
||||
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
|
||||
}
|
||||
|
||||
// TODO fix this so we execute this on the server
|
||||
var requests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
|
||||
? allRequests.ToList().OrderBy(x => x.RequestedDate).ToList()
|
||||
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x)).ToList();
|
||||
var total = requests.Count();
|
||||
requests = requests.Skip(position).Take(count).ToList();
|
||||
|
||||
await CheckForSubscription(shouldHide, requests);
|
||||
return new RequestsViewModel<AlbumRequest>
|
||||
{
|
||||
Collection = requests,
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,5 +17,34 @@ namespace Ombi.Store.Entities.Requests
|
|||
public bool Subscribed { get; set; }
|
||||
[NotMapped]
|
||||
public bool ShowSubscribe { get; set; }
|
||||
|
||||
|
||||
[NotMapped]
|
||||
public string RequestStatus {
|
||||
get
|
||||
{
|
||||
if (Available)
|
||||
{
|
||||
return "Common.Available";
|
||||
}
|
||||
|
||||
if (Denied ?? false)
|
||||
{
|
||||
return "Common.Denied";
|
||||
}
|
||||
|
||||
if (Approved & !Available)
|
||||
{
|
||||
return "Common.ProcessingRequest";
|
||||
}
|
||||
|
||||
if (!Approved && !Available)
|
||||
{
|
||||
return "Common.PendingApproval";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
<div *ngIf="movie && radarrEnabled" class="text-center">
|
||||
<button mat-raised-button color="warn" class="text-center" (click)="openAdvancedOptions();">Advanced Options</button>
|
||||
<button mat-raised-button color="warn" class="text-center" (click)="openAdvancedOptions();">{{'MediaDetails.AdvancedOptions' | translate }}</button>
|
||||
</div>
|
|
@ -3,7 +3,7 @@
|
|||
Advanced Options</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<mat-label>Radarr Quality Profile</mat-label>
|
||||
<mat-label>{{'MediaDetails.RadarrProfile' | translate }}</mat-label>
|
||||
<mat-select [(value)]="data.profileId">
|
||||
<mat-option *ngFor="let profile of data.profiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
||||
</mat-select>
|
||||
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<mat-label>Radarr Root Folders</mat-label>
|
||||
<mat-label>{{'MediaDetails.RadarrFolder' | translate }}</mat-label>
|
||||
<mat-select [(value)]="data.rootFolderId">
|
||||
<mat-option *ngFor="let profile of data.rootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
||||
</mat-select>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<div *ngIf="movie">
|
||||
<div>
|
||||
<strong>Status:</strong>
|
||||
<strong>{{'MediaDetails.Status' | translate }}:</strong>
|
||||
<div>{{movie.status}}</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Availability</strong>
|
||||
<strong>{{'MediaDetails.Availability' | translate }}</strong>
|
||||
<div *ngIf="movie.available">{{'Common.Available' | translate}}</div>
|
||||
<div *ngIf="!movie.available">{{'Common.NotAvailable' | translate}}</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Request Status</strong>
|
||||
<strong>{{'MediaDetails.RequestStatus' | translate }}</strong>
|
||||
<div *ngIf="movie.approved && !movie.available">{{'Common.ProcessingRequest' | translate}}</div>
|
||||
<div *ngIf="movie.requested && !movie.approved && !movie.available">{{'Common.PendingApproval' | translate}}
|
||||
</div>
|
||||
|
@ -19,21 +19,21 @@
|
|||
</div>
|
||||
</div>
|
||||
<div *ngIf="movie.quality">
|
||||
<strong>Quality:</strong>
|
||||
<strong>{{'MediaDetails.Quality' | translate }}:</strong>
|
||||
<div>{{movie.quality | quality}}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="advancedOptions">
|
||||
<strong>Root Folder Override</strong>
|
||||
<strong>{{'MediaDetails.RootFolderOverride' | translate }}</strong>
|
||||
<div>{{advancedOptions.rootFolder.path}}</div>
|
||||
</div>
|
||||
<div *ngIf="advancedOptions">
|
||||
<strong>Quality Override</strong>
|
||||
<strong>{{'MediaDetails.QualityOverride' | translate }}</strong>
|
||||
<div>{{advancedOptions.profile.name}}</div>
|
||||
</div>
|
||||
<br />
|
||||
<div *ngIf="movie.genres">
|
||||
<strong>Genres:</strong>
|
||||
<strong>{{'MediaDetails.Genres' | translate }}:</strong>
|
||||
<div>
|
||||
<mat-chip-list>
|
||||
<mat-chip color="accent" selected *ngFor="let genre of movie.genres">
|
||||
|
@ -44,45 +44,45 @@
|
|||
</div>
|
||||
<br />
|
||||
|
||||
<strong>Theatrical Release:</strong>
|
||||
<strong>{{'MediaDetails.TheatricalRelease' | translate }}:</strong>
|
||||
<div>
|
||||
|
||||
{{movie.releaseDate | date: 'mediumDate'}}
|
||||
<div *ngIf="movie.digitalReleaseDate">
|
||||
<strong>Digital Release:</strong>
|
||||
<strong>{{'MediaDetails.DigitalRelease' | translate }}:</strong>
|
||||
<div>
|
||||
{{movie.digitalReleaseDate | date: 'mediumDate'}}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="movie.voteAverage">
|
||||
<strong>User Score:</strong>
|
||||
<strong>{{'MediaDetails.UserScore' | translate }}:</strong>
|
||||
<div>
|
||||
{{movie.voteAverage | number:'1.0-1'}} / 10
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="movie.voteCount">
|
||||
<strong>Votes:</strong>
|
||||
<strong>{{'MediaDetails.Votes' | translate }}:</strong>
|
||||
<div>
|
||||
{{movie.voteCount | thousandShort: 1}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Runtime:</strong>
|
||||
<div>{{movie.runtime}} Minutes</div>
|
||||
<strong>{{'MediaDetails.Runtime' | translate }}:</strong>
|
||||
<div>{{'MediaDetails.Minutes' | translate:{runtime: movie.runtime} }}</div>
|
||||
</div>
|
||||
<div *ngIf="movie.revenue">
|
||||
<strong>Revenue:</strong>
|
||||
<strong>{{'MediaDetails.Revenue' | translate }}:</strong>
|
||||
<div> {{movie.revenue | currency: 'USD'}}</div>
|
||||
</div>
|
||||
<div *ngIf="movie.budget">
|
||||
<strong>Budget:</strong>
|
||||
<strong>{{'MediaDetails.Budget' | translate }}:</strong>
|
||||
<div> {{movie.budget | currency: 'USD'}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br />
|
||||
<div>
|
||||
<strong>Keywords/Tags:</strong>
|
||||
<strong>{{'MediaDetails.Keywords' | translate }}:</strong>
|
||||
<mat-chip-list>
|
||||
<mat-chip color="accent" selected *ngFor="let keyword of movie.keywords.keywordsValue">
|
||||
{{keyword.name}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<div *ngIf="tv.status">
|
||||
<strong>Status:</strong>
|
||||
<strong>{{'MediaDetails.Status' | translate }}:</strong>
|
||||
<div>
|
||||
{{tv.status}}
|
||||
</div>
|
||||
|
@ -13,15 +13,9 @@
|
|||
|
||||
|
||||
<div>
|
||||
<strong>Status:</strong>
|
||||
<strong>{{'MediaDetails.Runtime' | translate }}:</strong>
|
||||
<div>
|
||||
{{tv.status}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Runtime:</strong>
|
||||
<div>
|
||||
{{tv.runtime}} Minutes
|
||||
{{'MediaDetails.Minutes' | translate:{ runtime: tv.runtime} }}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="tv.rating">
|
||||
|
@ -38,7 +32,7 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="tv.genre">
|
||||
<strong>Genres:</strong>
|
||||
<strong>{{'MediaDetails.Genres' | translate }}:</strong>
|
||||
<div>
|
||||
<span *ngFor="let genre of tv.genre">
|
||||
{{genre}} |
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<div class="mat-elevation-z8">
|
||||
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
|
||||
|
||||
|
||||
<!-- <div class="row"> -->
|
||||
<div class="row justify-content-md-center top-spacing">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.AllRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.PendingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.ProcessingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.AvailableRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.DeniedRequests' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 offset-md-10">
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||
<mat-option value="10">10</mat-option>
|
||||
<mat-option value="15">15</mat-option>
|
||||
<mat-option value="30">30</mat-option>
|
||||
<mat-option value="100">100</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder">
|
||||
|
||||
|
||||
|
||||
<ng-container matColumnDef="artistName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.ArtistName' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.artistName}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.AlbumName' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: 'YYYY'}}) </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedUser.requestedBy">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestDate' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestedDate | amLocal | amDateFormat: 'LL'}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestStatus">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestStatus' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-raised-button color="accent" [routerLink]="'/details/artist/' + element.foreignArtistId">{{ 'Requests.Details' | translate}}</button>
|
||||
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin"> {{ 'Requests.Options' | translate}}</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
|
||||
</div>
|
|
@ -0,0 +1,49 @@
|
|||
@import "~styles/variables.scss";
|
||||
|
||||
.dark .mat-header-cell {
|
||||
background: $accent-dark !important;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
color: #303030;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
float:right;
|
||||
margin-right:20px;
|
||||
}
|
||||
|
||||
/*::ng-deep .dark .mat-form-field-label{
|
||||
font-size: 1.2em;
|
||||
}*/
|
||||
|
||||
::ng-deep .mat-form-field-infix {
|
||||
width: 8em;
|
||||
margin-top:1em;
|
||||
}
|
||||
|
||||
::ng-deep .dark .mat-tab-label-active{
|
||||
background: $accent-dark !important;
|
||||
color: #303030 !important;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
::ng-deep .mat-tab-label{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
::ng-deep .row {
|
||||
margin-right:0;
|
||||
margin-left:0;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.justify-content-md-center {
|
||||
justify-content: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1170px){
|
||||
.justify-content-md-center {
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
import { Component, AfterViewInit, ViewChild, EventEmitter, Output, ChangeDetectorRef, OnInit } from "@angular/core";
|
||||
import { IRequestsViewModel, IAlbumRequest } from "../../../interfaces";
|
||||
import { MatPaginator } from "@angular/material/paginator";
|
||||
import { MatSort } from "@angular/material/sort";
|
||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
import { RequestFilterType } from "../../models/RequestFilterType";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./albums-grid.component.html",
|
||||
selector: "albums-grid",
|
||||
styleUrls: ["./albums-grid.component.scss"]
|
||||
})
|
||||
export class AlbumsGridComponent implements OnInit, AfterViewInit {
|
||||
public dataSource: IAlbumRequest[] = [];
|
||||
public resultsLength: number;
|
||||
public isLoadingResults = true;
|
||||
public displayedColumns: string[] = ['artistName', 'title', 'requestedUser.requestedBy', 'requestStatus','requestedDate', 'actions'];
|
||||
public gridCount: string = "15";
|
||||
public isAdmin: boolean;
|
||||
public defaultSort: string = "requestedDate";
|
||||
public defaultOrder: string = "desc";
|
||||
public currentFilter: RequestFilterType = RequestFilterType.All;
|
||||
|
||||
public RequestFilter = RequestFilterType;
|
||||
|
||||
|
||||
private storageKey = "Albums_DefaultRequestListSort";
|
||||
private storageKeyOrder = "Albums_DefaultRequestListSortOrder";
|
||||
private storageKeyGridCount = "Albums_DefaultGridCount";
|
||||
private storageKeyCurrentFilter = "Albums_DefaultFilter";
|
||||
|
||||
@Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>();
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private requestService: RequestServiceV2, private ref: ChangeDetectorRef,
|
||||
private auth: AuthService, private storageService: StorageService) {
|
||||
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
|
||||
const defaultCount = this.storageService.get(this.storageKeyGridCount);
|
||||
const defaultSort = this.storageService.get(this.storageKey);
|
||||
const defaultOrder = this.storageService.get(this.storageKeyOrder);
|
||||
const defaultFilter = +this.storageService.get(this.storageKeyCurrentFilter);
|
||||
if (defaultSort) {
|
||||
this.defaultSort = defaultSort;
|
||||
}
|
||||
if (defaultOrder) {
|
||||
this.defaultOrder = defaultOrder;
|
||||
}
|
||||
if (defaultCount) {
|
||||
this.gridCount = defaultCount;
|
||||
}
|
||||
if (defaultFilter) {
|
||||
this.currentFilter = defaultFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public async ngAfterViewInit() {
|
||||
|
||||
this.storageService.save(this.storageKeyGridCount, this.gridCount);
|
||||
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
this.paginator.showFirstLastButtons = true;
|
||||
|
||||
merge(this.sort.sortChange, this.paginator.page, this.currentFilter)
|
||||
.pipe(
|
||||
startWith({}),
|
||||
switchMap((value: any) => {
|
||||
this.isLoadingResults = true;
|
||||
if (value.active || value.direction) {
|
||||
this.storageService.save(this.storageKey, value.active);
|
||||
this.storageService.save(this.storageKeyOrder, value.direction);
|
||||
}
|
||||
return this.loadData();
|
||||
}),
|
||||
map((data: IRequestsViewModel<IAlbumRequest>) => {
|
||||
// Flip flag to show that loading has finished.
|
||||
this.isLoadingResults = false;
|
||||
this.resultsLength = data.total;
|
||||
|
||||
return data.collection;
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.isLoadingResults = false;
|
||||
return observableOf([]);
|
||||
})
|
||||
).subscribe(data => this.dataSource = data);
|
||||
}
|
||||
|
||||
public loadData(): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
switch(RequestFilterType[RequestFilterType[this.currentFilter]]) {
|
||||
case RequestFilterType.All:
|
||||
return this.requestService.getAlbumRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Pending:
|
||||
return this.requestService.getAlbumPendingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Available:
|
||||
return this.requestService.getAlbumAvailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Processing:
|
||||
return this.requestService.getAlbumProcessingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Denied:
|
||||
return this.requestService.getAlbumDeniedRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public openOptions(request: IAlbumRequest) {
|
||||
const filter = () => {
|
||||
this.dataSource = this.dataSource.filter((req) => {
|
||||
return req.id !== request.id;
|
||||
})
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
this.ref.detectChanges();
|
||||
};
|
||||
|
||||
this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange });
|
||||
}
|
||||
|
||||
public switchFilter(type: RequestFilterType) {
|
||||
this.currentFilter = type;
|
||||
this.ngAfterViewInit();
|
||||
}
|
||||
}
|
|
@ -6,13 +6,15 @@ import { RequestService } from "../../services";
|
|||
import { TvGridComponent } from "./tv-grid/tv-grid.component";
|
||||
import { GridSpinnerComponent } from "./grid-spinner/grid-spinner.component";
|
||||
import { RequestOptionsComponent } from "./options/request-options.component";
|
||||
import { AlbumsGridComponent } from "./albums-grid/albums-grid.component";
|
||||
|
||||
export const components: any[] = [
|
||||
RequestsListComponent,
|
||||
MoviesGridComponent,
|
||||
TvGridComponent,
|
||||
GridSpinnerComponent,
|
||||
RequestOptionsComponent
|
||||
RequestOptionsComponent,
|
||||
AlbumsGridComponent
|
||||
];
|
||||
|
||||
export const entryComponents: any[] = [
|
||||
|
|
|
@ -72,6 +72,7 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
this.paginator.showFirstLastButtons = true;
|
||||
|
||||
merge(this.sort.sortChange, this.paginator.page, this.currentFilter)
|
||||
.pipe(
|
||||
|
|
|
@ -19,6 +19,9 @@ export class RequestOptionsComponent {
|
|||
if (this.data.type === RequestType.tvShow) {
|
||||
await this.requestService.deleteChild(this.data.id).toPromise();
|
||||
}
|
||||
if (this.data.type === RequestType.album) {
|
||||
await this.requestService.removeAlbumRequest(this.data.id).toPromise();
|
||||
}
|
||||
|
||||
this.bottomSheetRef.dismiss({type: UpdateType.Delete});
|
||||
return;
|
||||
|
@ -31,6 +34,9 @@ export class RequestOptionsComponent {
|
|||
if (this.data.type === RequestType.tvShow) {
|
||||
await this.requestService.approveChild({id: this.data.id}).toPromise();
|
||||
}
|
||||
if (this.data.type === RequestType.album) {
|
||||
await this.requestService.approveAlbum({id: this.data.id}).toPromise();
|
||||
}
|
||||
|
||||
this.bottomSheetRef.dismiss({type: UpdateType.Approve});
|
||||
return;
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab label="Albums">
|
||||
<h1>Coming soon</h1>
|
||||
<p>...</p>
|
||||
<ng-template matTabContent>
|
||||
<albums-grid (onOpenOptions)="onOpenOptions($event)"></albums-grid>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
</div>
|
||||
</mat-tab-group>
|
||||
|
|
|
@ -67,6 +67,7 @@ export class TvGridComponent implements OnInit, AfterViewInit {
|
|||
|
||||
this.storageService.save(this.storageKeyGridCount, this.gridCount);
|
||||
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
|
||||
this.paginator.showFirstLastButtons = true;
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
|
|
|
@ -7,7 +7,7 @@ export class NotificationService {
|
|||
|
||||
private config: MatSnackBarConfig<any> = {
|
||||
duration:3000,
|
||||
|
||||
|
||||
}
|
||||
|
||||
public success(body: string) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Injectable, Inject } from "@angular/core";
|
|||
import { HttpClient } from "@angular/common/http";
|
||||
import { Observable } from "rxjs";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions, IRequestEngineResult } from "../interfaces";
|
||||
import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions, IRequestEngineResult, IAlbumRequest } from "../interfaces";
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
@ -24,7 +24,7 @@ export class RequestServiceV2 extends ServiceHelpers {
|
|||
public getMovieProcessingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
|
||||
public getMoviePendingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
@ -35,34 +35,53 @@ export class RequestServiceV2 extends ServiceHelpers {
|
|||
|
||||
public getTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
||||
public getPendingTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
||||
public getProcessingTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
||||
public getAvailableTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/available/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
||||
public getDeniedTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public updateMovieAdvancedOptions(options: IMovieAdvancedOptions): Observable<IRequestEngineResult> {
|
||||
return this.http.post<IRequestEngineResult>(`${this.url}movie/advancedoptions`, options, {headers: this.headers});
|
||||
}
|
||||
|
||||
|
||||
public getMovieUnavailableRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/unavailable/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getTvUnavailableRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/unavailable/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public getAlbumRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAlbumAvailableRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/available/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAlbumProcessingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAlbumPendingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAlbumDeniedRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}Album/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<settings-menu></settings-menu>
|
||||
<div class="small-middle-container">
|
||||
<div class="small-middle-container" *ngIf="settings">
|
||||
<fieldset style="width:100%;">
|
||||
<legend>Plex Configuration</legend>
|
||||
<div class="row">
|
||||
|
|
|
@ -14,14 +14,17 @@ namespace Ombi.Controllers.V2
|
|||
{
|
||||
public class RequestsController : V2Controller
|
||||
{
|
||||
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine)
|
||||
{
|
||||
_movieRequestEngine = movieRequestEngine;
|
||||
_tvRequestEngine = tvRequestEngine;
|
||||
}
|
||||
|
||||
private readonly IMovieRequestEngine _movieRequestEngine;
|
||||
private readonly ITvRequestEngine _tvRequestEngine;
|
||||
private readonly IMusicRequestEngine _musicRequestEngine;
|
||||
|
||||
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicRequestEngine)
|
||||
{
|
||||
_movieRequestEngine = movieRequestEngine;
|
||||
_tvRequestEngine = tvRequestEngine;
|
||||
_musicRequestEngine = musicRequestEngine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets movie requests.
|
||||
|
@ -129,5 +132,35 @@ namespace Ombi.Controllers.V2
|
|||
{
|
||||
return await _movieRequestEngine.UpdateAdvancedOptions(options);
|
||||
}
|
||||
|
||||
[HttpGet("albums/available/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetAvailableAlbumRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _musicRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.Available);
|
||||
}
|
||||
|
||||
[HttpGet("album/processing/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetProcessingAlbumRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _musicRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.ProcessingRequest);
|
||||
}
|
||||
|
||||
[HttpGet("album/pending/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetPendingAlbumRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _musicRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.PendingApproval);
|
||||
}
|
||||
|
||||
[HttpGet("album/denied/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetDeniedAlbumRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _musicRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.Denied);
|
||||
}
|
||||
|
||||
[HttpGet("album/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<AlbumRequest>> GetAlbumRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _musicRequestEngine.GetRequests(count, position, sort, sortOrder);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -113,6 +113,8 @@
|
|||
"Title": "Requests",
|
||||
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
|
||||
"MoviesTab": "Movies",
|
||||
"ArtistName": "Artist",
|
||||
"AlbumName": "Album Name",
|
||||
"TvTab": "TV Shows",
|
||||
"MusicTab": "Music",
|
||||
"RequestedBy": "Requested By",
|
||||
|
@ -227,6 +229,25 @@
|
|||
"RequestSelectedAlbums": "Request Selected Albums",
|
||||
"ViewCollection":"View Collection",
|
||||
"NotEnoughInfo": "Unfortunately there is not enough information about this show yet!",
|
||||
"AdvancedOptions":"Advanced Options",
|
||||
"RadarrProfile":"Radarr Quality Profile",
|
||||
"RadarrFolder":"Radarr Root Folder",
|
||||
"Status":"Status",
|
||||
"Availability":"Availability",
|
||||
"RequestStatus":"Request Status",
|
||||
"Quality":"Quality",
|
||||
"RootFolderOverride":"Root Folder Override",
|
||||
"QualityOverride":"Quality Override",
|
||||
"Genres":"Genres",
|
||||
"TheatricalRelease":"Theatrical Release",
|
||||
"DigitalRelease":"Digital Release",
|
||||
"UserScore":"User Score",
|
||||
"Votes":"Votes",
|
||||
"Runtime":"Runtime",
|
||||
"Minutes": "{{runtime}} Minutes",
|
||||
"Revenue":"Revenue",
|
||||
"Budget":"Budget",
|
||||
"Keywords":"Keywords/Tags",
|
||||
"Casts": {
|
||||
"CastTitle": "Cast",
|
||||
"Character": "Character",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue