diff --git a/src/app/endpoints.ts b/src/app/endpoints.ts
index 42b2aea..733ab6e 100644
--- a/src/app/endpoints.ts
+++ b/src/app/endpoints.ts
@@ -2,6 +2,7 @@ export const Endpoints = {
authLogin: 'auth/login',
authLogout: 'auth/logout',
authRefresh: 'auth/refresh',
+ images: 'images',
recipes: 'recipes',
recipeDrafts: 'recipe-drafts',
};
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.html b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.html
index 99f9c72..3ba338f 100644
--- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.html
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.html
@@ -79,6 +79,9 @@
+
Images
+
+
Recipe Text
Recipe Text
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.ts
index 3456af0..e424e9e 100644
--- a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.ts
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/enter-recipe-data.ts
@@ -31,6 +31,8 @@ import {
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { faEllipsis } from '@fortawesome/free-solid-svg-icons';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
+import { MatDialog } from '@angular/material/dialog';
+import { ImageUploadDialog } from './image-upload-dialog/image-upload-dialog';
@Component({
selector: 'app-enter-recipe-data',
@@ -63,8 +65,8 @@ export class EnterRecipeData implements OnInit {
public readonly submit = output();
public readonly deleteDraft = output();
- protected recipeTextTextarea = viewChild.required>('recipeTextTextarea');
- protected ingredientsTable = viewChild.required<
+ protected readonly recipeTextTextarea = viewChild.required>('recipeTextTextarea');
+ protected readonly ingredientsTable = viewChild.required<
MatTable<
FormGroup<{
amount: FormControl;
@@ -73,7 +75,9 @@ export class EnterRecipeData implements OnInit {
}>
>
>('ingredientsTable');
- protected ingredientAmountControls = viewChildren>('ingredientAmount');
+ protected readonly ingredientAmountControls = viewChildren>('ingredientAmount');
+
+ private readonly dialog = inject(MatDialog);
protected readonly recipeFormGroup = new FormGroup({
title: new FormControl('', Validators.required),
@@ -188,5 +192,9 @@ export class EnterRecipeData implements OnInit {
this.deleteDraft.emit();
}
+ protected openImageUploadDialog(): void {
+ this.dialog.open(ImageUploadDialog);
+ }
+
protected readonly faEllipsis = faEllipsis;
}
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.css b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.css
new file mode 100644
index 0000000..ba08209
--- /dev/null
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.css
@@ -0,0 +1,23 @@
+.image-upload-dialog-content {
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ row-gap: 1rem;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ row-gap: 20px;
+}
+
+button {
+ width: 100%;
+}
+
+button div {
+ display: flex;
+ column-gap: 10px;
+ align-items: center;
+}
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.html b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.html
new file mode 100644
index 0000000..1fc2921
--- /dev/null
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.html
@@ -0,0 +1,49 @@
+
diff --git a/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.spec.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.spec.ts
new file mode 100644
index 0000000..c4a781d
--- /dev/null
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ImageUploadDialog } from './image-upload-dialog';
+
+describe('ImageUploadDialog', () => {
+ let component: ImageUploadDialog;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ImageUploadDialog],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ImageUploadDialog);
+ 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-upload-dialog/image-upload-dialog.ts b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.ts
new file mode 100644
index 0000000..a0833ad
--- /dev/null
+++ b/src/app/pages/recipe-upload-page/steps/enter-recipe-data/image-upload-dialog/image-upload-dialog.ts
@@ -0,0 +1,87 @@
+import { Component, computed, inject, signal } from '@angular/core';
+import { ImageService } from '../../../../../shared/services/ImageService';
+import { FileUpload } from '../../../../../shared/components/file-upload/file-upload';
+import { MatButton } from '@angular/material/button';
+import { faFileImage } from '@fortawesome/free-solid-svg-icons';
+import { FileUploadEvent } from '../../../../../shared/components/file-upload/FileUploadEvent';
+import { Spinner } from '../../../../../shared/components/spinner/spinner';
+import { MatError, MatFormField, MatInput, MatLabel } from '@angular/material/input';
+import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { MatDialogRef } from '@angular/material/dialog';
+import { ImageDoesNotExistValidator } from '../../../../../shared/validators/image-does-not-exist-validator';
+
+@Component({
+ selector: 'app-image-upload-dialog',
+ imports: [
+ FileUpload,
+ MatButton,
+ Spinner,
+ MatFormField,
+ MatLabel,
+ MatInput,
+ ReactiveFormsModule,
+ MatCheckbox,
+ MatError,
+ ],
+ templateUrl: './image-upload-dialog.html',
+ styleUrl: './image-upload-dialog.css',
+})
+export class ImageUploadDialog {
+ public readonly dialogRef = inject(MatDialogRef);
+
+ protected readonly fileToUpload = signal(null);
+ protected readonly inProgress = signal(false);
+ protected readonly submitDisabled = computed(() => !this.fileToUpload() || this.inProgress());
+ protected readonly error = signal(null);
+
+ private readonly imageDoesNotExistValidator = inject(ImageDoesNotExistValidator);
+
+ protected readonly imageUploadForm = new FormGroup({
+ filename: new FormControl('', {
+ validators: Validators.required,
+ asyncValidators: this.imageDoesNotExistValidator.validator(),
+ updateOn: 'blur',
+ }),
+ alt: new FormControl(''),
+ caption: new FormControl(''),
+ isPublic: new FormControl(true),
+ });
+
+ private readonly imageService = inject(ImageService);
+
+ protected onImageFileEvent(event: FileUploadEvent): void {
+ if (event._tag === 'file-add-event') {
+ this.fileToUpload.set(event.file);
+ this.imageUploadForm.controls.filename.setValue(event.file.name);
+ this.imageUploadForm.controls.filename.markAsTouched();
+ } else {
+ this.fileToUpload.set(null);
+ this.imageUploadForm.reset();
+ }
+ }
+
+ protected async onSubmit(): Promise {
+ this.inProgress.set(true);
+ const formValue = this.imageUploadForm.value;
+ let hadError = false;
+ try {
+ await this.imageService.uploadImage(
+ this.fileToUpload()!, // submit disabled if this is null
+ formValue.filename ?? undefined,
+ formValue.alt ?? undefined,
+ formValue.caption ?? undefined,
+ formValue.isPublic ?? undefined,
+ );
+ } catch (e) {
+ this.error.set('There was an error during upload. Try again.');
+ hadError = true;
+ }
+ this.inProgress.set(false);
+ if (!hadError) {
+ this.dialogRef.close();
+ }
+ }
+
+ protected readonly faFileImage = faFileImage;
+}
diff --git a/src/app/shared/components/file-upload/file-upload.css b/src/app/shared/components/file-upload/file-upload.css
index 8d6a06e..707eec1 100644
--- a/src/app/shared/components/file-upload/file-upload.css
+++ b/src/app/shared/components/file-upload/file-upload.css
@@ -12,3 +12,8 @@
fa-icon {
cursor: pointer;
}
+
+button {
+ border: none;
+ background: none;
+}
diff --git a/src/app/shared/components/file-upload/file-upload.html b/src/app/shared/components/file-upload/file-upload.html
index c2a2b96..ea07b74 100644
--- a/src/app/shared/components/file-upload/file-upload.html
+++ b/src/app/shared/components/file-upload/file-upload.html
@@ -1,6 +1,14 @@
-
+