From 9524608c1fc9992df043e58fb975a2116a4263bb Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 23 Aug 2025 21:31:26 +0100 Subject: [PATCH] modernise the discover --- .../carousel-list.component.html | 38 +++--- .../carousel-list/carousel-list.component.ts | 120 ++++++++++-------- .../discover/discover.component.html | 92 ++++++++------ .../components/discover/discover.component.ts | 33 +++-- 4 files changed, 157 insertions(+), 126 deletions(-) diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html index f8dbaf257..a8e2b9c8d 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html @@ -1,21 +1,25 @@ -
- - {{'Discovery.Combined' | translate}} - {{'Discovery.Movies' | translate}} - {{'Discovery.Tv' | translate}} - -
-@defer (when discoverResults.length > 0; prefetch on idle) { - - - - - +@if (discoverType() !== DiscoverType.Seasonal) { +
+ + {{'Discovery.Combined' | translate}} + {{'Discovery.Movies' | translate}} + {{'Discovery.Tv' | translate}} + +
} -@placeholder(minimum 300) { + +@defer (when hasResults(); prefetch on idle) { + + + + + +} @placeholder(minimum 300) {
-
- -
+ @for (item of [1,2,3,4,5,6,7,8,9,10]; track item) { +
+ +
+ }
} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts index 12c81e894..f557f1969 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, ViewChild, Output, EventEmitter, Inject } from "@angular/core"; +import { Component, ViewChild, Inject, input, output, signal, computed, inject, ChangeDetectionStrategy } from "@angular/core"; import { DiscoverOption, IDiscoverCardResult } from "../../interfaces"; import { ISearchMovieResult, ISearchTvResult, RequestType } from "../../../interfaces"; import { SearchV2Service } from "../../../services"; @@ -17,46 +17,55 @@ export enum DiscoverType { } @Component({ - standalone: false, + standalone: false, selector: "carousel-list", templateUrl: "./carousel-list.component.html", styleUrls: ["./carousel-list.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class CarouselListComponent implements OnInit { - - @Input() public discoverType: DiscoverType; - @Input() public id: string; - @Input() public isAdmin: boolean; - @Output() public movieCount: EventEmitter = new EventEmitter(); +export class CarouselListComponent { + // Inputs using new input() function + public discoverType = input.required(); + public id = input.required(); + public isAdmin = input(false); + + // Output using new output() function + public movieCount = output(); + @ViewChild('carousel', {static: false}) carousel: Carousel; + // Services using inject() function + private searchService = inject(SearchV2Service); + private storageService = inject(StorageService); + private featureFacade = inject(FeaturesFacade); + private baseUrl = inject(APP_BASE_HREF); + + // Public constants public DiscoverOption = DiscoverOption; - public discoverOptions: DiscoverOption = DiscoverOption.Combined; - public discoverResults: IDiscoverCardResult[] = []; - public movies: ISearchMovieResult[] = []; - public tvShows: ISearchTvResult[] = []; - public responsiveOptions: any; public RequestType = RequestType; - public loadingFlag: boolean; public DiscoverType = DiscoverType; - public is4kEnabled = false; + + // State using signals + public discoverOptions = signal(DiscoverOption.Combined); + public discoverResults = signal([]); + public movies = signal([]); + public tvShows = signal([]); + public loadingFlag = signal(false); + public is4kEnabled = signal(false); + + // Computed properties + public hasResults = computed(() => this.discoverResults().length > 0); + public totalResults = computed(() => this.discoverResults().length); get mediaTypeStorageKey() { - return "DiscoverOptions" + this.discoverType.toString(); + return "DiscoverOptions" + this.discoverType().toString(); }; + private amountToLoad = 10; private currentlyLoaded = 0; - private baseUrl: string = ""; + public responsiveOptions: any; - - constructor(private searchService: SearchV2Service, - private storageService: StorageService, - private featureFacade: FeaturesFacade, - @Inject(APP_BASE_HREF) private href: string) { - - if (this.href.length > 1) { - this.baseUrl = this.href; - } + constructor() { Carousel.prototype.onTouchMove = () => { }, this.responsiveOptions = [ @@ -149,12 +158,14 @@ export class CarouselListComponent implements OnInit { } public async ngOnInit() { - - this.is4kEnabled = this.featureFacade.is4kEnabled(); + // Initialize 4K feature flag + this.is4kEnabled.set(this.featureFacade.is4kEnabled()); this.currentlyLoaded = 0; + + // Load saved discover options from storage const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey); if (localDiscoverOptions) { - this.discoverOptions = DiscoverOption[DiscoverOption[localDiscoverOptions]]; + this.discoverOptions.set(DiscoverOption[DiscoverOption[localDiscoverOptions]]); } // Load initial data - just enough to fill the first carousel page @@ -162,10 +173,9 @@ export class CarouselListComponent implements OnInit { await this.loadData(false); // If we don't have enough results to fill the carousel, load one more batch - if (this.discoverResults.length < 10) { + if (this.discoverResults().length < 10) { await this.loadData(false); } - } public async toggleChanged(event: MatButtonToggleChange) { @@ -183,7 +193,7 @@ export class CarouselListComponent implements OnInit { if (end) { var moviePromise: Promise; var tvPromise: Promise; - switch (+this.discoverOptions) { + switch (+this.discoverOptions()) { case DiscoverOption.Combined: moviePromise = this.loadMovies(); tvPromise = this.loadTv(); @@ -205,7 +215,7 @@ export class CarouselListComponent implements OnInit { private async loadData(clearExisting: boolean = true) { var moviePromise: Promise; var tvPromise: Promise; - switch (+this.discoverOptions) { + switch (+this.discoverOptions()) { case DiscoverOption.Combined: moviePromise = this.loadMovies(); tvPromise = this.loadTv(); @@ -223,50 +233,50 @@ export class CarouselListComponent implements OnInit { } private async switchDiscoverMode(newMode: DiscoverOption) { - if (this.discoverOptions === newMode) { + if (this.discoverOptions() === newMode) { return; } this.loading(); this.currentlyLoaded = 0; - this.discoverOptions = +newMode; + this.discoverOptions.set(+newMode); this.storageService.save(this.mediaTypeStorageKey, newMode.toString()); await this.loadData(); this.finishLoading(); } private async loadMovies() { - switch (this.discoverType) { + switch (this.discoverType()) { case DiscoverType.Popular: - this.movies = await this.searchService.popularMoviesByPage(this.currentlyLoaded, this.amountToLoad); + this.movies.set(await this.searchService.popularMoviesByPage(this.currentlyLoaded, this.amountToLoad)); break; case DiscoverType.Trending: - this.movies = await this.searchService.nowPlayingMoviesByPage(this.currentlyLoaded, this.amountToLoad); + this.movies.set(await this.searchService.nowPlayingMoviesByPage(this.currentlyLoaded, this.amountToLoad)); break; case DiscoverType.Upcoming: - this.movies = await this.searchService.upcomingMoviesByPage(this.currentlyLoaded, this.amountToLoad); - break + this.movies.set(await this.searchService.upcomingMoviesByPage(this.currentlyLoaded, this.amountToLoad)); + break; case DiscoverType.RecentlyRequested: - this.movies = await this.searchService.recentlyRequestedMoviesByPage(this.currentlyLoaded, this.amountToLoad); + this.movies.set(await this.searchService.recentlyRequestedMoviesByPage(this.currentlyLoaded, this.amountToLoad)); break; case DiscoverType.Seasonal: - this.movies = await this.searchService.seasonalMoviesByPage(this.currentlyLoaded, this.amountToLoad); + this.movies.set(await this.searchService.seasonalMoviesByPage(this.currentlyLoaded, this.amountToLoad)); break; } - this.movieCount.emit(this.movies.length); + this.movieCount.emit(this.movies().length); this.currentlyLoaded += this.amountToLoad; } private async loadTv() { - switch (this.discoverType) { + switch (this.discoverType()) { case DiscoverType.Popular: - this.tvShows = await this.searchService.popularTvByPage(this.currentlyLoaded, this.amountToLoad); + this.tvShows.set(await this.searchService.popularTvByPage(this.currentlyLoaded, this.amountToLoad)); break; case DiscoverType.Trending: - this.tvShows = await this.searchService.trendingTvByPage(this.currentlyLoaded, this.amountToLoad); + this.tvShows.set(await this.searchService.trendingTvByPage(this.currentlyLoaded, this.amountToLoad)); break; case DiscoverType.Upcoming: - this.tvShows = await this.searchService.anticipatedTvByPage(this.currentlyLoaded, this.amountToLoad); - break + this.tvShows.set(await this.searchService.anticipatedTvByPage(this.currentlyLoaded, this.amountToLoad)); + break; case DiscoverType.RecentlyRequested: // this.tvShows = await this.searchService.recentlyRequestedMoviesByPage(this.currentlyLoaded, this.amountToLoad); // TODO need to do some more mapping break; @@ -284,7 +294,7 @@ export class CarouselListComponent implements OnInit { private createModel() { const tempResults = []; - switch (+this.discoverOptions) { + switch (+this.discoverOptions()) { case DiscoverOption.Combined: tempResults.push(...this.mapMovieModel()); tempResults.push(...this.mapTvModel()); @@ -298,14 +308,14 @@ export class CarouselListComponent implements OnInit { break; } - this.discoverResults.push(...tempResults); + this.discoverResults.update(current => [...current, ...tempResults]); this.finishLoading(); } private mapMovieModel(): IDiscoverCardResult[] { const tempResults = []; - this.movies.forEach(m => { + this.movies().forEach(m => { tempResults.push({ available: m.available, posterPath: m.posterPath ? `https://image.tmdb.org/t/p/w500/${m.posterPath}` : this.baseUrl + "/images/default_movie_poster.png", @@ -327,7 +337,7 @@ export class CarouselListComponent implements OnInit { private mapTvModel(): IDiscoverCardResult[] { const tempResults = []; - this.tvShows.forEach(m => { + this.tvShows().forEach(m => { tempResults.push({ available: m.fullyAvailable, posterPath: m.backdropPath ? `https://image.tmdb.org/t/p/w500/${m.backdropPath}` : this.baseUrl + "/images/default_tv_poster.png", @@ -348,7 +358,7 @@ export class CarouselListComponent implements OnInit { } private clear() { - this.discoverResults = []; + this.discoverResults.set([]); } private shuffle(discover: IDiscoverCardResult[]): IDiscoverCardResult[] { @@ -360,11 +370,11 @@ export class CarouselListComponent implements OnInit { } private loading() { - this.loadingFlag = true; + this.loadingFlag.set(true); } private finishLoading() { - this.loadingFlag = false; + this.loadingFlag.set(false); } diff --git a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html index dc4e33be1..ee66ec749 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html @@ -22,50 +22,58 @@

{{ 'Discovery.RecentlyRequestedTab' | translate }}

-
- -
+ @for (item of [1,2,3,4,5]; track item) { +
+ +
+ }
} - @defer (on viewport; prefetch on idle) { -
-

{{ 'Discovery.SeasonalTab' | translate }}

-
- -
-
- } @placeholder(minimum 300) { -
-

{{ 'Discovery.SeasonalTab' | translate }}

-
-
- + @if (showSeasonal()) { + @defer (on viewport; prefetch on idle) { +
+

{{ 'Discovery.SeasonalTab' | translate }}

+
+
-
+ } @placeholder(minimum 300) { +
+

{{ 'Discovery.SeasonalTab' | translate }}

+
+ @for (item of [1,2,3,4,5,6,7,8,9,10]; track item) { +
+ +
+ } +
+
+ } } @defer (on viewport; prefetch on idle) {

{{ 'Discovery.PopularTab' | translate }}

-
- -
+
+ +
} @placeholder(minimum 300) {

{{ 'Discovery.PopularTab' | translate }}

-
- -
+ @for (item of [1,2,3,4,5,6,7,8,9,10]; track item) { +
+ +
+ }
} @@ -73,17 +81,19 @@ @defer (on viewport; prefetch on idle) {

{{ 'Discovery.TrendingTab' | translate }}

-
- -
+
+ +
} @placeholder(minimum 300) {

{{ 'Discovery.TrendingTab' | translate }}

-
- -
+ @for (item of [1,2,3,4,5,6,7,8,9,10]; track item) { +
+ +
+ }
} @@ -91,17 +101,19 @@ @defer (on viewport; prefetch on idle) {

{{ 'Discovery.UpcomingTab' | translate }}

-
- -
+
+ +
} @placeholder(minimum 300) {

{{ 'Discovery.UpcomingTab' | translate }}

-
- -
+ @for (item of [1,2,3,4,5,6,7,8,9,10]; track item) { +
+ +
+ }
} diff --git a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.ts b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.ts index ec50334c6..17b56443d 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.ts @@ -1,29 +1,34 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, computed, inject, signal, ChangeDetectionStrategy } from "@angular/core"; import { AuthService } from "../../../auth/auth.service"; import { DiscoverType } from "../carousel-list/carousel-list.component"; @Component({ - standalone: false, + standalone: false, templateUrl: "./discover.component.html", styleUrls: ["./discover.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class DiscoverComponent implements OnInit { - +export class DiscoverComponent { + // Services using inject() function + private authService = inject(AuthService); + // Public constants public DiscoverType = DiscoverType; - public isAdmin: boolean; - public showSeasonal: boolean; + + // State using signals + public isAdmin = signal(false); + public seasonalMovieCount = signal(0); + + // Computed properties + public showSeasonal = computed(() => this.seasonalMovieCount() > 0); - constructor(private authService: AuthService) { } - - public ngOnInit(): void { - this.isAdmin = this.authService.isAdmin(); + constructor() { + // Initialize admin status + this.isAdmin.set(this.authService.isAdmin()); } - public setSeasonalMovieCount(count: number) { - if (count > 0) { - this.showSeasonal = true; - } + public setSeasonalMovieCount(count: number): void { + this.seasonalMovieCount.set(count); } }