Got the album reques list in place

pull/3632/head
Jamie Rees 5 years ago
parent 73353a6aa5
commit 6d339e7a1c

@ -23,5 +23,7 @@ namespace Ombi.Core.Engine
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search); Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
Task<bool> UserHasRequest(string userId); Task<bool> UserHasRequest(string userId);
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null); 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.Settings.Settings.Models.External;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using System.ComponentModel;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
{ {
@ -253,6 +254,28 @@ namespace Ombi.Core.Engine
return allRequests; 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) private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x)
{ {
if (shouldHide.UserId == x.RequestedUserId) 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 }; 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; } public bool Subscribed { get; set; }
[NotMapped] [NotMapped]
public bool ShowSubscribe { get; set; } 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"> <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> </div>

@ -3,7 +3,7 @@
Advanced Options</h1> Advanced Options</h1>
<div mat-dialog-content> <div mat-dialog-content>
<mat-form-field> <mat-form-field>
<mat-label>Radarr Quality Profile</mat-label> <mat-label>{{'MediaDetails.RadarrProfile' | translate }}</mat-label>
<mat-select [(value)]="data.profileId"> <mat-select [(value)]="data.profileId">
<mat-option *ngFor="let profile of data.profiles" value="{{profile.id}}">{{profile.name}}</mat-option> <mat-option *ngFor="let profile of data.profiles" value="{{profile.id}}">{{profile.name}}</mat-option>
</mat-select> </mat-select>
@ -11,7 +11,7 @@
</div> </div>
<div mat-dialog-content> <div mat-dialog-content>
<mat-form-field> <mat-form-field>
<mat-label>Radarr Root Folders</mat-label> <mat-label>{{'MediaDetails.RadarrFolder' | translate }}</mat-label>
<mat-select [(value)]="data.rootFolderId"> <mat-select [(value)]="data.rootFolderId">
<mat-option *ngFor="let profile of data.rootFolders" value="{{profile.id}}">{{profile.path}}</mat-option> <mat-option *ngFor="let profile of data.rootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
</mat-select> </mat-select>

@ -1,17 +1,17 @@
<div *ngIf="movie"> <div *ngIf="movie">
<div> <div>
<strong>Status:</strong> <strong>{{'MediaDetails.Status' | translate }}:</strong>
<div>{{movie.status}}</div> <div>{{movie.status}}</div>
</div> </div>
<div> <div>
<strong>Availability</strong> <strong>{{'MediaDetails.Availability' | translate }}</strong>
<div *ngIf="movie.available">{{'Common.Available' | translate}}</div> <div *ngIf="movie.available">{{'Common.Available' | translate}}</div>
<div *ngIf="!movie.available">{{'Common.NotAvailable' | translate}}</div> <div *ngIf="!movie.available">{{'Common.NotAvailable' | translate}}</div>
</div> </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.approved && !movie.available">{{'Common.ProcessingRequest' | translate}}</div>
<div *ngIf="movie.requested && !movie.approved && !movie.available">{{'Common.PendingApproval' | translate}} <div *ngIf="movie.requested && !movie.approved && !movie.available">{{'Common.PendingApproval' | translate}}
</div> </div>
@ -19,21 +19,21 @@
</div> </div>
</div> </div>
<div *ngIf="movie.quality"> <div *ngIf="movie.quality">
<strong>Quality:</strong> <strong>{{'MediaDetails.Quality' | translate }}:</strong>
<div>{{movie.quality | quality}}</div> <div>{{movie.quality | quality}}</div>
</div> </div>
<div *ngIf="advancedOptions"> <div *ngIf="advancedOptions">
<strong>Root Folder Override</strong> <strong>{{'MediaDetails.RootFolderOverride' | translate }}</strong>
<div>{{advancedOptions.rootFolder.path}}</div> <div>{{advancedOptions.rootFolder.path}}</div>
</div> </div>
<div *ngIf="advancedOptions"> <div *ngIf="advancedOptions">
<strong>Quality Override</strong> <strong>{{'MediaDetails.QualityOverride' | translate }}</strong>
<div>{{advancedOptions.profile.name}}</div> <div>{{advancedOptions.profile.name}}</div>
</div> </div>
<br /> <br />
<div *ngIf="movie.genres"> <div *ngIf="movie.genres">
<strong>Genres:</strong> <strong>{{'MediaDetails.Genres' | translate }}:</strong>
<div> <div>
<mat-chip-list> <mat-chip-list>
<mat-chip color="accent" selected *ngFor="let genre of movie.genres"> <mat-chip color="accent" selected *ngFor="let genre of movie.genres">
@ -44,45 +44,45 @@
</div> </div>
<br /> <br />
<strong>Theatrical Release:</strong> <strong>{{'MediaDetails.TheatricalRelease' | translate }}:</strong>
<div> <div>
{{movie.releaseDate | date: 'mediumDate'}} {{movie.releaseDate | date: 'mediumDate'}}
<div *ngIf="movie.digitalReleaseDate"> <div *ngIf="movie.digitalReleaseDate">
<strong>Digital Release:</strong> <strong>{{'MediaDetails.DigitalRelease' | translate }}:</strong>
<div> <div>
{{movie.digitalReleaseDate | date: 'mediumDate'}} {{movie.digitalReleaseDate | date: 'mediumDate'}}
</div> </div>
</div> </div>
<div *ngIf="movie.voteAverage"> <div *ngIf="movie.voteAverage">
<strong>User Score:</strong> <strong>{{'MediaDetails.UserScore' | translate }}:</strong>
<div> <div>
{{movie.voteAverage | number:'1.0-1'}} / 10 {{movie.voteAverage | number:'1.0-1'}} / 10
</div> </div>
</div> </div>
<div *ngIf="movie.voteCount"> <div *ngIf="movie.voteCount">
<strong>Votes:</strong> <strong>{{'MediaDetails.Votes' | translate }}:</strong>
<div> <div>
{{movie.voteCount | thousandShort: 1}} {{movie.voteCount | thousandShort: 1}}
</div> </div>
</div> </div>
<div> <div>
<strong>Runtime:</strong> <strong>{{'MediaDetails.Runtime' | translate }}:</strong>
<div>{{movie.runtime}} Minutes</div> <div>{{'MediaDetails.Minutes' | translate:{runtime: movie.runtime} }}</div>
</div> </div>
<div *ngIf="movie.revenue"> <div *ngIf="movie.revenue">
<strong>Revenue:</strong> <strong>{{'MediaDetails.Revenue' | translate }}:</strong>
<div> {{movie.revenue | currency: 'USD'}}</div> <div> {{movie.revenue | currency: 'USD'}}</div>
</div> </div>
<div *ngIf="movie.budget"> <div *ngIf="movie.budget">
<strong>Budget:</strong> <strong>{{'MediaDetails.Budget' | translate }}:</strong>
<div> {{movie.budget | currency: 'USD'}}</div> <div> {{movie.budget | currency: 'USD'}}</div>
</div> </div>
<br /> <br />
<div> <div>
<strong>Keywords/Tags:</strong> <strong>{{'MediaDetails.Keywords' | translate }}:</strong>
<mat-chip-list> <mat-chip-list>
<mat-chip color="accent" selected *ngFor="let keyword of movie.keywords.keywordsValue"> <mat-chip color="accent" selected *ngFor="let keyword of movie.keywords.keywordsValue">
{{keyword.name}} {{keyword.name}}

@ -1,6 +1,6 @@
<div> <div>
<div *ngIf="tv.status"> <div *ngIf="tv.status">
<strong>Status:</strong> <strong>{{'MediaDetails.Status' | translate }}:</strong>
<div> <div>
{{tv.status}} {{tv.status}}
</div> </div>
@ -13,15 +13,9 @@
<div> <div>
<strong>Status:</strong> <strong>{{'MediaDetails.Runtime' | translate }}:</strong>
<div> <div>
{{tv.status}} {{'MediaDetails.Minutes' | translate:{ runtime: tv.runtime} }}
</div>
</div>
<div>
<strong>Runtime:</strong>
<div>
{{tv.runtime}} Minutes
</div> </div>
</div> </div>
<div *ngIf="tv.rating"> <div *ngIf="tv.rating">
@ -38,7 +32,7 @@
</div> </div>
<div *ngIf="tv.genre"> <div *ngIf="tv.genre">
<strong>Genres:</strong> <strong>{{'MediaDetails.Genres' | translate }}:</strong>
<div> <div>
<span *ngFor="let genre of tv.genre"> <span *ngFor="let genre of tv.genre">
{{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 { TvGridComponent } from "./tv-grid/tv-grid.component";
import { GridSpinnerComponent } from "./grid-spinner/grid-spinner.component"; import { GridSpinnerComponent } from "./grid-spinner/grid-spinner.component";
import { RequestOptionsComponent } from "./options/request-options.component"; import { RequestOptionsComponent } from "./options/request-options.component";
import { AlbumsGridComponent } from "./albums-grid/albums-grid.component";
export const components: any[] = [ export const components: any[] = [
RequestsListComponent, RequestsListComponent,
MoviesGridComponent, MoviesGridComponent,
TvGridComponent, TvGridComponent,
GridSpinnerComponent, GridSpinnerComponent,
RequestOptionsComponent RequestOptionsComponent,
AlbumsGridComponent
]; ];
export const entryComponents: any[] = [ 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. // If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
this.paginator.showFirstLastButtons = true;
merge(this.sort.sortChange, this.paginator.page, this.currentFilter) merge(this.sort.sortChange, this.paginator.page, this.currentFilter)
.pipe( .pipe(

@ -19,6 +19,9 @@ export class RequestOptionsComponent {
if (this.data.type === RequestType.tvShow) { if (this.data.type === RequestType.tvShow) {
await this.requestService.deleteChild(this.data.id).toPromise(); 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}); this.bottomSheetRef.dismiss({type: UpdateType.Delete});
return; return;
@ -31,6 +34,9 @@ export class RequestOptionsComponent {
if (this.data.type === RequestType.tvShow) { if (this.data.type === RequestType.tvShow) {
await this.requestService.approveChild({id: this.data.id}).toPromise(); 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}); this.bottomSheetRef.dismiss({type: UpdateType.Approve});
return; return;

@ -12,8 +12,9 @@
</ng-template> </ng-template>
</mat-tab> </mat-tab>
<mat-tab label="Albums"> <mat-tab label="Albums">
<h1>Coming soon</h1> <ng-template matTabContent>
<p>...</p> <albums-grid (onOpenOptions)="onOpenOptions($event)"></albums-grid>
</ng-template>
</mat-tab> </mat-tab>
</div> </div>
</mat-tab-group> </mat-tab-group>

@ -67,6 +67,7 @@ export class TvGridComponent implements OnInit, AfterViewInit {
this.storageService.save(this.storageKeyGridCount, this.gridCount); this.storageService.save(this.storageKeyGridCount, this.gridCount);
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString()); 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. // If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);

@ -4,7 +4,7 @@ import { Injectable, Inject } from "@angular/core";
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { ServiceHelpers } from "./service.helpers"; import { ServiceHelpers } from "./service.helpers";
import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions, IRequestEngineResult } from "../interfaces"; import { IRequestsViewModel, IMovieRequests, IChildRequests, IMovieAdvancedOptions, IRequestEngineResult, IAlbumRequest } from "../interfaces";
@Injectable() @Injectable()
@ -65,4 +65,23 @@ export class RequestServiceV2 extends ServiceHelpers {
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/unavailable/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers}); 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> <settings-menu></settings-menu>
<div class="small-middle-container"> <div class="small-middle-container" *ngIf="settings">
<fieldset style="width:100%;"> <fieldset style="width:100%;">
<legend>Plex Configuration</legend> <legend>Plex Configuration</legend>
<div class="row"> <div class="row">

@ -14,15 +14,18 @@ namespace Ombi.Controllers.V2
{ {
public class RequestsController : V2Controller public class RequestsController : V2Controller
{ {
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine)
private readonly IMovieRequestEngine _movieRequestEngine;
private readonly ITvRequestEngine _tvRequestEngine;
private readonly IMusicRequestEngine _musicRequestEngine;
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicRequestEngine)
{ {
_movieRequestEngine = movieRequestEngine; _movieRequestEngine = movieRequestEngine;
_tvRequestEngine = tvRequestEngine; _tvRequestEngine = tvRequestEngine;
_musicRequestEngine = musicRequestEngine;
} }
private readonly IMovieRequestEngine _movieRequestEngine;
private readonly ITvRequestEngine _tvRequestEngine;
/// <summary> /// <summary>
/// Gets movie requests. /// Gets movie requests.
/// </summary> /// </summary>
@ -129,5 +132,35 @@ namespace Ombi.Controllers.V2
{ {
return await _movieRequestEngine.UpdateAdvancedOptions(options); 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", "Title": "Requests",
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.", "Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
"MoviesTab": "Movies", "MoviesTab": "Movies",
"ArtistName": "Artist",
"AlbumName": "Album Name",
"TvTab": "TV Shows", "TvTab": "TV Shows",
"MusicTab": "Music", "MusicTab": "Music",
"RequestedBy": "Requested By", "RequestedBy": "Requested By",
@ -227,6 +229,25 @@
"RequestSelectedAlbums": "Request Selected Albums", "RequestSelectedAlbums": "Request Selected Albums",
"ViewCollection":"View Collection", "ViewCollection":"View Collection",
"NotEnoughInfo": "Unfortunately there is not enough information about this show yet!", "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": { "Casts": {
"CastTitle": "Cast", "CastTitle": "Cast",
"Character": "Character", "Character": "Character",

Loading…
Cancel
Save