There was an error loading the image.
- } @else if (mainImage.isEnabled()) {
-
diff --git a/src/app/shared/components/recipe-card-grid/recipe-card/recipe-card.ts b/src/app/shared/components/recipe-card-grid/recipe-card/recipe-card.ts
index 3737c6c..21c6d14 100644
--- a/src/app/shared/components/recipe-card-grid/recipe-card/recipe-card.ts
+++ b/src/app/shared/components/recipe-card-grid/recipe-card/recipe-card.ts
@@ -1,12 +1,10 @@
-import { Component, computed, inject, input } from '@angular/core';
+import { Component, computed, inject, input, OnInit, signal } from '@angular/core';
import { RecipeInfoView } from '../../../models/Recipe.model';
import { RouterLink } from '@angular/router';
-import { CreateQueryOptions, injectQuery } from '@tanstack/angular-query-experimental';
import { ImageService } from '../../../services/ImageService';
import { faGlobe, faLock, faStar, faUser } from '@fortawesome/free-solid-svg-icons';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { Logo } from '../../logo/logo';
-import { ImageViewWithBlobUrl } from '../../../client-models/ImageViewWithBlobUrl';
import { Spinner } from '../../spinner/spinner';
@Component({
@@ -15,7 +13,7 @@ import { Spinner } from '../../spinner/spinner';
templateUrl: './recipe-card.html',
styleUrl: './recipe-card.css',
})
-export class RecipeCard {
+export class RecipeCard implements OnInit {
public recipe = input.required
();
protected readonly recipePageLink = computed(() => {
@@ -30,21 +28,25 @@ export class RecipeCard {
private readonly imageService = inject(ImageService);
- protected readonly mainImage = injectQuery(() => {
- const mainImageView = this.recipe().mainImage;
- let options: Partial<
- CreateQueryOptions
- > = {};
- if (mainImageView) {
- options = this.imageService.getImage(mainImageView);
- } else {
- options.enabled = false;
+ protected readonly loadingMainImage = signal(false);
+ protected readonly loadMainImageError = signal(null);
+ protected readonly mainImage = signal(null);
+
+ public ngOnInit(): void {
+ const recipe = this.recipe();
+ if (recipe.mainImage) {
+ this.loadingMainImage.set(true);
+ this.imageService.getImageBlobUrl(recipe.mainImage.owner.username, recipe.mainImage.filename).subscribe({
+ next: (blobUrl) => {
+ this.loadingMainImage.set(false);
+ this.mainImage.set(blobUrl);
+ },
+ error: (e) => {
+ this.loadingMainImage.set(false);
+ this.loadMainImageError.set(e);
+ console.error(e);
+ },
+ });
}
- return options as CreateQueryOptions<
- ImageViewWithBlobUrl,
- Error,
- ImageViewWithBlobUrl,
- [string, string, string]
- >;
- });
+ }
}
diff --git a/src/app/shared/services/ImageService.ts b/src/app/shared/services/ImageService.ts
index 4f89cd2..6b94cb1 100644
--- a/src/app/shared/services/ImageService.ts
+++ b/src/app/shared/services/ImageService.ts
@@ -1,11 +1,10 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { firstValueFrom, from, map, mergeMap, Observable, tap, toArray } from 'rxjs';
+import { firstValueFrom, from, map, mergeMap, Observable, shareReplay, toArray } from 'rxjs';
import { EndpointService } from './EndpointService';
import { ImageView } from '../models/ImageView.model';
import { SliceView } from '../models/SliceView.model';
import { QueryParams } from '../models/Query.model';
-import { QueryClient, QueryOptions, queryOptions } from '@tanstack/angular-query-experimental';
import { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl';
import { WithStringDates } from '../util';
import { ImageUpdateBody } from '../bodies/ImageUpdateBody';
@@ -31,7 +30,12 @@ export class ImageService {
private readonly httpClient = inject(HttpClient);
private readonly endpointService = inject(EndpointService);
- private readonly queryClient = inject(QueryClient);
+
+ private readonly imageBlobUrls: Map> = new Map();
+
+ private getUsernameFilenameKey(username: string, filename: string): string {
+ return username + '/' + filename;
+ }
public hydrateImageView(rawImageView: WithStringDates): ImageView {
return {
@@ -90,32 +94,23 @@ export class ImageService {
.pipe(map((res) => res.count));
}
- public getImageViewWithBlobUrl(imageView: ImageView): Promise {
- return firstValueFrom(
- this.httpClient
- .get(this.endpointService.getUrl('images', [imageView.owner.username, imageView.filename]), {
- responseType: 'blob',
- })
- .pipe(
- map((blob) => URL.createObjectURL(blob)),
- map(
- (blobUrl) =>
- ({
- ...imageView,
- blobUrl,
- }) satisfies ImageViewWithBlobUrl,
- ),
- ),
- );
+ private fetchImageViewWithBlobUrl(username: string, filename: string): Observable {
+ return this.httpClient
+ .get(this.endpointService.getUrl('images', [username, filename]), {
+ responseType: 'blob',
+ })
+ .pipe(map((blob) => URL.createObjectURL(blob)));
}
- public getImage(
- imageView: ImageView,
- ): QueryOptions {
- return queryOptions({
- queryKey: ['image-views-with-blob-urls', imageView.owner.username, imageView.filename],
- queryFn: () => this.getImageViewWithBlobUrl(imageView),
- });
+ public getImageBlobUrl(username: string, filename: string): Observable {
+ const key = this.getUsernameFilenameKey(username, filename);
+ if (!this.imageBlobUrls.has(key)) {
+ const blobUrl$ = this.fetchImageViewWithBlobUrl(username, filename).pipe(
+ shareReplay({ bufferSize: 1, refCount: false }),
+ );
+ this.imageBlobUrls.set(key, blobUrl$);
+ }
+ return this.imageBlobUrls.get(key)!;
}
public getImageView2(username: string, filename: string): Observable {
@@ -144,15 +139,7 @@ export class ImageService {
formData.append('isPublic', isPublic.toString());
}
- return firstValueFrom(
- this.httpClient.post(this.endpointService.getUrl('images'), formData).pipe(
- tap(async () => {
- await this.queryClient.invalidateQueries({
- queryKey: ['image-views'],
- });
- }),
- ),
- );
+ return firstValueFrom(this.httpClient.post(this.endpointService.getUrl('images'), formData));
}
public imageExists(username: string, filename: string): Promise {