Added the basic advanced search

pull/4274/head
tidusjar 4 years ago
parent 6ee9606f7c
commit 924a562c57

@ -147,15 +147,15 @@ namespace Ombi.Core.Engine.V2
{ {
var langCode = await DefaultLanguageCode(null); var langCode = await DefaultLanguageCode(null);
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems); //var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
var results = new List<MovieDbSearchResult>(); var results = new List<MovieDbSearchResult>();
foreach (var pagesToLoad in pages) //foreach (var pagesToLoad in pages)
{ //{
var apiResult = await MovieApi.AdvancedSearch(model, cancellationToken); var apiResult = await MovieApi.AdvancedSearch(model, cancellationToken);
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); //results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
} //}
return await TransformMovieResultsToResponse(results); return await TransformMovieResultsToResponse(apiResult);
} }
/// <summary> /// <summary>

@ -90,7 +90,7 @@ namespace Ombi.Api.TheMovieDb
{ {
request.FullUri = request.FullUri.AddQueryParameter("with_watch_providers", string.Join(',', model.WatchProviders)); request.FullUri = request.FullUri.AddQueryParameter("with_watch_providers", string.Join(',', model.WatchProviders));
} }
request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc"); //request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc");
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken); var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken);
return Mapper.Map<List<MovieDbSearchResult>>(result.results); return Mapper.Map<List<MovieDbSearchResult>>(result.results);

@ -1,15 +1,15 @@
import { DiscoverComponent } from "./discover/discover.component"; import { RadarrService, RequestService, SearchService, SonarrService } from "../../services";
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
import { AuthGuard } from "../../auth/auth.guard";
import { CarouselListComponent } from "./carousel-list/carousel-list.component";
import { DiscoverActorComponent } from "./actor/discover-actor.component"; import { DiscoverActorComponent } from "./actor/discover-actor.component";
import { DiscoverCardComponent } from "./card/discover-card.component"; import { DiscoverCardComponent } from "./card/discover-card.component";
import { Routes } from "@angular/router"; import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
import { AuthGuard } from "../../auth/auth.guard"; import { DiscoverComponent } from "./discover/discover.component";
import { SearchService, RequestService, SonarrService, RadarrService } from "../../services";
import { MatDialog } from "@angular/material/dialog";
import { DiscoverSearchResultsComponent } from "./search-results/search-results.component"; import { DiscoverSearchResultsComponent } from "./search-results/search-results.component";
import { CarouselListComponent } from "./carousel-list/carousel-list.component"; import { MatDialog } from "@angular/material/dialog";
import { RequestServiceV2 } from "../../services/requestV2.service"; import { RequestServiceV2 } from "../../services/requestV2.service";
import { Routes } from "@angular/router";
export const components: any[] = [ export const components: any[] = [
DiscoverComponent, DiscoverComponent,
@ -34,4 +34,5 @@ export const routes: Routes = [
{ path: "collection/:collectionId", component: DiscoverCollectionsComponent, canActivate: [AuthGuard] }, { path: "collection/:collectionId", component: DiscoverCollectionsComponent, canActivate: [AuthGuard] },
{ path: "actor/:actorId", component: DiscoverActorComponent, canActivate: [AuthGuard] }, { path: "actor/:actorId", component: DiscoverActorComponent, canActivate: [AuthGuard] },
{ path: ":searchTerm", component: DiscoverSearchResultsComponent, canActivate: [AuthGuard] }, { path: ":searchTerm", component: DiscoverSearchResultsComponent, canActivate: [AuthGuard] },
{ path: "advanced/search", component: DiscoverSearchResultsComponent, canActivate: [AuthGuard] },
]; ];

@ -1,14 +1,15 @@
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { IMultiSearchResult, ISearchMovieResult, RequestType } from "../../../interfaces";
import { SearchV2Service } from "../../../services";
import { IDiscoverCardResult } from "../../interfaces"; import { AdvancedSearchDialogDataService } from "../../../shared/advanced-search-dialog/advanced-search-dialog-data.service";
import { IMultiSearchResult, RequestType } from "../../../interfaces"; import { AuthService } from "../../../auth/auth.service";
import { FilterService } from "../../services/filter-service"; import { FilterService } from "../../services/filter-service";
import { IDiscoverCardResult } from "../../interfaces";
import { SearchFilter } from "../../../my-nav/SearchFilter"; import { SearchFilter } from "../../../my-nav/SearchFilter";
import { SearchV2Service } from "../../../services";
import { StorageService } from "../../../shared/storage/storage-service"; import { StorageService } from "../../../shared/storage/storage-service";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import { AuthService } from "../../../auth/auth.service";
@Component({ @Component({
templateUrl: "./search-results.component.html", templateUrl: "./search-results.component.html",
@ -25,22 +26,41 @@ export class DiscoverSearchResultsComponent implements OnInit {
public filter: SearchFilter; public filter: SearchFilter;
private isAdvancedSearch: boolean;
constructor(private searchService: SearchV2Service, constructor(private searchService: SearchV2Service,
private route: ActivatedRoute, private route: ActivatedRoute,
private filterService: FilterService, private filterService: FilterService,
private router: Router,
private advancedDataService: AdvancedSearchDialogDataService,
private store: StorageService, private store: StorageService,
private authService: AuthService) { private authService: AuthService) {
this.route.params.subscribe((params: any) => { this.route.params.subscribe((params: any) => {
this.isAdvancedSearch = this.router.url === '/discover/advanced/search';
if (this.isAdvancedSearch) {
this.loadAdvancedData();
return;
}
this.searchTerm = params.searchTerm; this.searchTerm = params.searchTerm;
this.clear(); this.clear();
this.init(); this.init();
}); });
this.advancedDataService.onDataChange.subscribe(() => {
this.clear();
this.loadAdvancedData();
});
} }
public async ngOnInit() { public async ngOnInit() {
this.loadingFlag = true;
this.isAdmin = this.authService.isAdmin(); this.isAdmin = this.authService.isAdmin();
if (this.advancedDataService) {
return;
}
this.loadingFlag = true;
this.filterService.onFilterChange.subscribe(async x => { this.filterService.onFilterChange.subscribe(async x => {
if (!isEqual(this.filter, x)) { if (!isEqual(this.filter, x)) {
this.filter = { ...x }; this.filter = { ...x };
@ -115,6 +135,48 @@ export class DiscoverSearchResultsComponent implements OnInit {
this.discoverResults = []; this.discoverResults = [];
} }
private loadAdvancedData() {
const advancedData = this.advancedDataService.getData();
this.mapAdvancedData(advancedData);
return;
}
public mapAdvancedData(advancedData: ISearchMovieResult[]) {
this.finishLoading();
const type = this.advancedDataService.getType();
advancedData.forEach(m => {
let mediaType = type;
let poster = `https://image.tmdb.org/t/p/w300/${m.posterPath}`;
if (!m.posterPath) {
if (mediaType === RequestType.movie) {
poster = "images/default_movie_poster.png"
}
if (mediaType === RequestType.tvShow) {
poster = "images/default_tv_poster.png"
}
}
this.discoverResults.push({
posterPath: poster,
requested: false,
title: m.title,
type: mediaType,
id: m.id,
url: "",
rating: 0,
overview: m.overview,
approved: false,
imdbid: "",
denied: false,
background: "",
available: false,
tvMovieDb: false
});
});
}
private async search() { private async search() {
this.clear(); this.clear();
this.results = await this.searchService this.results = await this.searchService

@ -1,16 +1,14 @@
import { NgModule } from "@angular/core"; import * as fromComponents from './components';
import { RouterModule } from "@angular/router";
import { CarouselModule } from 'primeng/carousel';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import {MatButtonToggleModule} from '@angular/material/button-toggle'; import {MatButtonToggleModule} from '@angular/material/button-toggle';
import { NgModule } from "@angular/core";
import { SharedModule } from "../shared/shared.module";
import { PipeModule } from "../pipes/pipe.module"; import { PipeModule } from "../pipes/pipe.module";
import { CarouselModule } from 'primeng/carousel'; import { RouterModule } from "@angular/router";
import { SharedModule } from "../shared/shared.module";
import { SkeletonModule } from 'primeng/skeleton'; import { SkeletonModule } from 'primeng/skeleton';
import * as fromComponents from './components';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild(fromComponents.routes), RouterModule.forChild(fromComponents.routes),

@ -235,3 +235,13 @@
.advanced-search { .advanced-search {
margin-left: 10px; margin-left: 10px;
} }
::ng-deep .dialog-responsive {
width: 40%;
}
@media only screen and (max-width: 760px) {
::ng-deep .dialog-responsive {
width: 100%;
}
}

@ -11,6 +11,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Router } from '@angular/router';
import { SearchFilter } from './SearchFilter'; import { SearchFilter } from './SearchFilter';
import { StorageService } from '../shared/storage/storage-service'; import { StorageService } from '../shared/storage/storage-service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -57,7 +58,8 @@ export class MyNavComponent implements OnInit {
private store: StorageService, private store: StorageService,
private filterService: FilterService, private filterService: FilterService,
private dialogService: MatDialog, private dialogService: MatDialog,
private readonly settingState: SettingsStateService) { private readonly settingState: SettingsStateService,
private router: Router) {
} }
public async ngOnInit() { public async ngOnInit() {
@ -125,7 +127,15 @@ export class MyNavComponent implements OnInit {
} }
public openAdvancedSearch() { public openAdvancedSearch() {
this.dialogService.open(AdvancedSearchDialogComponent, null); const dialogRef = this.dialogService.open(AdvancedSearchDialogComponent, { panelClass: 'dialog-responsive' });
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.router.navigate([`discover/advanced/search`]);
}
return;
});
} }
public getUserImage(): string { public getUserImage(): string {

@ -0,0 +1,27 @@
import { EventEmitter, Injectable, Output } from "@angular/core";
import { RequestType } from "../../interfaces";
@Injectable({
providedIn: "root"
})
export class AdvancedSearchDialogDataService {
@Output() public onDataChange = new EventEmitter<any>();
private _data: any;
private _type: RequestType;
setData(data: any, type: RequestType) {
this._data = data;
this._type = type;
this.onDataChange.emit(this._data);
}
getData(): any {
return this._data;
}
getType(): RequestType {
return this._type;
}
}

@ -4,8 +4,8 @@
</h1> </h1>
<hr /> <hr />
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
<i class="fas fa-x7 fa-exclamation-triangle glyphicon"></i> <i class="fas fa-x7 fa-search glyphicon"></i>
<span>{{ "MediaDetails.AutoApproveOptions" | translate }}</span> <span>{{ "Search.AdvancedSearch" | translate }}</span>
</div> </div>
<div style="max-width: 0; max-height: 0; overflow: hidden"> <div style="max-width: 0; max-height: 0; overflow: hidden">
@ -13,7 +13,10 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div style="margin: 2%;">
<span>Please choose what type of media you are searching for:</span>
</div>
<div class="col-md-12">
<div class="md-form-field"> <div class="md-form-field">
<mat-radio-group formControlName="type" aria-label="Select an option"> <mat-radio-group formControlName="type" aria-label="Select an option">
<mat-radio-button value="movie">Movies </mat-radio-button> <mat-radio-button value="movie">Movies </mat-radio-button>
@ -21,23 +24,26 @@
</mat-radio-group> </mat-radio-group>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-12" style="margin-top:1%">
<mat-form-field appearance="outline" floatLabel=always> <mat-form-field appearance="outline" floatLabel=auto>
<mat-label>Year</mat-label> <mat-label>Year of Release</mat-label>
<input matInput id="releaseYear" name="releaseYear" formControlName="releaseYear"> <input matInput id="releaseYear" name="releaseYear" formControlName="releaseYear">
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-md-6">
<keyword-search [form]="form"></keyword-search> <div class="col-md-12">
</div>
<div class="col-md-6">
<genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select> <genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select>
</div> </div>
<div class="col-md-6"> <div class="col-md-12">
<watch-providers-select [form]="form" [mediaType]="form.controls.type.value"></watch-providers-select> <watch-providers-select [form]="form" [mediaType]="form.controls.type.value"></watch-providers-select>
</div> </div>
<div class="col-md-12">
<span style="margin: 1%;">Please note that Keyword Searching is very hit and miss due to the inconsistent data in TheMovieDb</span>
<keyword-search [form]="form"></keyword-search>
</div>
</div> </div>

@ -1,9 +1,9 @@
import { Component, Inject, OnInit } from "@angular/core"; import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder, FormGroup } from "@angular/forms"; import { FormBuilder, FormGroup } from "@angular/forms";
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
import { IDiscoverModel } from "../../interfaces"; import { RequestType } from "../../interfaces";
import { SearchV2Service } from "../../services"; import { SearchV2Service } from "../../services";
import { AdvancedSearchDialogDataService } from "./advanced-search-dialog-data.service";
@Component({ @Component({
selector: "advanced-search-dialog", selector: "advanced-search-dialog",
@ -12,15 +12,14 @@ import { SearchV2Service } from "../../services";
}) })
export class AdvancedSearchDialogComponent implements OnInit { export class AdvancedSearchDialogComponent implements OnInit {
constructor( constructor(
public dialogRef: MatDialogRef<AdvancedSearchDialogComponent, string>, public dialogRef: MatDialogRef<AdvancedSearchDialogComponent, boolean>,
@Inject(MAT_DIALOG_DATA) public data: any,
private fb: FormBuilder, private fb: FormBuilder,
private searchService: SearchV2Service, private searchService: SearchV2Service,
private advancedSearchDialogService: AdvancedSearchDialogDataService
) {} ) {}
public form: FormGroup; public form: FormGroup;
public async ngOnInit() { public async ngOnInit() {
this.form = this.fb.group({ this.form = this.fb.group({
@ -35,17 +34,28 @@ export class AdvancedSearchDialogComponent implements OnInit {
this.form.controls.genres.setValue([]); this.form.controls.genres.setValue([]);
this.form.controls.watchProviders.setValue([]); this.form.controls.watchProviders.setValue([]);
}); });
} }
public async onSubmit() { public async onSubmit() {
const watchProviderIds = <number[]>this.form.controls.watchProviders.value.map(x => x.provider_id); const formData = this.form.value;
const genres = <number[]>this.form.controls.genreIds.value.map(x => x.id); const watchProviderIds = <number[]>formData.watchProviders.map(x => x.provider_id);
await this.searchService.advancedSearch({ const genres = <number[]>formData.genreIds.map(x => x.id);
const keywords = <number[]>formData.keywordIds.map(x => x.id);
const data = await this.searchService.advancedSearch({
watchProviders: watchProviderIds, watchProviders: watchProviderIds,
genreIds: genres, genreIds: genres,
type: this.form.controls.type.value, keywordIds: keywords,
releaseYear: formData.releaseYear,
type: formData.type,
}, 0, 30); }, 0, 30);
this.advancedSearchDialogService.setData(data, formData.type === 'movie' ? RequestType.movie : RequestType.tvShow);
this.dialogRef.close(true);
}
public onClose() {
this.dialogRef.close(false);
} }
} }

@ -1,5 +1,5 @@
<mat-form-field class="example-chip-list" appearance="fill"> <mat-form-field appearance="outline" floatLabel=auto class="example-chip-list">
<mat-label>Genres</mat-label> <mat-label>Genres</mat-label>
<mat-chip-list #chipList aria-label="Fruit selection"> <mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip <mat-chip

@ -1,17 +1,4 @@
<!-- <mat-form-field class="example-full-width"> <mat-form-field class="example-chip-list" appearance="outline" floatLabel=auto>
<input type="text" placeholder="Excluded Keyword IDs for Movie & TV Suggestions" matInput
formControlName="input" [matAutocomplete]="auto"
matTooltip="Prevent movies with certain keywords from being suggested. May require a restart to take effect.">
<mat-autocomplete autoActiveFirstOption
#auto="matAutocomplete">
<mat-option *ngFor="let option of filteredTags" [value]="option">
{{option.name}}
</mat-option>
</mat-autocomplete>
</mat-form-field> -->
<mat-form-field class="example-chip-list" appearance="fill">
<mat-label>Keywords</mat-label> <mat-label>Keywords</mat-label>
<mat-chip-list #chipList aria-label="Fruit selection"> <mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip <mat-chip

@ -1,4 +1,4 @@
<mat-form-field class="example-chip-list" appearance="fill"> <mat-form-field class="example-chip-list" appearance="outline" floatLabel=auto>
<mat-label>Watch Providers</mat-label> <mat-label>Watch Providers</mat-label>
<mat-chip-list #chipList aria-label="Fruit selection"> <mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip <mat-chip

@ -22,6 +22,7 @@
"RequestDenied": "Request Denied", "RequestDenied": "Request Denied",
"NotRequested": "Not Requested", "NotRequested": "Not Requested",
"Requested": "Requested", "Requested": "Requested",
"Search":"Search",
"Request": "Request", "Request": "Request",
"Denied": "Denied", "Denied": "Denied",
"Approve": "Approve", "Approve": "Approve",
@ -88,6 +89,7 @@
"MoviesTab": "Movies", "MoviesTab": "Movies",
"TvTab": "TV Shows", "TvTab": "TV Shows",
"MusicTab": "Music", "MusicTab": "Music",
"AdvancedSearch":"You can fill in any of the below to discover new media. All of the results are sorted by popularity",
"Suggestions": "Suggestions", "Suggestions": "Suggestions",
"NoResults": "Sorry, we didn't find any results!", "NoResults": "Sorry, we didn't find any results!",
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",

Loading…
Cancel
Save