feat(discover): Admins can now approve the Recently Requested list

This commit is contained in:
tidusjar 2022-09-14 12:06:28 +01:00
commit 6e7303fb3b
9 changed files with 132 additions and 62 deletions

View file

@ -1,18 +0,0 @@
.detailed-container {
width: 400px;
::ng-deep .poster {
border-radius: 10px;
opacity: 1;
display: block;
width: 100%;
height: auto;
transition: .5s ease;
backface-visibility: hidden;
}
}

View file

@ -17,7 +17,7 @@ import { MatButtonModule } from "@angular/material/button";
@Input() public text: string; @Input() public text: string;
@Input() public id: string; @Input() public id: string;
@Input() public type: string; @Input() public type: string = "primary";
@Input() public class: string; @Input() public class: string;
@Input('data-toggle') public dataToggle: string; @Input('data-toggle') public dataToggle: string;
@Input('data-target') public dataTarget: string; @Input('data-target') public dataTarget: string;

View file

@ -1,7 +1,7 @@
<div id="detailed-{{request.mediaId}}" class="detailed-container" (click)="click()" [style.background-image]="background"> <div id="detailed-{{request.mediaId}}" class="detailed-container" [style.background-image]="background">
<div class="row"> <div class="row">
<div class="col-xl-5 col-lg-5 col-md-5 col-sm-12 posterColumn"> <div class="col-xl-5 col-lg-5 col-md-5 col-sm-12 posterColumn">
<ombi-image [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}"> <ombi-image (click)="click()" [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}">
</ombi-image> </ombi-image>
</div> </div>
<div class="col-xl-7 col-lg-7 col-md-7 col-sm-12"> <div class="col-xl-7 col-lg-7 col-md-7 col-sm-12">
@ -20,6 +20,11 @@
<p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p> <p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p>
</div> </div>
</div> </div>
<div class="row action-items">
<div class="col-12" *ngIf="isAdmin">
<button *ngIf="!request.approved" id="approve-{{request.mediaId}}" color="accent" mat-raised-button (click)="approve()">{{'Common.Approve' | translate}}</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@
.detailed-container { .detailed-container {
width: 400px; width: 400px;
height: auto; height: 250px;
margin: 10px; margin: 10px;
padding: 10px; padding: 10px;
border-radius: 10px; border-radius: 10px;
@ -10,6 +10,7 @@
@media (max-width:768px) { @media (max-width:768px) {
width: 200px; width: 200px;
height: auto;
} }
background-color: $ombi-background-accent; background-color: $ombi-background-accent;
@ -36,12 +37,19 @@
border-radius: 10px; border-radius: 10px;
opacity: 1; opacity: 1;
display: block; display: block;
height: 225px;
width: 100%; width: 100%;
height: 200px;
transition: .5s ease; transition: .5s ease;
backface-visibility: hidden; backface-visibility: hidden;
border: 1px solid #35465c; border: 1px solid #35465c;
} }
.action-items {
@media (min-width:768px) {
bottom: 0;
position: absolute;
z-index: 0i;
}
}
} }

View file

@ -67,6 +67,7 @@ NewMovieRequest.args = {
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.', overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1), releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested, } as IRecentlyRequested,
isAdmin: false,
}; };
export const MovieNoUsername = Template.bind({}); export const MovieNoUsername = Template.bind({});
@ -206,4 +207,60 @@ TvNoUsername.args = {
mediaId: '603', mediaId: '603',
releaseDate: new Date(2020, 1, 1), releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested, } as IRecentlyRequested,
};
export const AdminNewMovie = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminNewMovie.args = {
request: {
title: 'The Matrix',
approved: false,
available: false,
tvPartiallyAvailable: false,
requestDate: new Date(2022, 1, 1),
username: 'John Doe',
userId: '12345',
type: RequestType.movie,
mediaId: '603',
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
};
export const AdminTvShow = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminTvShow.args = {
request: {
title: 'For All Mankind',
approved: false,
available: false,
tvPartiallyAvailable: true,
requestDate: new Date(2022, 1, 1),
userId: '12345',
type: RequestType.tvShow,
mediaId: '603',
username: 'John Doe',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
};
export const AdminApprovedMovie = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminApprovedMovie.args = {
request: {
title: 'The Matrix',
approved: true,
available: false,
tvPartiallyAvailable: false,
requestDate: new Date(2022, 1, 1),
username: 'John Doe',
userId: '12345',
type: RequestType.movie,
mediaId: '603',
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
}; };

View file

@ -7,14 +7,14 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
@Component({ @Component({
standalone: false, standalone: false,
selector: 'ombi-detailed-card', selector: 'ombi-detailed-card',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './detailed-card.component.html', templateUrl: './detailed-card.component.html',
styleUrls: ['./detailed-card.component.scss'] styleUrls: ['./detailed-card.component.scss']
}) })
export class DetailedCardComponent implements OnInit, OnDestroy { export class DetailedCardComponent implements OnInit, OnDestroy {
@Input() public request: IRecentlyRequested; @Input() public request: IRecentlyRequested;
@Input() public isAdmin: boolean = false;
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>(); @Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
@Output() public onApprove: EventEmitter<void> = new EventEmitter<void>();
public RequestType = RequestType; public RequestType = RequestType;
public loading: false; public loading: false;
@ -62,6 +62,10 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser";
this.onClick.emit(); this.onClick.emit();
} }
public approve() {
this.onApprove.emit();
}
public getClass(request: IRecentlyRequested) { public getClass(request: IRecentlyRequested) {
if (request.available || request.tvPartiallyAvailable) { if (request.available || request.tvPartiallyAvailable) {
return "success"; return "success";

View file

@ -1,5 +1,5 @@
<p-carousel #carousel [value]="requests" [numVisible]="3" [numScroll]="1" [responsiveOptions]="responsiveOptions" [page]="0"> <p-carousel #carousel [value]="requests$ | async" [numVisible]="3" [numScroll]="1" [responsiveOptions]="responsiveOptions" [page]="0">
<ng-template let-result pTemplate="item"> <ng-template let-result pTemplate="item">
<ombi-detailed-card [request]="result" (onClick)="navigate(result)"></ombi-detailed-card> <ombi-detailed-card [request]="result" [isAdmin]="isAdmin" (onClick)="navigate(result)" (onApprove)="approve(result)"></ombi-detailed-card>
</ng-template> </ng-template>
</p-carousel> </p-carousel>

View file

@ -1,15 +1,13 @@
import { Component, OnInit, Input, ViewChild, Output, EventEmitter, OnDestroy } from "@angular/core"; import { Component, OnInit, Input, ViewChild, OnDestroy } from "@angular/core";
import { DiscoverOption, IDiscoverCardResult } from "../../interfaces"; import { IRecentlyRequested, IRequestEngineResult, RequestType } from "../../../interfaces";
import { IRecentlyRequested, ISearchMovieResult, ISearchTvResult, RequestType } from "../../../interfaces";
import { SearchV2Service } from "../../../services";
import { StorageService } from "../../../shared/storage/storage-service";
import { MatButtonToggleChange } from '@angular/material/button-toggle';
import { Carousel } from 'primeng/carousel'; import { Carousel } from 'primeng/carousel';
import { FeaturesFacade } from "../../../state/features/features.facade";
import { ResponsiveOptions } from "../carousel.options"; import { ResponsiveOptions } from "../carousel.options";
import { RequestServiceV2 } from "app/services/requestV2.service"; import { RequestServiceV2 } from "app/services/requestV2.service";
import { Subject, takeUntil } from "rxjs"; import { finalize, map, Observable, Subject, takeUntil, tap } from "rxjs";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { AuthService } from "app/auth/auth.service";
import { NotificationService, RequestService } from "app/services";
import { TranslateService } from "@ngx-translate/core";
export enum DiscoverType { export enum DiscoverType {
Upcoming, Upcoming,
@ -30,21 +28,22 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
@Input() public isAdmin: boolean; @Input() public isAdmin: boolean;
@ViewChild('carousel', {static: false}) carousel: Carousel; @ViewChild('carousel', {static: false}) carousel: Carousel;
public requests$: Observable<IRecentlyRequested[]>;
public requests: IRecentlyRequested[];
public responsiveOptions: any; public responsiveOptions: any;
public RequestType = RequestType; public RequestType = RequestType;
public loadingFlag: boolean; public loadingFlag: boolean;
public DiscoverType = DiscoverType; public DiscoverType = DiscoverType;
public is4kEnabled = false;
private $loadSub = new Subject<void>(); private $loadSub = new Subject<void>();
constructor(private requestService: RequestServiceV2, constructor(private requestServiceV2: RequestServiceV2,
private featureFacade: FeaturesFacade, private requestService: RequestService,
private router: Router) { private router: Router,
Carousel.prototype.onTouchMove = () => {}, private authService: AuthService,
private notificationService: NotificationService,
private translateService: TranslateService) {
Carousel.prototype.onTouchMove = () => {};
this.responsiveOptions = ResponsiveOptions; this.responsiveOptions = ResponsiveOptions;
} }
@ -54,14 +53,43 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
} }
public ngOnInit() { public ngOnInit() {
this.loading();
this.loadData(); this.loadData();
this.isAdmin = this.authService.isAdmin();
} }
public navigate(request: IRecentlyRequested) { public navigate(request: IRecentlyRequested) {
this.router.navigate([this.generateDetailsLink(request), request.mediaId]); this.router.navigate([this.generateDetailsLink(request), request.mediaId]);
} }
public approve(request: IRecentlyRequested) {
switch(request.type) {
case RequestType.movie:
this.requestService.approveMovie({id: request.requestId, is4K: false}).pipe(
map((res) => this.handleApproval(res, request))
).subscribe();
break;
case RequestType.tvShow:
this.requestService.approveChild({id: request.requestId}).pipe(
tap((res) => this.handleApproval(res, request))
).subscribe();
break;
case RequestType.album:
this.requestService.approveAlbum({id: request.requestId}).pipe(
tap((res) => this.handleApproval(res, request))
).subscribe();
break;
}
}
private handleApproval(result: IRequestEngineResult, request: IRecentlyRequested) {
if (result.result) {
this.notificationService.success(this.translateService.instant("Requests.SuccessfullyApproved"));
request.approved = true;
} else {
this.notificationService.error(result.errorMessage);
}
}
private generateDetailsLink(request: IRecentlyRequested): string { private generateDetailsLink(request: IRecentlyRequested): string {
switch (request.type) { switch (request.type) {
case RequestType.movie: case RequestType.movie:
@ -74,13 +102,13 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
} }
private loadData() { private loadData() {
this.requestService.getRecentlyRequested().pipe(takeUntil(this.$loadSub)).subscribe(x => { this.requests$ = this.requestServiceV2.getRecentlyRequested().pipe(
this.requests = x; tap(() => this.loading()),
this.finishLoading(); takeUntil(this.$loadSub),
}); finalize(() => this.finishLoading())
);
} }
private loading() { private loading() {
this.loadingFlag = true; this.loadingFlag = true;
} }

View file

@ -1,14 +0,0 @@
{
"OmbiDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;"
},
"SettingsDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;"
},
"ExternalDatabase": {
"Type": "MySQL",
"ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;"
}
}