mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Made a start on the advanced options
This commit is contained in:
parent
7119407141
commit
83ee2dae1a
18 changed files with 476 additions and 118 deletions
|
@ -26,7 +26,7 @@ namespace Ombi.Api.TheMovieDb
|
|||
Task<List<MovieDbSearchResult>> UpcomingTv(string languageCode, int? page = null);
|
||||
Task<List<MovieDbSearchResult>> SimilarMovies(int movieId, string langCode);
|
||||
Task<FindResult> Find(string externalId, ExternalSource source);
|
||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||
Task<SeasonDetails> GetSeasonEpisodes(int theMovieDbId, int seasonNumber, CancellationToken token, string langCode = "en");
|
||||
Task<TvInfo> GetTVInfo(string themoviedbid, string langCode = "en");
|
||||
Task<TheMovieDbContainer<ActorResult>> SearchByActor(string searchTerm, string langCode);
|
||||
|
@ -35,10 +35,10 @@ namespace Ombi.Api.TheMovieDb
|
|||
Task<TheMovieDbContainer<DiscoverMovies>> DiscoverMovies(string langCode, int keywordId);
|
||||
Task<FullMovieInfo> GetFullMovieInfo(int movieId, CancellationToken cancellationToken, string langCode);
|
||||
Task<Collections> GetCollection(string langCode, int collectionId, CancellationToken cancellationToken);
|
||||
Task<List<Keyword>> SearchKeyword(string searchTerm);
|
||||
Task<Keyword> GetKeyword(int keywordId);
|
||||
Task<List<TheMovidDbKeyValue>> SearchKeyword(string searchTerm);
|
||||
Task<TheMovidDbKeyValue> GetKeyword(int keywordId);
|
||||
Task<WatchProviders> GetMovieWatchProviders(int theMoviedbId, CancellationToken token);
|
||||
Task<WatchProviders> GetTvWatchProviders(int theMoviedbId, CancellationToken token);
|
||||
Task<List<Genre>> GetGenres(string media);
|
||||
Task<List<Genre>> GetGenres(string media, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
|
@ -357,34 +357,34 @@ namespace Ombi.Api.TheMovieDb
|
|||
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<List<Keyword>> SearchKeyword(string searchTerm)
|
||||
public async Task<List<TheMovidDbKeyValue>> 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<TheMovieDbContainer<Keyword>>(request);
|
||||
return result.results ?? new List<Keyword>();
|
||||
var result = await Api.Request<TheMovieDbContainer<TheMovidDbKeyValue>>(request);
|
||||
return result.results ?? new List<TheMovidDbKeyValue>();
|
||||
}
|
||||
|
||||
public async Task<Keyword> GetKeyword(int keywordId)
|
||||
public async Task<TheMovidDbKeyValue> GetKeyword(int keywordId)
|
||||
{
|
||||
var request = new Request($"keyword/{keywordId}", BaseUri, HttpMethod.Get);
|
||||
request.AddQueryString("api_key", ApiToken);
|
||||
AddRetry(request);
|
||||
|
||||
var keyword = await Api.Request<Keyword>(request);
|
||||
var keyword = await Api.Request<TheMovidDbKeyValue>(request);
|
||||
return keyword == null || keyword.Id == 0 ? null : keyword;
|
||||
}
|
||||
|
||||
public async Task<List<Genre>> GetGenres(string media)
|
||||
public async Task<List<Genre>> 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<GenreContainer<Genre>>(request);
|
||||
var result = await Api.Request<GenreContainer<Genre>>(request, cancellationToken);
|
||||
return result.genres ?? new List<Genre>();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 { ButtonModule } from "primeng/button";
|
||||
import { ConfirmDialogModule } from "primeng/confirmdialog";
|
||||
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 { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
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 { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
|
||||
|
||||
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 { 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 { 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 { 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 { 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Services
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<div class="small-middle-container" >
|
||||
|
||||
<div *ngIf="loadingFlag" class="row justify-content-md-center top-spacing loading-spinner">
|
||||
<mat-spinner [color]="'accent'"></mat-spinner>
|
||||
</div>
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
<mat-slide-toggle id="filterMusic" class="mat-menu-item slide-menu" [checked]="searchFilter.music"
|
||||
(click)="$event.stopPropagation()" (change)="changeFilter($event,SearchFilterType.Music)">
|
||||
{{ 'NavigationBar.Filter.Music' | translate}}</mat-slide-toggle>
|
||||
<button (click)="openAdvancedSearch()">Advanced Search</button>
|
||||
<!-- <mat-slide-toggle class="mat-menu-item slide-menu" [checked]="searchFilter.people"
|
||||
(click)="$event.stopPropagation()" (change)="changeFilter($event,SearchFilterType.People)">
|
||||
{{ 'NavigationBar.Filter.People' | translate}}</mat-slide-toggle> -->
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<HTMLInputElement>;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private tmdbService: TheMovieDbService,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<form [formGroup]="form" *ngIf="form">
|
||||
<h1 id="advancedOptionsTitle">
|
||||
<i class="fas fa-sliders-h"></i> Advanced Search
|
||||
</h1>
|
||||
<hr />
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="fas fa-x7 fa-exclamation-triangle glyphicon"></i>
|
||||
<span>{{ "MediaDetails.AutoApproveOptions" | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div style="max-width: 0; max-height: 0; overflow: hidden">
|
||||
<input autofocus="true" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="md-form-field">
|
||||
<mat-radio-group formControlName="type" aria-label="Select an option">
|
||||
<mat-radio-button value="movie">Movies </mat-radio-button>
|
||||
<mat-radio-button style="padding-left: 5px;" value="tv">TV Shows </mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<mat-form-field appearance="outline" floatLabel=always>
|
||||
<mat-label>Year</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">
|
||||
<genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions class="right-buttons">
|
||||
<button
|
||||
mat-raised-button
|
||||
id="cancelButton"
|
||||
[mat-dialog-close]=""
|
||||
color="warn"
|
||||
>
|
||||
<i class="fas fa-times"></i> {{ "Common.Cancel" | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
id="requestButton"
|
||||
(click)="submitRequest()"
|
||||
color="accent"
|
||||
>
|
||||
<i class="fas fa-plus"></i> {{ "Common.Request" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
@import "~styles/variables.scss";
|
||||
|
||||
.alert-info {
|
||||
background: $accent;
|
||||
border-color: $ombi-background-primary;
|
||||
color:white;
|
||||
}
|
|
@ -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<AdvancedSearchDialogComponent, string>,
|
||||
@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([]);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
<mat-form-field class="example-chip-list" appearance="fill">
|
||||
<mat-label>Genres</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip
|
||||
*ngFor="let word of form.controls.genres.value"
|
||||
[removable]="true"
|
||||
(removed)="remove(word)">
|
||||
{{word.name}}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Search Keyword"
|
||||
#keywordInput
|
||||
[formControl]="control"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
|
||||
<mat-option *ngFor="let word of filteredKeywords | async" [value]="word">
|
||||
{{word.name}}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
|
@ -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<IMovieDbKeyword[]>;
|
||||
|
||||
@ViewChild('keywordInput') input: ElementRef<HTMLInputElement>;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<!-- <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-label>Keywords</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Fruit selection">
|
||||
<mat-chip
|
||||
*ngFor="let word of form.controls.keywords.value"
|
||||
[removable]="true"
|
||||
(removed)="remove(word)">
|
||||
{{word.name}}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Search Keyword"
|
||||
#keywordInput
|
||||
[formControl]="control"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
|
||||
<mat-option *ngFor="let word of filteredKeywords | async" [value]="word">
|
||||
{{word.name}}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
|
|
@ -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<IMovieDbKeyword[]>;
|
||||
|
||||
@ViewChild('keywordInput') input: ElementRef<HTMLInputElement>;
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.filteredKeywords = this.control.valueChanges.pipe(
|
||||
startWith(''),
|
||||
debounceTime(400),
|
||||
distinctUntilChanged(),
|
||||
switchMap(val => {
|
||||
return this.filter(val || '')
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
filter(val: string): Observable<any[]> {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { MatIconModule } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
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 { 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 { 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 { 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 { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
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,
|
||||
|
|
|
@ -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
|
|||
/// </summary>
|
||||
/// <param name="searchTerm">The search term.</param>
|
||||
[HttpGet("Keywords")]
|
||||
public async Task<IEnumerable<Keyword>> GetKeywords([FromQuery]string searchTerm) =>
|
||||
public async Task<IEnumerable<TheMovidDbKeyValue>> GetKeywords([FromQuery]string searchTerm) =>
|
||||
await TmdbApi.SearchKeyword(searchTerm);
|
||||
|
||||
/// <summary>
|
||||
|
@ -36,15 +36,15 @@ namespace Ombi.Controllers.External
|
|||
public async Task<IActionResult> GetKeywords(int keywordId)
|
||||
{
|
||||
var keyword = await TmdbApi.GetKeyword(keywordId);
|
||||
return keyword == null ? NotFound() : (IActionResult)Ok(keyword);
|
||||
return keyword == null ? NotFound() : Ok(keyword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the genres for either Tv or Movies depending on media type
|
||||
/// </summary>
|
||||
/// <param name="media">Either `tv` or `movie`.</param>
|
||||
/// <param name="media">Either `tv` or `movie`.</param>
|
||||
[HttpGet("Genres/{media}")]
|
||||
public async Task<IEnumerable<Genre>> GetGenres(string media) =>
|
||||
await TmdbApi.GetGenres(media);
|
||||
await TmdbApi.GetGenres(media, HttpContext.RequestAborted);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue