diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 9317cf6..b0671c6 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -12,6 +12,15 @@ export const appConfig: ApplicationConfig = { provideBrowserGlobalErrorListeners(), provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), - provideTanStackQuery(new QueryClient(), withDevtools()), + provideTanStackQuery( + new QueryClient({ + defaultOptions: { + queries: { + experimental_prefetchInRender: true, + }, + }, + }), + withDevtools(), + ), ], }; diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.html b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.html index 1b8a264..934e178 100644 --- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.html +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.html @@ -1,17 +1,23 @@ -
- - Filename - - - - Alt - - - - Caption - - - -
+ @if (imageViewQuery.isLoading()) { + + } @else if (imageViewQuery.isError()) { +

There was an error.

+ } @else { +
+ + Filename + + + + Alt + + + + Caption + + + +
+ }
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.ts index ac94769..58836b1 100644 --- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.ts +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.ts @@ -5,18 +5,27 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula import { ImageView } from '../../../../../../shared/models/ImageView.model'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatButton } from '@angular/material/button'; +import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental'; import { ImageService } from '../../../../../../shared/services/ImageService'; +import { notNullOrUndefined } from '../../../../../../shared/util'; +import { Spinner } from '../../../../../../shared/components/spinner/spinner'; @Component({ selector: 'app-edit-image-dialog', - imports: [DialogContainer, MatFormField, MatLabel, MatInput, ReactiveFormsModule, MatButton], + imports: [DialogContainer, MatFormField, MatLabel, MatInput, ReactiveFormsModule, MatButton, Spinner], templateUrl: './edit-image-dialog.html', styleUrl: './edit-image-dialog.css', }) export class EditImageDialog implements OnInit { - protected readonly imageView: ImageView = inject(MAT_DIALOG_DATA); - private readonly imageService = inject(ImageService); + private readonly usernameFilename: [username: string, filename: string] = inject(MAT_DIALOG_DATA); private readonly dialogRef = inject(MatDialogRef); + private readonly imageService = inject(ImageService); + + protected readonly imageViewQuery = injectQuery(() => + this.imageService.getImageView(this.usernameFilename[0], this.usernameFilename[1]), + ); + + private readonly imageViewMutation = injectMutation(() => this.imageService.updateImage2()); protected readonly imageForm = new FormGroup({ filename: new FormControl( @@ -31,19 +40,29 @@ export class EditImageDialog implements OnInit { }); public ngOnInit(): void { - this.imageForm.patchValue({ - filename: this.imageView.filename, - alt: this.imageView.alt, - caption: this.imageView.alt, + this.imageViewQuery.promise().then((imageView) => { + this.imageForm.patchValue({ + filename: imageView.filename, + alt: imageView.alt, + caption: imageView.caption, + }); }); } public async onSubmit(event: SubmitEvent): Promise { event.preventDefault(); - await this.imageService.updateImage(this.imageView.owner.username, this.imageView.filename, { - alt: this.imageForm.value.alt, - caption: this.imageForm.value.caption, + const imageView: ImageView = this.imageViewQuery.data()!; + const formValue = this.imageForm.value; + if (notNullOrUndefined(formValue.alt)) { + imageView.alt = formValue.alt; + } + if (notNullOrUndefined(formValue.caption)) { + imageView.caption = formValue.caption; + } + this.imageViewMutation.mutate(imageView, { + onSuccess: () => { + this.dialogRef.close(); + }, }); - this.dialogRef.close(); } } diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.ts index 9c7efe6..a8d6ce6 100644 --- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.ts +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.ts @@ -105,7 +105,7 @@ export class ImageSelect { protected editImage(imageView: ImageView): void { this.dialog.open(EditImageDialog, { - data: imageView, + data: [imageView.owner.username, imageView.filename], }); } diff --git a/src/app/shared/models/ImageView.model.ts b/src/app/shared/models/ImageView.model.ts index c155026..fe5e021 100644 --- a/src/app/shared/models/ImageView.model.ts +++ b/src/app/shared/models/ImageView.model.ts @@ -1,10 +1,15 @@ import { ResourceOwner } from './ResourceOwner.model'; export interface ImageView { - alt: string; - filename: string; - height: number | null; - owner: ResourceOwner; url: string; + created: Date; + modified?: Date | null; + filename: string; + mimeType: string; + alt?: string | null; + caption?: string | null; + owner: ResourceOwner; + isPublic?: boolean; + height: number | null; width: number | null; } diff --git a/src/app/shared/services/ImageService.ts b/src/app/shared/services/ImageService.ts index 7122981..5a23926 100644 --- a/src/app/shared/services/ImageService.ts +++ b/src/app/shared/services/ImageService.ts @@ -5,8 +5,9 @@ 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 { mutationOptions, QueryClient, QueryOptions, queryOptions } from '@tanstack/angular-query-experimental'; import { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl'; +import { WithStringDates } from '../util'; @Injectable({ providedIn: 'root', @@ -31,6 +32,14 @@ export class ImageService { private readonly endpointService = inject(EndpointService); private readonly queryClient = inject(QueryClient); + public hydrateImageView(rawImageView: WithStringDates): ImageView { + return { + ...rawImageView, + created: new Date(rawImageView.created), + modified: rawImageView.modified ? new Date(rawImageView.modified) : undefined, + }; + } + public getOwnedImages(queryParams?: QueryParams): Promise> { return firstValueFrom( this.httpClient.get>(this.endpointService.getUrl('images', [], queryParams)), @@ -65,6 +74,20 @@ export class ImageService { }); } + public getImageView(username: string, filename: string) { + return queryOptions({ + queryKey: ['image-views', username, filename], + queryFn: () => + firstValueFrom( + this.httpClient + .get< + WithStringDates + >(this.endpointService.getUrl('images', [username, filename, 'view'])) + .pipe(map((rawImageView) => this.hydrateImageView(rawImageView))), + ), + }); + } + public uploadImage( image: File, filename?: string, @@ -144,4 +167,22 @@ export class ImageService { ), ); } + + public updateImage2() { + return mutationOptions({ + mutationKey: ['image-views'], + mutationFn: (imageView: ImageView) => + firstValueFrom( + this.httpClient.put( + this.endpointService.getUrl('images', [imageView.owner.username, imageView.filename]), + imageView, + ), + ), + onSuccess: async () => { + await this.queryClient.invalidateQueries({ + queryKey: ['image-views'], + }); + }, + }); + } } diff --git a/src/app/shared/services/RecipeDraftService.ts b/src/app/shared/services/RecipeDraftService.ts index f0a1ef2..5745819 100644 --- a/src/app/shared/services/RecipeDraftService.ts +++ b/src/app/shared/services/RecipeDraftService.ts @@ -9,6 +9,7 @@ import { WithStringDates } from '../util'; import { Recipe } from '../models/Recipe.model'; import { ImageView } from '../models/ImageView.model'; import { SetImageBody } from '../models/SetImageBody'; +import { ImageService } from './ImageService'; @Injectable({ providedIn: 'root', @@ -16,12 +17,14 @@ import { SetImageBody } from '../models/SetImageBody'; export class RecipeDraftService { private readonly http = inject(HttpClient); private readonly endpointService = inject(EndpointService); + private readonly imageService = inject(ImageService); private hydrateView(rawView: WithStringDates): RecipeDraftViewModel { return { ...rawView, created: new Date(rawView.created), modified: rawView.modified ? new Date(rawView.modified) : undefined, + mainImage: rawView.mainImage ? this.imageService.hydrateImageView(rawView.mainImage) : undefined, lastInference: rawView.lastInference ? { ...rawView.lastInference, diff --git a/src/app/shared/util.ts b/src/app/shared/util.ts index 3208242..4553882 100644 --- a/src/app/shared/util.ts +++ b/src/app/shared/util.ts @@ -33,3 +33,7 @@ export type WithStringDates = { ? WithStringDates | null | undefined : T[K]; }; + +export const notNullOrUndefined = (t: T | null | undefined): t is T => { + return t !== null && t !== undefined; +};