mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-06 13:11:13 -07:00
feat(discover): ✨ Added infinite scroll on advanced search results
* feat(discover): ✨ Added infinite scroll on advanced search results
This commit is contained in:
parent
e00e39a1be
commit
898bc89fa7
12 changed files with 88 additions and 40 deletions
|
@ -152,15 +152,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, pagesToLoad.Page, cancellationToken);
|
||||||
//results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
||||||
//}
|
}
|
||||||
return await TransformMovieResultsToResponse(apiResult);
|
return await TransformMovieResultsToResponse(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -45,6 +45,6 @@ namespace Ombi.Api.TheMovieDb
|
||||||
Task<List<Genre>> GetGenres(string media, CancellationToken cancellationToken, string languageCode);
|
Task<List<Genre>> GetGenres(string media, CancellationToken cancellationToken, string languageCode);
|
||||||
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
|
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
|
||||||
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
|
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
|
||||||
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, CancellationToken cancellationToken);
|
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,11 @@ namespace Ombi.Api.TheMovieDb
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, CancellationToken cancellationToken)
|
public async Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new Request($"discover/{model.Type}", BaseUri, HttpMethod.Get);
|
var request = new Request($"discover/{model.Type}", BaseUri, HttpMethod.Get);
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||||
if(model.ReleaseYear.HasValue && model.ReleaseYear.Value > 1900)
|
if (model.ReleaseYear.HasValue && model.ReleaseYear.Value > 1900)
|
||||||
{
|
{
|
||||||
request.FullUri = request.FullUri.AddQueryParameter("year", model.ReleaseYear.Value.ToString());
|
request.FullUri = request.FullUri.AddQueryParameter("year", model.ReleaseYear.Value.ToString());
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,9 @@ namespace Ombi.Api.TheMovieDb
|
||||||
}
|
}
|
||||||
//request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc");
|
//request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc");
|
||||||
|
|
||||||
|
request.AddQueryString("page", page.ToString());
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +142,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
var result = await Api.Request<ActorCredits>(request);
|
var result = await Api.Request<ActorCredits>(request);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ActorCredits> GetActorTvCredits(int actorId, string langCode)
|
public async Task<ActorCredits> GetActorTvCredits(int actorId, string langCode)
|
||||||
{
|
{
|
||||||
var request = new Request($"person/{actorId}/tv_credits", BaseUri, HttpMethod.Get);
|
var request = new Request($"person/{actorId}/tv_credits", BaseUri, HttpMethod.Get);
|
||||||
|
@ -281,7 +284,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||||
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
|
return Mapper.Map<List<MovieDbSearchResult>>(result.results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<MovieDbSearchResult>> TrendingMovies(string langCode, int? page = null)
|
public Task<List<MovieDbSearchResult>> TrendingMovies(string langCode, int? page = null)
|
||||||
{
|
{
|
||||||
return Trending("movie", langCode, page);
|
return Trending("movie", langCode, page);
|
||||||
|
@ -295,7 +298,7 @@ namespace Ombi.Api.TheMovieDb
|
||||||
{
|
{
|
||||||
// https://developers.themoviedb.org/3/trending/get-trending
|
// https://developers.themoviedb.org/3/trending/get-trending
|
||||||
var timeWindow = "week"; // another option can be 'day'
|
var timeWindow = "week"; // another option can be 'day'
|
||||||
var request = new Request($"trending/{type}/{timeWindow}", BaseUri, HttpMethod.Get);
|
var request = new Request($"trending/{type}/{timeWindow}", BaseUri, HttpMethod.Get);
|
||||||
request.AddQueryString("api_key", ApiToken);
|
request.AddQueryString("api_key", ApiToken);
|
||||||
request.AddQueryString("language", langCode);
|
request.AddQueryString("language", langCode);
|
||||||
|
|
||||||
|
@ -413,8 +416,8 @@ namespace Ombi.Api.TheMovieDb
|
||||||
request.AddQueryString("language", langCode);
|
request.AddQueryString("language", langCode);
|
||||||
request.AddQueryString("sort_by", "vote_average.desc");
|
request.AddQueryString("sort_by", "vote_average.desc");
|
||||||
|
|
||||||
request.AddQueryString("with_keywords", keywordId);
|
request.AddQueryString("with_keywords", keywordId);
|
||||||
|
|
||||||
// `vote_count` consideration isn't explicitly documented, but using only the `sort_by` filter
|
// `vote_count` consideration isn't explicitly documented, but using only the `sort_by` filter
|
||||||
// does not provide the same results as `/movie/top_rated`. This appears to be adequate enough
|
// does not provide the same results as `/movie/top_rated`. This appears to be adequate enough
|
||||||
// to filter out extremely high-rated movies due to very little votes
|
// to filter out extremely high-rated movies due to very little votes
|
||||||
|
@ -530,7 +533,8 @@ namespace Ombi.Api.TheMovieDb
|
||||||
var settings = await Settings;
|
var settings = await Settings;
|
||||||
List<int> excludedGenres;
|
List<int> excludedGenres;
|
||||||
|
|
||||||
switch (media_type) {
|
switch (media_type)
|
||||||
|
{
|
||||||
case "tv":
|
case "tv":
|
||||||
excludedGenres = settings.ExcludedTvGenreIds;
|
excludedGenres = settings.ExcludedTvGenreIds;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"ng2-cookies": "^1.0.12",
|
"ng2-cookies": "^1.0.12",
|
||||||
"ngx-clipboard": "^12.1.0",
|
"ngx-clipboard": "^12.1.0",
|
||||||
"ngx-infinite-scroll": "^9.0.0",
|
"ngx-infinite-scroll": "^14.0.0",
|
||||||
"ngx-moment": "^3.0.1",
|
"ngx-moment": "^3.0.1",
|
||||||
"ngx-order-pipe": "^2.2.0",
|
"ngx-order-pipe": "^2.2.0",
|
||||||
"popper.js": "^1.14.3",
|
"popper.js": "^1.14.3",
|
||||||
|
|
|
@ -2,7 +2,13 @@
|
||||||
<div *ngIf="loadingFlag" class="row justify-content-md-center top-spacing loading-spinner">
|
<div *ngIf="loadingFlag" class="row justify-content-md-center top-spacing loading-spinner">
|
||||||
<mat-spinner [color]="'accent'"></mat-spinner>
|
<mat-spinner [color]="'accent'"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="discoverResults.length > 0" class="row full-height discoverResults col" >
|
|
||||||
|
<div *ngIf="discoverResults.length > 0"
|
||||||
|
class="row full-height discoverResults col"
|
||||||
|
infiniteScroll
|
||||||
|
[infiniteScrollDistance]="3"
|
||||||
|
[infiniteScrollThrottle]="200"
|
||||||
|
(scrolled)="onScroll()">
|
||||||
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
|
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
|
||||||
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
public filter: SearchFilter;
|
public filter: SearchFilter;
|
||||||
|
|
||||||
private isAdvancedSearch: boolean;
|
private isAdvancedSearch: boolean;
|
||||||
|
private loadPosition: number = 30;
|
||||||
|
|
||||||
constructor(private searchService: SearchV2Service,
|
constructor(private searchService: SearchV2Service,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -65,7 +66,7 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.advancedDataService) {
|
if (this.isAdvancedSearch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loadingFlag = true;
|
this.loadingFlag = true;
|
||||||
|
@ -179,6 +180,31 @@ export class DiscoverSearchResultsComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onScroll() {
|
||||||
|
console.log("scrolled");
|
||||||
|
if (this.advancedDataService) {
|
||||||
|
this.loadMoreAdvancedSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadMoreAdvancedSearch() {
|
||||||
|
const advancedOptions = this.advancedDataService.getOptions();
|
||||||
|
|
||||||
|
this.searchService.advancedSearch({
|
||||||
|
type: advancedOptions.type == RequestType.movie ? "movie" : "tv",
|
||||||
|
companies: advancedOptions.companies,
|
||||||
|
genreIds: advancedOptions.genres,
|
||||||
|
keywordIds : advancedOptions.keywords,
|
||||||
|
releaseYear: advancedOptions.releaseYear,
|
||||||
|
watchProviders: advancedOptions.watchProviders,
|
||||||
|
}, this.loadPosition, 30).then(x => {
|
||||||
|
|
||||||
|
this.loadPosition += 30;
|
||||||
|
this.mapAdvancedData(x);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async search() {
|
private async search() {
|
||||||
this.clear();
|
this.clear();
|
||||||
this.results = await this.searchService
|
this.results = await this.searchService
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class TvSearchComponent implements OnInit {
|
||||||
private notificationService: NotificationService, private authService: AuthService,
|
private notificationService: NotificationService, private authService: AuthService,
|
||||||
private imageService: ImageService, private sanitizer: DomSanitizer,
|
private imageService: ImageService, private sanitizer: DomSanitizer,
|
||||||
@Inject(APP_BASE_HREF) href:string) {
|
@Inject(APP_BASE_HREF) href:string) {
|
||||||
this.href = href;
|
this.href = href;
|
||||||
this.searchChanged.pipe(
|
this.searchChanged.pipe(
|
||||||
debounceTime(600), // Wait Xms after the last event before emitting last event
|
debounceTime(600), // Wait Xms after the last event before emitting last event
|
||||||
distinctUntilChanged(), // only emit if value is different from previous value
|
distinctUntilChanged(), // only emit if value is different from previous value
|
||||||
|
|
|
@ -8,7 +8,9 @@ import { RequestType } from "../../interfaces";
|
||||||
export class AdvancedSearchDialogDataService {
|
export class AdvancedSearchDialogDataService {
|
||||||
|
|
||||||
@Output() public onDataChange = new EventEmitter<any>();
|
@Output() public onDataChange = new EventEmitter<any>();
|
||||||
|
@Output() public onOptionsChange = new EventEmitter<any>();
|
||||||
private _data: any;
|
private _data: any;
|
||||||
|
private _options: any;
|
||||||
private _type: RequestType;
|
private _type: RequestType;
|
||||||
|
|
||||||
setData(data: any, type: RequestType) {
|
setData(data: any, type: RequestType) {
|
||||||
|
@ -17,10 +19,30 @@ export class AdvancedSearchDialogDataService {
|
||||||
this.onDataChange.emit(this._data);
|
this.onDataChange.emit(this._data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOptions(watchProviders: number[], genres: number[], keywords: number[], releaseYear: number, type: RequestType, position: number) {
|
||||||
|
this._options = {
|
||||||
|
watchProviders,
|
||||||
|
genres,
|
||||||
|
keywords,
|
||||||
|
releaseYear,
|
||||||
|
type,
|
||||||
|
position
|
||||||
|
};
|
||||||
|
this.onOptionsChange.emit(this._options);
|
||||||
|
}
|
||||||
|
|
||||||
getData(): any {
|
getData(): any {
|
||||||
return this._data;
|
return this._data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOptions(): any {
|
||||||
|
return this._options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoaded(): number {
|
||||||
|
return this._options.loaded;
|
||||||
|
}
|
||||||
|
|
||||||
getType(): RequestType {
|
getType(): RequestType {
|
||||||
return this._type;
|
return this._type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<mat-label>{{ "Search.YearOfRelease" | translate }}</mat-label>
|
|
||||||
<genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select>
|
<genre-select [form]="form" [mediaType]="form.controls.type.value"></genre-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,9 @@ export class AdvancedSearchDialogComponent implements OnInit {
|
||||||
type: formData.type,
|
type: formData.type,
|
||||||
}, 0, 30);
|
}, 0, 30);
|
||||||
|
|
||||||
this.advancedSearchDialogService.setData(data, formData.type === 'movie' ? RequestType.movie : RequestType.tvShow);
|
const type = formData.type === 'movie' ? RequestType.movie : RequestType.tvShow;
|
||||||
|
this.advancedSearchDialogService.setData(data, type);
|
||||||
|
this.advancedSearchDialogService.setOptions(watchProviderIds, genres, keywords, formData.releaseYear, type, 30);
|
||||||
|
|
||||||
this.dialogRef.close(true);
|
this.dialogRef.close(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1902,11 +1902,6 @@
|
||||||
node-gyp "^8.4.1"
|
node-gyp "^8.4.1"
|
||||||
read-package-json-fast "^2.0.3"
|
read-package-json-fast "^2.0.3"
|
||||||
|
|
||||||
"@scarf/scarf@^1.1.0":
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.1.tgz#d8b9f20037b3a37dbf8dcdc4b3b72f9285bfce35"
|
|
||||||
integrity sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==
|
|
||||||
|
|
||||||
"@schematics/angular@14.0.0":
|
"@schematics/angular@14.0.0":
|
||||||
version "14.0.0"
|
version "14.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-14.0.0.tgz#de6cb4c86586ed5b06adfd7a759cc9908e627787"
|
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-14.0.0.tgz#de6cb4c86586ed5b06adfd7a759cc9908e627787"
|
||||||
|
@ -5619,13 +5614,12 @@ ngx-clipboard@^12.1.0:
|
||||||
ngx-window-token "^2.0.0"
|
ngx-window-token "^2.0.0"
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
ngx-infinite-scroll@^9.0.0:
|
ngx-infinite-scroll@^14.0.0:
|
||||||
version "9.1.0"
|
version "14.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-9.1.0.tgz#6716a47613ff59f236b85c3ce291b2fd57106824"
|
resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-14.0.0.tgz#395b15be5f451c3e3d2ad7ce2aeb66f8c66aba5d"
|
||||||
integrity sha512-ZulbahgFsoPmP8cz7qPGDeFX9nKiSm74aav8vXNSI1ZoPiGYY5FQd8AK+yXqygY7tyCJRyt8Wp3DIg7zgP5dPA==
|
integrity sha512-YZB5PBPXSERNtCGQRZTVflbgkh5asp01NPfC8KPItemmQik1Ip8ZCCbcyHA77TDTdilmaiu8TbguA3geg/LMWw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@scarf/scarf" "^1.1.0"
|
tslib "^2.3.0"
|
||||||
opencollective-postinstall "^2.0.2"
|
|
||||||
|
|
||||||
ngx-moment@^3.0.1:
|
ngx-moment@^3.0.1:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
|
@ -5901,11 +5895,6 @@ open@8.4.0, open@^8.0.9:
|
||||||
is-docker "^2.1.1"
|
is-docker "^2.1.1"
|
||||||
is-wsl "^2.2.0"
|
is-wsl "^2.2.0"
|
||||||
|
|
||||||
opencollective-postinstall@^2.0.2:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
|
|
||||||
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
|
|
||||||
|
|
||||||
ora@5.4.1, ora@^5.4.1:
|
ora@5.4.1, ora@^5.4.1:
|
||||||
version "5.4.1"
|
version "5.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
|
resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
|
||||||
|
|
|
@ -25,14 +25,14 @@
|
||||||
{
|
{
|
||||||
"episodeNumber": 1,
|
"episodeNumber": 1,
|
||||||
"title": "Our Cup Runneth Over",
|
"title": "Our Cup Runneth Over",
|
||||||
"airDate": "2015-01-14T01:00:00+00:00",
|
"airDate": "2015-01-13T00:00:00",
|
||||||
"url": "https://www.tvmaze.com/episodes/153107/schitts-creek-1x01-our-cup-runneth-over",
|
"url": "https://www.tvmaze.com/episodes/153107/schitts-creek-1x01-our-cup-runneth-over",
|
||||||
"available": false,
|
"available": false,
|
||||||
"approved": false,
|
"approved": false,
|
||||||
"requested": false,
|
"requested": false,
|
||||||
"seasonId": 0,
|
"seasonId": 0,
|
||||||
"season": null,
|
"season": null,
|
||||||
"airDateDisplay": "01/14/2015 01:00:00",
|
"airDateDisplay": "01/13/2015 00:00:00",
|
||||||
"requestStatus": "",
|
"requestStatus": "",
|
||||||
"id": 0
|
"id": 0
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue