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(),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(withInterceptors([authInterceptor])),
|
||||
provideTanStackQuery(new QueryClient(), withDevtools()),
|
||||
provideTanStackQuery(
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
experimental_prefetchInRender: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
withDevtools(),
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
<app-dialog-container title="Edit Image">
|
||||
<form (submit)="onSubmit($event)">
|
||||
<mat-form-field>
|
||||
<mat-label>Filename</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.filename" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Alt</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.alt" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Caption</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.caption" />
|
||||
</mat-form-field>
|
||||
<button type="submit" matButton="filled">Submit</button>
|
||||
</form>
|
||||
@if (imageViewQuery.isLoading()) {
|
||||
<app-spinner></app-spinner>
|
||||
} @else if (imageViewQuery.isError()) {
|
||||
<p>There was an error.</p>
|
||||
} @else {
|
||||
<form (submit)="onSubmit($event)">
|
||||
<mat-form-field>
|
||||
<mat-label>Filename</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.filename" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Alt</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.alt" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Caption</mat-label>
|
||||
<input matInput [formControl]="imageForm.controls.caption" />
|
||||
</mat-form-field>
|
||||
<button type="submit" matButton="filled">Submit</button>
|
||||
</form>
|
||||
}
|
||||
</app-dialog-container>
|
||||
|
||||
@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ export class ImageSelect {
|
||||
|
||||
protected editImage(imageView: ImageView): void {
|
||||
this.dialog.open(EditImageDialog, {
|
||||
data: imageView,
|
||||
data: [imageView.owner.username, imageView.filename],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>): 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>> {
|
||||
return firstValueFrom(
|
||||
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(
|
||||
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'],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>): 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,
|
||||
|
||||
@ -33,3 +33,7 @@ export type WithStringDates<T> = {
|
||||
? WithStringDates<T[K]> | null | undefined
|
||||
: 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