From 83ee2dae1a5e5d7e38f9db533b45e085efaaa369 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 26 Jul 2021 14:39:06 +0100 Subject: [PATCH] Made a start on the advanced options --- src/Ombi.TheMovieDbApi/IMovieDbApi.cs | 8 +- .../{Keyword.cs => TheMovidDbKeyValue.cs} | 2 +- src/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 14 +- src/Ombi/ClientApp/src/app/app.module.ts | 120 +++++++++++------- .../search-results.component.html | 1 + .../src/app/my-nav/my-nav.component.html | 1 + .../src/app/my-nav/my-nav.component.ts | 7 + .../applications/themoviedb.service.ts | 4 +- .../themoviedb/themoviedb.component.ts | 12 +- .../advanced-search-dialog.component.html | 58 +++++++++ .../advanced-search-dialog.component.scss | 8 ++ .../advanced-search-dialog.component.ts | 36 ++++++ .../genre-select/genre-select.component.html | 25 ++++ .../genre-select/genre-select.component.ts | 85 +++++++++++++ .../keyword-search.component.html | 37 ++++++ .../keyword-search.component.ts | 64 ++++++++++ .../ClientApp/src/app/shared/shared.module.ts | 58 +++++---- .../V1/External/TheMovieDbController.cs | 16 +-- 18 files changed, 457 insertions(+), 99 deletions(-) rename src/Ombi.TheMovieDbApi/Models/{Keyword.cs => TheMovidDbKeyValue.cs} (84%) create mode 100644 src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.html create mode 100644 src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.scss create mode 100644 src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.ts create mode 100644 src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.html create mode 100644 src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.ts create mode 100644 src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.html create mode 100644 src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.ts diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index 7a0f7e385..202392c5f 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -26,7 +26,7 @@ namespace Ombi.Api.TheMovieDb Task> UpcomingTv(string languageCode, int? page = null); Task> SimilarMovies(int movieId, string langCode); Task Find(string externalId, ExternalSource source); - Task GetTvExternals(int theMovieDbId); + Task GetTvExternals(int theMovieDbId); Task GetSeasonEpisodes(int theMovieDbId, int seasonNumber, CancellationToken token, string langCode = "en"); Task GetTVInfo(string themoviedbid, string langCode = "en"); Task> SearchByActor(string searchTerm, string langCode); @@ -35,10 +35,10 @@ namespace Ombi.Api.TheMovieDb Task> DiscoverMovies(string langCode, int keywordId); Task GetFullMovieInfo(int movieId, CancellationToken cancellationToken, string langCode); Task GetCollection(string langCode, int collectionId, CancellationToken cancellationToken); - Task> SearchKeyword(string searchTerm); - Task GetKeyword(int keywordId); + Task> SearchKeyword(string searchTerm); + Task GetKeyword(int keywordId); Task GetMovieWatchProviders(int theMoviedbId, CancellationToken token); Task GetTvWatchProviders(int theMoviedbId, CancellationToken token); - Task> GetGenres(string media); + Task> GetGenres(string media, CancellationToken cancellationToken); } } diff --git a/src/Ombi.TheMovieDbApi/Models/Keyword.cs b/src/Ombi.TheMovieDbApi/Models/TheMovidDbKeyValue.cs similarity index 84% rename from src/Ombi.TheMovieDbApi/Models/Keyword.cs rename to src/Ombi.TheMovieDbApi/Models/TheMovidDbKeyValue.cs index 770eebc94..a5f3fc0db 100644 --- a/src/Ombi.TheMovieDbApi/Models/Keyword.cs +++ b/src/Ombi.TheMovieDbApi/Models/TheMovidDbKeyValue.cs @@ -2,7 +2,7 @@ namespace Ombi.Api.TheMovieDb.Models { - public sealed class Keyword + public sealed class TheMovidDbKeyValue { [DataMember(Name = "id")] public int Id { get; set; } diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 70e4a94c7..a8fdc3269 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -357,34 +357,34 @@ namespace Ombi.Api.TheMovieDb return Mapper.Map>(result.results); } - public async Task> SearchKeyword(string searchTerm) + public async Task> SearchKeyword(string searchTerm) { var request = new Request("search/keyword", BaseUri, HttpMethod.Get); request.AddQueryString("api_key", ApiToken); request.AddQueryString("query", searchTerm); AddRetry(request); - var result = await Api.Request>(request); - return result.results ?? new List(); + var result = await Api.Request>(request); + return result.results ?? new List(); } - public async Task GetKeyword(int keywordId) + public async Task GetKeyword(int keywordId) { var request = new Request($"keyword/{keywordId}", BaseUri, HttpMethod.Get); request.AddQueryString("api_key", ApiToken); AddRetry(request); - var keyword = await Api.Request(request); + var keyword = await Api.Request(request); return keyword == null || keyword.Id == 0 ? null : keyword; } - public async Task> GetGenres(string media) + public async Task> GetGenres(string media, CancellationToken cancellationToken) { var request = new Request($"genre/{media}/list", BaseUri, HttpMethod.Get); request.AddQueryString("api_key", ApiToken); AddRetry(request); - var result = await Api.Request>(request); + var result = await Api.Request>(request, cancellationToken); return result.genres ?? new List(); } diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index 8e44d59bd..1716b5c5e 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -1,73 +1,98 @@ -import { CommonModule, PlatformLocation, APP_BASE_HREF } from "@angular/common"; -import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http"; -import { NgModule } from "@angular/core"; +import { APP_BASE_HREF, CommonModule, PlatformLocation } from "@angular/common"; +import { CardsFreeModule, MDBBootstrapModule, NavbarModule } from "angular-bootstrap-md"; +import { CustomPageService, ImageService, RequestService, SettingsService } from "./services"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from "@angular/common/http"; +import { IdentityService, IssuesService, JobService, MessageService, PlexTvService, SearchService, StatusService } from "./services"; import { RouterModule, Routes } from "@angular/router"; - -import { JwtModule } from "@auth0/angular-jwt"; import { TranslateLoader, TranslateModule } from "@ngx-translate/core"; -import { TranslateHttpLoader } from "@ngx-translate/http-loader"; -import { CookieService } from "ng2-cookies"; +import { AppComponent } from "./app.component"; +import { AuthGuard } from "./auth/auth.guard"; +import { AuthService } from "./auth/auth.service"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { BrowserModule } from "@angular/platform-browser"; import { ButtonModule } from "primeng/button"; import { ConfirmDialogModule } from "primeng/confirmdialog"; +import { CookieComponent } from "./auth/cookie.component"; +import { CookieService } from "ng2-cookies"; +import { CustomPageComponent } from "./custompage/custompage.component"; import { DataViewModule } from "primeng/dataview"; import { DialogModule } from "primeng/dialog"; -import { OverlayPanelModule } from "primeng/overlaypanel"; -import { TooltipModule } from "primeng/tooltip"; -import { SidebarModule } from "primeng/sidebar"; - +import { FilterService } from "./discover/services/filter-service"; +import { JwtModule } from "@auth0/angular-jwt"; +import { LandingPageComponent } from "./landingpage/landingpage.component"; +import { LandingPageService } from "./services"; +import { LayoutModule } from '@angular/cdk/layout'; +import { LoginComponent } from "./login/login.component"; +import { LoginOAuthComponent } from "./login/loginoauth.component"; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from "@angular/material/card"; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatNativeDateModule } from '@angular/material/core'; +import { MatChipsModule } from "@angular/material/chips"; +import { MatDialogModule } from "@angular/material/dialog"; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from "@angular/material/input"; import { MatListModule } from '@angular/material/list'; +import { MatMenuModule } from "@angular/material/menu"; +import { MatNativeDateModule } from '@angular/material/core'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatCardModule } from "@angular/material/card"; -import { MatInputModule } from "@angular/material/input"; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; +import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTabsModule } from "@angular/material/tabs"; +import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from "@angular/material/tooltip"; +import { MyNavComponent } from './my-nav/my-nav.component'; +import { NavSearchComponent } from "./my-nav/nav-search.component"; +import { NgModule } from "@angular/core"; +import { NotificationService } from "./services"; +import { OverlayModule } from "@angular/cdk/overlay"; +import { OverlayPanelModule } from "primeng/overlaypanel"; +import { PageNotFoundComponent } from "./errors/not-found.component"; +import { RemainingRequestsComponent } from "./shared/remaining-requests/remaining-requests.component"; +import { ResetPasswordComponent } from "./login/resetpassword.component"; +import { SearchV2Service } from "./services/searchV2.service"; +import { SidebarModule } from "primeng/sidebar"; +import { SignalRNotificationService } from "./services/signlarnotification.service"; +import { StorageService } from "./shared/storage/storage-service"; +import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; +import { TooltipModule } from "primeng/tooltip"; +import { TranslateHttpLoader } from "@ngx-translate/http-loader"; +import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; + +// Components + + + + + + -import { MDBBootstrapModule, CardsFreeModule, NavbarModule } from "angular-bootstrap-md"; -// Components -import { AppComponent } from "./app.component"; -import { CookieComponent } from "./auth/cookie.component"; -import { CustomPageComponent } from "./custompage/custompage.component"; -import { PageNotFoundComponent } from "./errors/not-found.component"; -import { LandingPageComponent } from "./landingpage/landingpage.component"; -import { LoginComponent } from "./login/login.component"; -import { LoginOAuthComponent } from "./login/loginoauth.component"; -import { ResetPasswordComponent } from "./login/resetpassword.component"; -import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; // Services -import { AuthGuard } from "./auth/auth.guard"; -import { AuthService } from "./auth/auth.service"; -import { ImageService, SettingsService, CustomPageService, RequestService } from "./services"; -import { LandingPageService } from "./services"; -import { NotificationService } from "./services"; -import { IssuesService, JobService, PlexTvService, StatusService, SearchService, IdentityService, MessageService } from "./services"; -import { MyNavComponent } from './my-nav/my-nav.component'; -import { LayoutModule } from '@angular/cdk/layout'; -import { SearchV2Service } from "./services/searchV2.service"; -import { NavSearchComponent } from "./my-nav/nav-search.component"; -import { OverlayModule } from "@angular/cdk/overlay"; -import { StorageService } from "./shared/storage/storage-service"; -import { SignalRNotificationService } from "./services/signlarnotification.service"; -import { MatMenuModule } from "@angular/material/menu"; -import { RemainingRequestsComponent } from "./shared/remaining-requests/remaining-requests.component"; -import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; -import { FilterService } from "./discover/services/filter-service"; + + + + + + + + + + + + + + + + + + const routes: Routes = [ { path: "*", component: PageNotFoundComponent }, @@ -135,6 +160,8 @@ export function JwtTokenGetter() { MatMenuModule, MatInputModule, MatTabsModule, + MatChipsModule, + MatDialogModule, ReactiveFormsModule, MatAutocompleteModule, TooltipModule, @@ -146,7 +173,6 @@ export function JwtTokenGetter() { MatCheckboxModule, MatProgressSpinnerModule, MDBBootstrapModule.forRoot(), - // NbThemeModule.forRoot({ name: 'dark'}), JwtModule.forRoot({ config: { tokenGetter: JwtTokenGetter, diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html index 29a583890..2bd13441d 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html @@ -1,4 +1,5 @@
+
diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html index 95f31198b..84ae81fe8 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html @@ -78,6 +78,7 @@ {{ 'NavigationBar.Filter.Music' | translate}} + diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts index 57bfaca91..393d12fd4 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.ts @@ -3,9 +3,11 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { IUser, RequestType, UserType } from '../interfaces'; import { SettingsService, SettingsStateService } from '../services'; +import { AdvancedSearchDialogComponent } from '../shared/advanced-search-dialog/advanced-search-dialog.component'; import { FilterService } from '../discover/services/filter-service'; import { ILocalUser } from '../auth/IUserLogin'; import { INavBar } from '../interfaces/ICommon'; +import { MatDialog } from '@angular/material/dialog'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { Md5 } from 'ts-md5/dist/md5'; import { Observable } from 'rxjs'; @@ -54,6 +56,7 @@ export class MyNavComponent implements OnInit { private settingsService: SettingsService, private store: StorageService, private filterService: FilterService, + private dialogService: MatDialog, private readonly settingState: SettingsStateService) { } @@ -121,6 +124,10 @@ export class MyNavComponent implements OnInit { this.store.save("searchFilter", JSON.stringify(this.searchFilter)); } + public openAdvancedSearch() { + this.dialogService.open(AdvancedSearchDialogComponent, null); + } + public getUserImage(): string { var fallback = this.applicationLogo ? this.applicationLogo : 'https://raw.githubusercontent.com/Ombi-app/Ombi/gh-pages/img/android-chrome-512x512.png'; return `https://www.gravatar.com/avatar/${this.emailHash}?d=${fallback}`; diff --git a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts index ff76d591b..0af37c508 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/themoviedb.service.ts @@ -7,7 +7,9 @@ import { catchError } from "rxjs/operators"; import { IMovieDbKeyword } from "../../interfaces"; import { ServiceHelpers } from "../service.helpers"; -@Injectable() +@Injectable({ + providedIn: 'root', + }) export class TheMovieDbService extends ServiceHelpers { constructor(http: HttpClient, @Inject(APP_BASE_HREF) href:string) { super(http, "/api/v1/TheMovieDb", href); diff --git a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts index 49ec3e51f..008e2dfab 100644 --- a/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/themoviedb/themoviedb.component.ts @@ -1,13 +1,13 @@ import {COMMA, ENTER} from "@angular/cdk/keycodes"; -import { Component, OnInit, ElementRef, ViewChild } from "@angular/core"; -import { MatAutocomplete } from "@angular/material/autocomplete"; +import { Component, ElementRef, OnInit, ViewChild } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; +import { IMovieDbKeyword, ITheMovieDbSettings } from "../../interfaces"; +import { debounceTime, switchMap } from "rxjs/operators"; -import { ITheMovieDbSettings, IMovieDbKeyword } from "../../interfaces"; +import { MatAutocomplete } from "@angular/material/autocomplete"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; import { TheMovieDbService } from "../../services"; -import { FormBuilder, FormGroup } from "@angular/forms"; -import { debounceTime, switchMap } from "rxjs/operators"; interface IKeywordTag { id: number; @@ -30,8 +30,6 @@ export class TheMovieDbComponent implements OnInit { public filteredMovieGenres: IMovieDbKeyword[]; public filteredTvGenres: IMovieDbKeyword[]; - @ViewChild('fruitInput') public fruitInput: ElementRef; - constructor(private settingsService: SettingsService, private notificationService: NotificationService, private tmdbService: TheMovieDbService, diff --git a/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.html b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.html new file mode 100644 index 000000000..02f9ac35b --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.html @@ -0,0 +1,58 @@ +
+

+ Advanced Search +

+
+ + +
+ +
+ +
+
+
+ + Movies + TV Shows + +
+
+
+ + Year + + +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+
diff --git a/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.scss b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.scss new file mode 100644 index 000000000..8d701c21f --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.scss @@ -0,0 +1,8 @@ + +@import "~styles/variables.scss"; + +.alert-info { + background: $accent; + border-color: $ombi-background-primary; + color:white; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.ts b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.ts new file mode 100644 index 000000000..e59f7fab7 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/advanced-search-dialog/advanced-search-dialog.component.ts @@ -0,0 +1,36 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; + + +@Component({ + selector: "advanced-search-dialog", + templateUrl: "advanced-search-dialog.component.html", + styleUrls: [ "advanced-search-dialog.component.scss" ] +}) +export class AdvancedSearchDialogComponent implements OnInit { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + private fb: FormBuilder + ) {} + + public form: FormGroup; + + + public async ngOnInit() { + + this.form = this.fb.group({ + keywords: [[]], + genres: [[]], + releaseYear: [], + type: ['movie'], + }) + + this.form.controls.type.valueChanges.subscribe(val => { + this.form.controls.genres.setValue([]); + }); + + + } +} diff --git a/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.html b/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.html new file mode 100644 index 000000000..523974a90 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.html @@ -0,0 +1,25 @@ + + + Genres + + + {{word.name}} + cancel + + + + + + {{word.name}} + + + + diff --git a/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.ts b/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.ts new file mode 100644 index 000000000..c80eb4ae8 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/components/genre-select/genre-select.component.ts @@ -0,0 +1,85 @@ +import { Component, ElementRef, Input, OnInit, ViewChild } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { debounceTime, distinctUntilChanged, map, startWith, switchMap } from "rxjs/operators"; + +import { IMovieDbKeyword } from "../../../interfaces"; +import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; +import { Observable } from "rxjs"; +import { TheMovieDbService } from "../../../services"; + +@Component({ + selector: "genre-select", + templateUrl: "genre-select.component.html" +}) +export class GenreSelectComponent implements OnInit { + constructor( + private tmdbService: TheMovieDbService + ) {} + + @Input() public form: FormGroup; + + private _mediaType: string; + @Input() set mediaType(type: string) { + this._mediaType = type; + this.tmdbService.getGenres(this._mediaType).subscribe((res) => { + this.genres = res; + this.filteredKeywords = this.control.valueChanges.pipe( + startWith(''), + map((genre: string | null) => genre ? this._filter(genre) : this.genres.slice())); + }); + + } + get mediaType(): string { + return this._mediaType; + } + public genres: IMovieDbKeyword[] = []; + public control = new FormControl(); + public filteredTags: IMovieDbKeyword[]; + public filteredKeywords: Observable; + + @ViewChild('keywordInput') input: ElementRef; + + async ngOnInit() { + + // this.genres = await this.tmdbService.getGenres(this.mediaType).toPromise(); + + + + } + + remove(word: IMovieDbKeyword): void { + const exisiting = this.form.controls.genres.value; + const index = exisiting.indexOf(word); + + if (index >= 0) { + exisiting.splice(index, 1); + this.form.controls.genres.setValue(exisiting); + } + } + + + selected(event: MatAutocompleteSelectedEvent): void { + const val = event.option.value; + const exisiting = this.form.controls.genres.value; + if(exisiting.indexOf(val) < 0) { + exisiting.push(val); + } + this.form.controls.genres.setValue(exisiting); + this.input.nativeElement.value = ''; + this.control.setValue(null); + } + + private _filter(value: string|IMovieDbKeyword): IMovieDbKeyword[] { + if (typeof value === 'object') { + const filterValue = value.name.toLowerCase(); + return this.genres.filter(g => g.name.toLowerCase().includes(filterValue)); + } else if (typeof value === 'string') { + const filterValue = value.toLowerCase(); + return this.genres.filter(g => g.name.toLowerCase().includes(filterValue)); + } + + return this.genres; + } + + +} diff --git a/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.html b/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.html new file mode 100644 index 000000000..1ff1874e9 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.html @@ -0,0 +1,37 @@ + + + + + Keywords + + + {{word.name}} + cancel + + + + + + {{word.name}} + + + + diff --git a/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.ts b/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.ts new file mode 100644 index 000000000..1ea8bcef1 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/components/keyword-search/keyword-search.component.ts @@ -0,0 +1,64 @@ +import { Component, ElementRef, Input, OnInit, ViewChild } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; +import { debounceTime, distinctUntilChanged, startWith, switchMap } from "rxjs/operators"; + +import { IMovieDbKeyword } from "../../../interfaces"; +import { MatAutocompleteSelectedEvent } from "@angular/material/autocomplete"; +import { Observable } from "rxjs"; +import { TheMovieDbService } from "../../../services"; + +@Component({ + selector: "keyword-search", + templateUrl: "keyword-search.component.html" +}) +export class KeywordSearchComponent implements OnInit { + constructor( + private tmdbService: TheMovieDbService + ) {} + + @Input() public form: FormGroup; + public control = new FormControl(); + public filteredTags: IMovieDbKeyword[]; + public filteredKeywords: Observable; + + @ViewChild('keywordInput') input: ElementRef; + + ngOnInit(): void { + + this.filteredKeywords = this.control.valueChanges.pipe( + startWith(''), + debounceTime(400), + distinctUntilChanged(), + switchMap(val => { + return this.filter(val || '') + }) + ); + } + + filter(val: string): Observable { + return this.tmdbService.getKeywords(val); + }; + + remove(word: IMovieDbKeyword): void { + const exisiting = this.form.controls.keywords.value; + const index = exisiting.indexOf(word); + + if (index >= 0) { + exisiting.splice(index, 1); + this.form.controls.keywords.setValue(exisiting); + } + } + + + selected(event: MatAutocompleteSelectedEvent): void { + const val = event.option.value; + const exisiting = this.form.controls.keywords.value; + if (exisiting.indexOf(val) < 0) { + exisiting.push(val); + } + this.form.controls.keywords.setValue(exisiting); + this.input.nativeElement.value = ''; + this.control.setValue(null); + } + +} diff --git a/src/Ombi/ClientApp/src/app/shared/shared.module.ts b/src/Ombi/ClientApp/src/app/shared/shared.module.ts index 1189320dd..a76914470 100644 --- a/src/Ombi/ClientApp/src/app/shared/shared.module.ts +++ b/src/Ombi/ClientApp/src/app/shared/shared.module.ts @@ -1,43 +1,46 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { TranslateModule } from "@ngx-translate/core"; -import { TruncateModule } from "@yellowspot/ng-truncate"; -import { MomentModule } from "ngx-moment"; -import { IssuesReportComponent } from "./issues-report.component"; - -import { SidebarModule } from "primeng/sidebar"; +import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-request-dialog.component"; +import { AdvancedSearchDialogComponent } from "./advanced-search-dialog/advanced-search-dialog.component"; +import { CommonModule } from "@angular/common"; +import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component"; +import { EpisodeRequestComponent } from "./episode-request/episode-request.component"; +import { GenreSelectComponent } from "./components/genre-select/genre-select.component"; import { InputSwitchModule } from "primeng/inputswitch"; - +import { IssuesReportComponent } from "./issues-report.component"; +import { KeywordSearchComponent } from "./components/keyword-search/keyword-search.component"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { MatButtonModule } from '@angular/material/button'; -import { MatNativeDateModule } from '@angular/material/core'; +import { MatCardModule } from "@angular/material/card"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { MatChipsModule } from "@angular/material/chips"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatExpansionModule } from "@angular/material/expansion"; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from "@angular/material/input"; import { MatListModule } from '@angular/material/list'; +import {MatMenuModule} from '@angular/material/menu'; +import { MatNativeDateModule } from '@angular/material/core'; import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import {MatRadioModule} from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSortModule } from '@angular/material/sort'; import { MatStepperModule } from '@angular/material/stepper'; import { MatTableModule } from '@angular/material/table'; -import {MatMenuModule} from '@angular/material/menu'; +import { MatTabsModule } from "@angular/material/tabs"; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTreeModule } from '@angular/material/tree'; - import { MatAutocompleteModule } from "@angular/material/autocomplete"; -import { MatCardModule } from "@angular/material/card"; -import { MatCheckboxModule } from "@angular/material/checkbox"; -import { MatChipsModule } from "@angular/material/chips"; -import { MatDialogModule } from "@angular/material/dialog"; -import { MatExpansionModule } from "@angular/material/expansion"; -import { MatInputModule } from "@angular/material/input"; -import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; -import { MatSlideToggleModule } from "@angular/material/slide-toggle"; -import { MatTabsModule } from "@angular/material/tabs"; -import { EpisodeRequestComponent } from "./episode-request/episode-request.component"; -import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component"; -import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-request-dialog.component"; +import { MomentModule } from "ngx-moment"; +import { NgModule } from "@angular/core"; +import { SidebarModule } from "primeng/sidebar"; +import { TheMovieDbService } from "../services"; +import { TranslateModule } from "@ngx-translate/core"; +import { TruncateModule } from "@yellowspot/ng-truncate"; @NgModule({ declarations: [ @@ -45,6 +48,9 @@ import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-reques EpisodeRequestComponent, DetailsGroupComponent, AdminRequestDialogComponent, + AdvancedSearchDialogComponent, + KeywordSearchComponent, + GenreSelectComponent, ], imports: [ SidebarModule, @@ -59,6 +65,7 @@ import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-reques MatAutocompleteModule, MatInputModule, MatTabsModule, + MatRadioModule, MatButtonModule, MatNativeDateModule, MatChipsModule, @@ -89,6 +96,9 @@ import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-reques IssuesReportComponent, EpisodeRequestComponent, AdminRequestDialogComponent, + AdvancedSearchDialogComponent, + GenreSelectComponent, + KeywordSearchComponent, DetailsGroupComponent, TruncateModule, InputSwitchModule, diff --git a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs index ac4bb4eea..3dacdb406 100644 --- a/src/Ombi/Controllers/V1/External/TheMovieDbController.cs +++ b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs @@ -1,7 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; -using Ombi.Attributes; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,10 +11,10 @@ using Genre = Ombi.TheMovieDbApi.Models.Genre; namespace Ombi.Controllers.External { - [Admin] [ApiV1] + [Authorize] [Produces("application/json")] - public sealed class TheMovieDbController : Controller + public sealed class TheMovieDbController : ControllerBase { public TheMovieDbController(IMovieDbApi tmdbApi) => TmdbApi = tmdbApi; @@ -25,7 +25,7 @@ namespace Ombi.Controllers.External /// /// The search term. [HttpGet("Keywords")] - public async Task> GetKeywords([FromQuery]string searchTerm) => + public async Task> GetKeywords([FromQuery]string searchTerm) => await TmdbApi.SearchKeyword(searchTerm); /// @@ -36,15 +36,15 @@ namespace Ombi.Controllers.External public async Task GetKeywords(int keywordId) { var keyword = await TmdbApi.GetKeyword(keywordId); - return keyword == null ? NotFound() : (IActionResult)Ok(keyword); + return keyword == null ? NotFound() : Ok(keyword); } /// /// Gets the genres for either Tv or Movies depending on media type /// - /// Either `tv` or `movie`. + /// Either `tv` or `movie`. [HttpGet("Genres/{media}")] public async Task> GetGenres(string media) => - await TmdbApi.GetGenres(media); + await TmdbApi.GetGenres(media, HttpContext.RequestAborted); } }