MME-8 Refactoring queries/mutations to solve stale data in image-edit dialog. TODO: clean up/refactoring.
This commit is contained in:
parent
7c483ba5f6
commit
7d56c6e17c
@ -12,6 +12,15 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient(withInterceptors([authInterceptor])),
|
provideHttpClient(withInterceptors([authInterceptor])),
|
||||||
provideTanStackQuery(new QueryClient(), withDevtools()),
|
provideTanStackQuery(
|
||||||
|
new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
experimental_prefetchInRender: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
withDevtools(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
<app-dialog-container title="Edit Image">
|
<app-dialog-container title="Edit Image">
|
||||||
|
@if (imageViewQuery.isLoading()) {
|
||||||
|
<app-spinner></app-spinner>
|
||||||
|
} @else if (imageViewQuery.isError()) {
|
||||||
|
<p>There was an error.</p>
|
||||||
|
} @else {
|
||||||
<form (submit)="onSubmit($event)">
|
<form (submit)="onSubmit($event)">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Filename</mat-label>
|
<mat-label>Filename</mat-label>
|
||||||
@ -14,4 +19,5 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button type="submit" matButton="filled">Submit</button>
|
<button type="submit" matButton="filled">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
</app-dialog-container>
|
</app-dialog-container>
|
||||||
|
|||||||
@ -5,18 +5,27 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula
|
|||||||
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
import { ImageView } from '../../../../../../shared/models/ImageView.model';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatButton } from '@angular/material/button';
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
|
||||||
import { ImageService } from '../../../../../../shared/services/ImageService';
|
import { ImageService } from '../../../../../../shared/services/ImageService';
|
||||||
|
import { notNullOrUndefined } from '../../../../../../shared/util';
|
||||||
|
import { Spinner } from '../../../../../../shared/components/spinner/spinner';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-image-dialog',
|
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',
|
templateUrl: './edit-image-dialog.html',
|
||||||
styleUrl: './edit-image-dialog.css',
|
styleUrl: './edit-image-dialog.css',
|
||||||
})
|
})
|
||||||
export class EditImageDialog implements OnInit {
|
export class EditImageDialog implements OnInit {
|
||||||
protected readonly imageView: ImageView = inject(MAT_DIALOG_DATA);
|
private readonly usernameFilename: [username: string, filename: string] = inject(MAT_DIALOG_DATA);
|
||||||
private readonly imageService = inject(ImageService);
|
|
||||||
private readonly dialogRef = inject(MatDialogRef);
|
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({
|
protected readonly imageForm = new FormGroup({
|
||||||
filename: new FormControl(
|
filename: new FormControl(
|
||||||
@ -31,19 +40,29 @@ export class EditImageDialog implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
this.imageViewQuery.promise().then((imageView) => {
|
||||||
this.imageForm.patchValue({
|
this.imageForm.patchValue({
|
||||||
filename: this.imageView.filename,
|
filename: imageView.filename,
|
||||||
alt: this.imageView.alt,
|
alt: imageView.alt,
|
||||||
caption: this.imageView.alt,
|
caption: imageView.caption,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onSubmit(event: SubmitEvent): Promise<void> {
|
public async onSubmit(event: SubmitEvent): Promise<void> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await this.imageService.updateImage(this.imageView.owner.username, this.imageView.filename, {
|
const imageView: ImageView = this.imageViewQuery.data()!;
|
||||||
alt: this.imageForm.value.alt,
|
const formValue = this.imageForm.value;
|
||||||
caption: this.imageForm.value.caption,
|
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();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export class ImageSelect {
|
|||||||
|
|
||||||
protected editImage(imageView: ImageView): void {
|
protected editImage(imageView: ImageView): void {
|
||||||
this.dialog.open(EditImageDialog, {
|
this.dialog.open(EditImageDialog, {
|
||||||
data: imageView,
|
data: [imageView.owner.username, imageView.filename],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import { ResourceOwner } from './ResourceOwner.model';
|
import { ResourceOwner } from './ResourceOwner.model';
|
||||||
|
|
||||||
export interface ImageView {
|
export interface ImageView {
|
||||||
alt: string;
|
|
||||||
filename: string;
|
|
||||||
height: number | null;
|
|
||||||
owner: ResourceOwner;
|
|
||||||
url: string;
|
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;
|
width: number | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import { EndpointService } from './EndpointService';
|
|||||||
import { ImageView } from '../models/ImageView.model';
|
import { ImageView } from '../models/ImageView.model';
|
||||||
import { SliceView } from '../models/SliceView.model';
|
import { SliceView } from '../models/SliceView.model';
|
||||||
import { QueryParams } from '../models/Query.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 { ImageViewWithBlobUrl } from '../client-models/ImageViewWithBlobUrl';
|
||||||
|
import { WithStringDates } from '../util';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -31,6 +32,14 @@ export class ImageService {
|
|||||||
private readonly endpointService = inject(EndpointService);
|
private readonly endpointService = inject(EndpointService);
|
||||||
private readonly queryClient = inject(QueryClient);
|
private readonly queryClient = inject(QueryClient);
|
||||||
|
|
||||||
|
public hydrateImageView(rawImageView: WithStringDates<ImageView>): ImageView {
|
||||||
|
return {
|
||||||
|
...rawImageView,
|
||||||
|
created: new Date(rawImageView.created),
|
||||||
|
modified: rawImageView.modified ? new Date(rawImageView.modified) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public getOwnedImages(queryParams?: QueryParams<typeof ImageService.ImageProps>): Promise<SliceView<ImageView>> {
|
public getOwnedImages(queryParams?: QueryParams<typeof ImageService.ImageProps>): Promise<SliceView<ImageView>> {
|
||||||
return firstValueFrom(
|
return firstValueFrom(
|
||||||
this.httpClient.get<SliceView<ImageView>>(this.endpointService.getUrl('images', [], queryParams)),
|
this.httpClient.get<SliceView<ImageView>>(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<ImageView>
|
||||||
|
>(this.endpointService.getUrl('images', [username, filename, 'view']))
|
||||||
|
.pipe(map((rawImageView) => this.hydrateImageView(rawImageView))),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public uploadImage(
|
public uploadImage(
|
||||||
image: File,
|
image: File,
|
||||||
filename?: string,
|
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'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { WithStringDates } from '../util';
|
|||||||
import { Recipe } from '../models/Recipe.model';
|
import { Recipe } from '../models/Recipe.model';
|
||||||
import { ImageView } from '../models/ImageView.model';
|
import { ImageView } from '../models/ImageView.model';
|
||||||
import { SetImageBody } from '../models/SetImageBody';
|
import { SetImageBody } from '../models/SetImageBody';
|
||||||
|
import { ImageService } from './ImageService';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -16,12 +17,14 @@ import { SetImageBody } from '../models/SetImageBody';
|
|||||||
export class RecipeDraftService {
|
export class RecipeDraftService {
|
||||||
private readonly http = inject(HttpClient);
|
private readonly http = inject(HttpClient);
|
||||||
private readonly endpointService = inject(EndpointService);
|
private readonly endpointService = inject(EndpointService);
|
||||||
|
private readonly imageService = inject(ImageService);
|
||||||
|
|
||||||
private hydrateView(rawView: WithStringDates<RecipeDraftViewModel>): RecipeDraftViewModel {
|
private hydrateView(rawView: WithStringDates<RecipeDraftViewModel>): RecipeDraftViewModel {
|
||||||
return {
|
return {
|
||||||
...rawView,
|
...rawView,
|
||||||
created: new Date(rawView.created),
|
created: new Date(rawView.created),
|
||||||
modified: rawView.modified ? new Date(rawView.modified) : undefined,
|
modified: rawView.modified ? new Date(rawView.modified) : undefined,
|
||||||
|
mainImage: rawView.mainImage ? this.imageService.hydrateImageView(rawView.mainImage) : undefined,
|
||||||
lastInference: rawView.lastInference
|
lastInference: rawView.lastInference
|
||||||
? {
|
? {
|
||||||
...rawView.lastInference,
|
...rawView.lastInference,
|
||||||
|
|||||||
@ -33,3 +33,7 @@ export type WithStringDates<T> = {
|
|||||||
? WithStringDates<T[K]> | null | undefined
|
? WithStringDates<T[K]> | null | undefined
|
||||||
: T[K];
|
: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const notNullOrUndefined = <T>(t: T | null | undefined): t is T => {
|
||||||
|
return t !== null && t !== undefined;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user