mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Added the TV Requests grid
This commit is contained in:
parent
40c9c82eaf
commit
f80ef6bb24
15 changed files with 251 additions and 32 deletions
|
@ -23,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<IEnumerable<TvRequests>> GetRequestsLite();
|
Task<IEnumerable<TvRequests>> GetRequestsLite();
|
||||||
Task UpdateQualityProfile(int requestId, int profileId);
|
Task UpdateQualityProfile(int requestId, int profileId);
|
||||||
Task UpdateRootPath(int requestId, int rootPath);
|
Task UpdateRootPath(int requestId, int rootPath);
|
||||||
|
Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using Ombi.Core.Models.Search;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
@ -225,6 +226,63 @@ namespace Ombi.Core.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, string sortProperty, string sortOrder)
|
||||||
|
{
|
||||||
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
List<TvRequests> allRequests = null;
|
||||||
|
if (shouldHide.Hide)
|
||||||
|
{
|
||||||
|
var tv = TvRepository.GetLite(shouldHide.UserId);
|
||||||
|
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
|
||||||
|
{
|
||||||
|
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out children
|
||||||
|
FilterChildren(allRequests, shouldHide);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tv = TvRepository.GetLite();
|
||||||
|
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
|
||||||
|
{
|
||||||
|
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allRequests == null)
|
||||||
|
{
|
||||||
|
return new RequestsViewModel<TvRequests>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = allRequests.Count;
|
||||||
|
|
||||||
|
|
||||||
|
var prop = TypeDescriptor.GetProperties(typeof(TvRequests)).Find(sortProperty, true);
|
||||||
|
|
||||||
|
if (sortProperty.Contains('.'))
|
||||||
|
{
|
||||||
|
// This is a navigation property currently not supported
|
||||||
|
prop = TypeDescriptor.GetProperties(typeof(TvRequests)).Find("Title", 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);
|
||||||
|
}
|
||||||
|
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
? allRequests.OrderBy(x => prop.GetValue(x)).ToList()
|
||||||
|
: allRequests.OrderByDescending(x => prop.GetValue(x)).ToList();
|
||||||
|
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
||||||
|
|
||||||
|
|
||||||
|
return new RequestsViewModel<TvRequests>
|
||||||
|
{
|
||||||
|
Collection = allRequests,
|
||||||
|
Total = total,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<IEnumerable<TvRequests>> GetRequestsLite()
|
public async Task<IEnumerable<TvRequests>> GetRequestsLite()
|
||||||
{
|
{
|
||||||
var shouldHide = await HideFromOtherUsers();
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
@ -282,17 +340,22 @@ namespace Ombi.Core.Engine
|
||||||
private static void FilterChildren(TvRequests t, HideResult shouldHide)
|
private static void FilterChildren(TvRequests t, HideResult shouldHide)
|
||||||
{
|
{
|
||||||
// Filter out children
|
// Filter out children
|
||||||
|
FilterChildren(t.ChildRequests, shouldHide);
|
||||||
|
}
|
||||||
|
|
||||||
for (var j = 0; j < t.ChildRequests.Count; j++)
|
private static void FilterChildren(List<ChildRequests> t, HideResult shouldHide)
|
||||||
{
|
{
|
||||||
var child = t.ChildRequests[j];
|
// Filter out children
|
||||||
|
|
||||||
|
for (var j = 0; j < t.Count; j++)
|
||||||
|
{
|
||||||
|
var child = t[j];
|
||||||
if (child.RequestedUserId != shouldHide.UserId)
|
if (child.RequestedUserId != shouldHide.UserId)
|
||||||
{
|
{
|
||||||
t.ChildRequests.RemoveAt(j);
|
t.RemoveAt(j);
|
||||||
j--;
|
j--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
|
public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
|
||||||
|
@ -607,7 +670,7 @@ namespace Ombi.Core.Engine
|
||||||
var result = await TvSender.Send(model);
|
var result = await TvSender.Send(model);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult { Result = true, RequestId = model.Id};
|
return new RequestEngineResult { Result = true, RequestId = model.Id };
|
||||||
}
|
}
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
{
|
{
|
||||||
|
|
|
@ -114,6 +114,8 @@ export interface ITvRequests {
|
||||||
|
|
||||||
export interface IChildRequests extends IBaseRequest {
|
export interface IChildRequests extends IBaseRequest {
|
||||||
seasonRequests: INewSeasonRequests[];
|
seasonRequests: INewSeasonRequests[];
|
||||||
|
parentRequestId: number;
|
||||||
|
parentRequest: ITvRequests;
|
||||||
subscribed: boolean;
|
subscribed: boolean;
|
||||||
showSubscribe: boolean;
|
showSubscribe: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="loading-shade" *ngIf="loading">
|
||||||
|
<div class="row justify-content-md-center top-spacing loading-spinner">
|
||||||
|
<mat-spinner [color]="'accent'"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
.loading-shade {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 56px;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Component, Input } from "@angular/core";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./grid-spinner.component.html",
|
||||||
|
selector: "grid-spinner",
|
||||||
|
styleUrls: ["./grid-spinner.component.scss"]
|
||||||
|
})
|
||||||
|
export class GridSpinnerComponent{
|
||||||
|
@Input() public loading = false;
|
||||||
|
}
|
|
@ -3,10 +3,14 @@ import { MoviesGridComponent } from "./movies-grid/movies-grid.component";
|
||||||
|
|
||||||
import { RequestServiceV2 } from "../../services/requestV2.service";
|
import { RequestServiceV2 } from "../../services/requestV2.service";
|
||||||
import { RequestService } from "../../services";
|
import { RequestService } from "../../services";
|
||||||
|
import { TvGridComponent } from "./tv-grid/tv-grid.component";
|
||||||
|
import { GridSpinnerComponent } from "./grid-spinner/grid-spinner.component";
|
||||||
|
|
||||||
export const components: any[] = [
|
export const components: any[] = [
|
||||||
RequestsListComponent,
|
RequestsListComponent,
|
||||||
MoviesGridComponent,
|
MoviesGridComponent,
|
||||||
|
TvGridComponent,
|
||||||
|
GridSpinnerComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<div class="mat-elevation-z8">
|
<div class="mat-elevation-z8">
|
||||||
<div class="loading-shade" *ngIf="isLoadingResults">
|
<grid-spinner [loading]="isLoading"></grid-spinner>
|
||||||
<div *ngIf="isLoadingResults" class="row justify-content-md-center top-spacing loading-spinner">
|
|
||||||
<mat-spinner [color]="'accent'"></mat-spinner>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, AfterViewInit, ViewChild } from "@angular/core";
|
import { Component, AfterViewInit, ViewChild } from "@angular/core";
|
||||||
import { IMovieRequests, OrderType, FilterType, IRequestsViewModel } from "../../../interfaces";
|
import { IMovieRequests, IRequestsViewModel } from "../../../interfaces";
|
||||||
import { MatPaginator, MatSort } from "@angular/material";
|
import { MatPaginator, MatSort } from "@angular/material";
|
||||||
import { merge, Observable, of as observableOf } from 'rxjs';
|
import { merge, Observable, of as observableOf } from 'rxjs';
|
||||||
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<div class="small-middle-container">
|
<div class="small-middle-container">
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab label="Movies">
|
<mat-tab label="Movies">
|
||||||
|
<ng-template matTabContent>
|
||||||
<movies-grid></movies-grid>
|
<movies-grid></movies-grid>
|
||||||
|
</ng-template>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="TV Shows">
|
<mat-tab label="TV Shows">
|
||||||
<h1>Some more tab content</h1>
|
<ng-template matTabContent>
|
||||||
<p>...</p>
|
<tv-grid></tv-grid>
|
||||||
|
</ng-template>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="Albums">
|
<mat-tab label="Albums">
|
||||||
<h1>Some more tab content</h1>
|
<h1>Some more tab content</h1>
|
||||||
|
|
|
@ -7,15 +7,3 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
}
|
}
|
||||||
.loading-shade {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 56px;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.15);
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
<div class="mat-elevation-z8">
|
||||||
|
|
||||||
|
<grid-spinner [loading]="isLoading"></grid-spinner>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-select placeholder="Requests to Display" [(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>
|
||||||
|
|
||||||
|
<table mat-table [dataSource]="dataSource" class="table" matSort matSortActive="title"
|
||||||
|
matSortDisableClear matSortDirection="desc">
|
||||||
|
|
||||||
|
|
||||||
|
<ng-container matColumnDef="title">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Title </th>
|
||||||
|
<td mat-cell *matCellDef="let element"> {{element.title}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="requestCount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef > Request Count </th>
|
||||||
|
<td mat-cell *matCellDef="let element"> {{element.childRequests.length}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-container matColumnDef="overview">
|
||||||
|
<th mat-header-cell *matHeaderCellDef > Overview </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<small>{{element.overview}}</small>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="status">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Status </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<small>{{element.status}}</small>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="releaseDate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Release Date </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<small>{{element.releaseDate | amLocal | amDateFormat: 'LL'}}</small>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef> </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<button mat-raised-button color="primary" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</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,53 @@
|
||||||
|
import { Component, AfterViewInit, ViewChild } from "@angular/core";
|
||||||
|
import { IRequestsViewModel, ITvRequests } from "../../../interfaces";
|
||||||
|
import { MatPaginator, MatSort } from "@angular/material";
|
||||||
|
import { merge, Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./tv-grid.component.html",
|
||||||
|
selector: "tv-grid",
|
||||||
|
styleUrls: ["../requests-list.component.scss"]
|
||||||
|
})
|
||||||
|
export class TvGridComponent implements AfterViewInit {
|
||||||
|
public dataSource: ITvRequests[] = [];
|
||||||
|
public resultsLength: number;
|
||||||
|
public isLoadingResults = true;
|
||||||
|
public displayedColumns: string[] = ['title', 'overview', 'status', 'requestCount', 'releaseDate','actions'];
|
||||||
|
public gridCount: string = "15";
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
|
constructor(private requestService: RequestServiceV2) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ngAfterViewInit() {
|
||||||
|
|
||||||
|
// If the user changes the sort order, reset back to the first page.
|
||||||
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||||
|
|
||||||
|
merge(this.sort.sortChange, this.paginator.page)
|
||||||
|
.pipe(
|
||||||
|
startWith({}),
|
||||||
|
switchMap(() => {
|
||||||
|
this.isLoadingResults = true;
|
||||||
|
return this.requestService.getTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||||
|
}),
|
||||||
|
map((data: IRequestsViewModel<ITvRequests>) => {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { Injectable } 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 } from "../interfaces";
|
import { IRequestsViewModel, IMovieRequests, ITvRequests } from "../interfaces";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -17,4 +17,8 @@ export class RequestServiceV2 extends ServiceHelpers {
|
||||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<ITvRequests>> {
|
||||||
|
return this.http.get<IRequestsViewModel<ITvRequests>>(`${this.url}tv/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,14 @@ namespace Ombi.Controllers.V2
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class RequestsController : ControllerBase
|
public class RequestsController : ControllerBase
|
||||||
{
|
{
|
||||||
public RequestsController(IMovieRequestEngine movieRequestEngine)
|
public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine)
|
||||||
{
|
{
|
||||||
_movieRequestEngine = movieRequestEngine;
|
_movieRequestEngine = movieRequestEngine;
|
||||||
|
_tvRequestEngine = tvRequestEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IMovieRequestEngine _movieRequestEngine;
|
private readonly IMovieRequestEngine _movieRequestEngine;
|
||||||
|
private readonly ITvRequestEngine _tvRequestEngine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets movie requests.
|
/// Gets movie requests.
|
||||||
|
@ -34,5 +36,18 @@ namespace Ombi.Controllers.V2
|
||||||
{
|
{
|
||||||
return await _movieRequestEngine.GetRequests(count, position, sort, sortOrder);
|
return await _movieRequestEngine.GetRequests(count, position, sort, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets Tv requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The count of items you want to return. e.g. 30</param>
|
||||||
|
/// <param name="position">The position. e.g. position 60 for a 2nd page (since we have already got the first 30 items)</param>
|
||||||
|
/// <param name="sort">The item to sort on e.g. "requestDate"</param>
|
||||||
|
/// <param name="sortOrder">asc or desc</param>
|
||||||
|
[HttpGet("tv/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||||
|
public async Task<RequestsViewModel<TvRequests>> GetTvRequests(int count, int position, string sort, string sortOrder)
|
||||||
|
{
|
||||||
|
return await _tvRequestEngine.GetRequests(count, position, sort, sortOrder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue