mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-30 03:28:28 -07:00
Added the basic advanced search
This commit is contained in:
parent
6ee9606f7c
commit
924a562c57
14 changed files with 187 additions and 74 deletions
|
@ -147,15 +147,15 @@ namespace Ombi.Core.Engine.V2
|
|||
{
|
||||
var langCode = await DefaultLanguageCode(null);
|
||||
|
||||
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
|
||||
//var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
|
||||
|
||||
var results = new List<MovieDbSearchResult>();
|
||||
foreach (var pagesToLoad in pages)
|
||||
{
|
||||
//foreach (var pagesToLoad in pages)
|
||||
//{
|
||||
var apiResult = await MovieApi.AdvancedSearch(model, cancellationToken);
|
||||
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
||||
}
|
||||
return await TransformMovieResultsToResponse(results);
|
||||
//results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
||||
//}
|
||||
return await TransformMovieResultsToResponse(apiResult);
|
||||
}
|
||||
|
||||
/// <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("sort_by", "popularity.desc");
|
||||
//request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc");
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request, cancellationToken);
|
||||
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { DiscoverComponent } from "./discover/discover.component";
|
||||
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
||||
import { RadarrService, RequestService, SearchService, SonarrService } from "../../services";
|
||||
|
||||
import { AuthGuard } from "../../auth/auth.guard";
|
||||
import { CarouselListComponent } from "./carousel-list/carousel-list.component";
|
||||
import { DiscoverActorComponent } from "./actor/discover-actor.component";
|
||||
import { DiscoverCardComponent } from "./card/discover-card.component";
|
||||
import { Routes } from "@angular/router";
|
||||
import { AuthGuard } from "../../auth/auth.guard";
|
||||
import { SearchService, RequestService, SonarrService, RadarrService } from "../../services";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { DiscoverCollectionsComponent } from "./collections/discover-collections.component";
|
||||
import { DiscoverComponent } from "./discover/discover.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 { Routes } from "@angular/router";
|
||||
|
||||
export const components: any[] = [
|
||||
DiscoverComponent,
|
||||
|
@ -34,4 +34,5 @@ export const routes: Routes = [
|
|||
{ path: "collection/:collectionId", component: DiscoverCollectionsComponent, canActivate: [AuthGuard] },
|
||||
{ path: "actor/:actorId", component: DiscoverActorComponent, 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 { ActivatedRoute } from "@angular/router";
|
||||
import { SearchV2Service } from "../../../services";
|
||||
import { IDiscoverCardResult } from "../../interfaces";
|
||||
import { IMultiSearchResult, RequestType } from "../../../interfaces";
|
||||
import { FilterService } from "../../services/filter-service";
|
||||
import { SearchFilter } from "../../../my-nav/SearchFilter";
|
||||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
import { IMultiSearchResult, ISearchMovieResult, RequestType } from "../../../interfaces";
|
||||
|
||||
import { isEqual } from "lodash";
|
||||
import { AdvancedSearchDialogDataService } from "../../../shared/advanced-search-dialog/advanced-search-dialog-data.service";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { FilterService } from "../../services/filter-service";
|
||||
import { IDiscoverCardResult } from "../../interfaces";
|
||||
import { SearchFilter } from "../../../my-nav/SearchFilter";
|
||||
import { SearchV2Service } from "../../../services";
|
||||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
import { isEqual } from "lodash";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./search-results.component.html",
|
||||
|
@ -25,22 +26,41 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
|||
|
||||
public filter: SearchFilter;
|
||||
|
||||
private isAdvancedSearch: boolean;
|
||||
|
||||
constructor(private searchService: SearchV2Service,
|
||||
private route: ActivatedRoute,
|
||||
private filterService: FilterService,
|
||||
private router: Router,
|
||||
private advancedDataService: AdvancedSearchDialogDataService,
|
||||
private store: StorageService,
|
||||
private authService: AuthService) {
|
||||
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.clear();
|
||||
this.init();
|
||||
});
|
||||
|
||||
this.advancedDataService.onDataChange.subscribe(() => {
|
||||
this.clear();
|
||||
this.loadAdvancedData();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
this.loadingFlag = true;
|
||||
this.isAdmin = this.authService.isAdmin();
|
||||
|
||||
if (this.advancedDataService) {
|
||||
return;
|
||||
}
|
||||
this.loadingFlag = true;
|
||||
|
||||
this.filterService.onFilterChange.subscribe(async x => {
|
||||
if (!isEqual(this.filter, x)) {
|
||||
this.filter = { ...x };
|
||||
|
@ -115,6 +135,48 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
|||
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() {
|
||||
this.clear();
|
||||
this.results = await this.searchService
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import * as fromComponents from './components';
|
||||
|
||||
import { CarouselModule } from 'primeng/carousel';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { NgModule } from "@angular/core";
|
||||
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 * as fromComponents from './components';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(fromComponents.routes),
|
||||
|
|
|
@ -235,3 +235,13 @@
|
|||
.advanced-search {
|
||||
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 { Md5 } from 'ts-md5/dist/md5';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { SearchFilter } from './SearchFilter';
|
||||
import { StorageService } from '../shared/storage/storage-service';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
@ -57,7 +58,8 @@ export class MyNavComponent implements OnInit {
|
|||
private store: StorageService,
|
||||
private filterService: FilterService,
|
||||
private dialogService: MatDialog,
|
||||
private readonly settingState: SettingsStateService) {
|
||||
private readonly settingState: SettingsStateService,
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
|
@ -125,7 +127,15 @@ export class MyNavComponent implements OnInit {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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>
|
||||
<hr />
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="fas fa-x7 fa-exclamation-triangle glyphicon"></i>
|
||||
<span>{{ "MediaDetails.AutoApproveOptions" | translate }}</span>
|
||||
<i class="fas fa-x7 fa-search glyphicon"></i>
|
||||
<span>{{ "Search.AdvancedSearch" | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 0; max-height: 0; overflow: hidden">
|
||||
|
@ -13,7 +13,10 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<mat-radio-group formControlName="type" aria-label="Select an option">
|
||||
<mat-radio-button value="movie">Movies </mat-radio-button>
|
||||
|
@ -21,23 +24,26 @@
|
|||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<mat-form-field appearance="outline" floatLabel=always>
|
||||
<mat-label>Year</mat-label>
|
||||
<div class="col-md-12" style="margin-top:1%">
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>Year of Release</mat-label>
|
||||
<input matInput id="releaseYear" name="releaseYear" formControlName="releaseYear">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<keyword-search [form]="form"></keyword-search>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="col-md-12">
|
||||
<genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-12">
|
||||
<watch-providers-select [form]="form" [mediaType]="form.controls.type.value"></watch-providers-select>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
|
||||
import { IDiscoverModel } from "../../interfaces";
|
||||
import { RequestType } from "../../interfaces";
|
||||
import { SearchV2Service } from "../../services";
|
||||
|
||||
import { AdvancedSearchDialogDataService } from "./advanced-search-dialog-data.service";
|
||||
|
||||
@Component({
|
||||
selector: "advanced-search-dialog",
|
||||
|
@ -12,15 +12,14 @@ import { SearchV2Service } from "../../services";
|
|||
})
|
||||
export class AdvancedSearchDialogComponent implements OnInit {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<AdvancedSearchDialogComponent, string>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
public dialogRef: MatDialogRef<AdvancedSearchDialogComponent, boolean>,
|
||||
private fb: FormBuilder,
|
||||
private searchService: SearchV2Service,
|
||||
private advancedSearchDialogService: AdvancedSearchDialogDataService
|
||||
) {}
|
||||
|
||||
public form: FormGroup;
|
||||
|
||||
|
||||
public async ngOnInit() {
|
||||
|
||||
this.form = this.fb.group({
|
||||
|
@ -35,17 +34,28 @@ export class AdvancedSearchDialogComponent implements OnInit {
|
|||
this.form.controls.genres.setValue([]);
|
||||
this.form.controls.watchProviders.setValue([]);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public async onSubmit() {
|
||||
const watchProviderIds = <number[]>this.form.controls.watchProviders.value.map(x => x.provider_id);
|
||||
const genres = <number[]>this.form.controls.genreIds.value.map(x => x.id);
|
||||
await this.searchService.advancedSearch({
|
||||
const formData = this.form.value;
|
||||
const watchProviderIds = <number[]>formData.watchProviders.map(x => x.provider_id);
|
||||
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,
|
||||
genreIds: genres,
|
||||
type: this.form.controls.type.value,
|
||||
keywordIds: keywords,
|
||||
releaseYear: formData.releaseYear,
|
||||
type: formData.type,
|
||||
}, 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-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
<!-- <mat-form-field class="example-full-width">
|
||||
<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-form-field class="example-chip-list" appearance="outline" floatLabel=auto>
|
||||
<mat-label>Keywords</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<mat-form-field class="example-chip-list" appearance="fill">
|
||||
<mat-label>Watch Providers</mat-label>
|
||||
<mat-form-field class="example-chip-list" appearance="outline" floatLabel=auto>
|
||||
<mat-label>Watch Providers</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip
|
||||
*ngFor="let word of form.controls.watchProviders.value"
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"RequestDenied": "Request Denied",
|
||||
"NotRequested": "Not Requested",
|
||||
"Requested": "Requested",
|
||||
"Search":"Search",
|
||||
"Request": "Request",
|
||||
"Denied": "Denied",
|
||||
"Approve": "Approve",
|
||||
|
@ -88,6 +89,7 @@
|
|||
"MoviesTab": "Movies",
|
||||
"TvTab": "TV Shows",
|
||||
"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",
|
||||
"NoResults": "Sorry, we didn't find any results!",
|
||||
"DigitalDate": "Digital Release: {{date}}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue