diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.css b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.css new file mode 100644 index 0000000..b9cb93b --- /dev/null +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.css @@ -0,0 +1,5 @@ +form { + display: flex; + flex-direction: column; + row-gap: 10px; +} 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 new file mode 100644 index 0000000..1b8a264 --- /dev/null +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.html @@ -0,0 +1,17 @@ + +
+ + Filename + + + + Alt + + + + Caption + + + +
+
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.spec.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.spec.ts new file mode 100644 index 0000000..3659271 --- /dev/null +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditImageDialog } from './edit-image-dialog'; + +describe('EditImageDialog', () => { + let component: EditImageDialog; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EditImageDialog], + }).compileComponents(); + + fixture = TestBed.createComponent(EditImageDialog); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 0000000..6180435 --- /dev/null +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/edit-image-dialog/edit-image-dialog.ts @@ -0,0 +1,54 @@ +import { Component, inject, input, OnInit } from '@angular/core'; +import { DialogContainer } from '../../../../../../shared/components/dialog-container/dialog-container'; +import { MatFormField, MatInput, MatLabel } from '@angular/material/input'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ImageView } from '../../../../../../shared/models/ImageView.model'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatButton } from '@angular/material/button'; +import { ImageService } from '../../../../../../shared/services/ImageService'; +import { QueryClient } from '@tanstack/angular-query-experimental'; + +@Component({ + selector: 'app-edit-image-dialog', + imports: [DialogContainer, MatFormField, MatLabel, MatInput, ReactiveFormsModule, MatButton], + 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 queryClient = inject(QueryClient); + private readonly dialogRef = inject(MatDialogRef); + + protected readonly imageForm = new FormGroup({ + filename: new FormControl( + { + value: '', + disabled: true, + }, + Validators.required, + ), + alt: new FormControl(''), + caption: new FormControl(''), + }); + + public ngOnInit(): void { + this.imageForm.patchValue({ + filename: this.imageView.filename, + alt: this.imageView.alt, + caption: this.imageView.alt, + }); + } + + 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, + }); + await this.queryClient.invalidateQueries({ + queryKey: ['image-views'], + }); + this.dialogRef.close(); + } +} diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.css b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.css index 4a9b765..9b65a85 100644 --- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.css +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.css @@ -13,3 +13,8 @@ mat-card-content p { overflow-wrap: anywhere; } + +mat-card-actions { + display: flex; + justify-content: space-between; +} diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.html b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.html index 5625fd0..7bb923b 100644 --- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.html +++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-select/image-select.html @@ -26,7 +26,17 @@ Main image + >Main? + + + + + } 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 77f43c0..516a8af 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 @@ -1,16 +1,35 @@ import { Component, computed, inject, input, output, signal } from '@angular/core'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { ImageService } from '../../../../../shared/services/ImageService'; -import { injectQuery, keepPreviousData } from '@tanstack/angular-query-experimental'; +import { injectQuery, keepPreviousData, QueryClient } from '@tanstack/angular-query-experimental'; import { Spinner } from '../../../../../shared/components/spinner/spinner'; import { ImageView } from '../../../../../shared/models/ImageView.model'; import { injectQueries } from '@tanstack/angular-query-experimental/inject-queries-experimental'; import { MatCard, MatCardActions, MatCardContent, MatCardImage } from '@angular/material/card'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { faEllipsis } from '@fortawesome/free-solid-svg-icons'; +import { MatButton } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; +import { EditImageDialog } from './edit-image-dialog/edit-image-dialog'; @Component({ selector: 'app-image-select', - imports: [MatPaginator, Spinner, MatCard, MatCardImage, MatCardContent, MatCardActions, MatCheckbox], + imports: [ + MatPaginator, + Spinner, + MatCard, + MatCardImage, + MatCardContent, + MatCardActions, + MatCheckbox, + MatMenuTrigger, + MatMenu, + FaIconComponent, + MatButton, + MatMenuItem, + ], templateUrl: './image-select.html', styleUrl: './image-select.css', }) @@ -22,6 +41,8 @@ export class ImageSelect { protected readonly pageSize = signal(9); private readonly imageService = inject(ImageService); + private readonly dialog = inject(MatDialog); + private readonly queryClient = inject(QueryClient); protected readonly imageViewsQuery = injectQuery(() => ({ queryKey: ['image-views', this.currentPage(), this.pageSize()], @@ -90,4 +111,22 @@ export class ImageSelect { } return false; } + + protected editImage(imageView: ImageView): void { + this.dialog.open(EditImageDialog, { + data: imageView, + }); + } + + protected async deleteImage(imageView: ImageView): Promise { + await this.imageService.deleteImage(imageView.owner.username, imageView.filename); + await this.queryClient.invalidateQueries({ + queryKey: ['image-views'], + }); + await this.queryClient.invalidateQueries({ + queryKey: ['images'], + }); + } + + protected readonly faEllipsis = faEllipsis; } diff --git a/src/app/shared/services/ImageService.ts b/src/app/shared/services/ImageService.ts index 81f8a5a..abe6097 100644 --- a/src/app/shared/services/ImageService.ts +++ b/src/app/shared/services/ImageService.ts @@ -81,4 +81,27 @@ export class ImageService { .pipe(map((view) => view.exists)), ); } + + public deleteImage(username: string, filename: string): Promise { + return firstValueFrom( + this.httpClient.delete(this.endpointService.getUrl('images', [username, filename])), + ); + } + + public updateImage( + username: string, + filename: string, + data: { + alt?: string | null; + caption?: string | null; + isPublic?: boolean | null; + viewersToAdd?: string[] | null; + viewersToRemove?: string[] | null; + clearAllViewers?: boolean | null; + }, + ): Promise { + return firstValueFrom( + this.httpClient.put(this.endpointService.getUrl('images', [username, filename]), data), + ); + } }