MME-17 Redo recipe ingredients in upload form; refactor to use app-dialog-container for dialogs.
This commit is contained in:
parent
80b0c5e4d5
commit
9040a61edb
@ -16,7 +16,7 @@
|
|||||||
<app-infer></app-infer>
|
<app-infer></app-infer>
|
||||||
} @else if (displayStep() === RecipeUploadStep.ENTER_DATA) {
|
} @else if (displayStep() === RecipeUploadStep.ENTER_DATA) {
|
||||||
<app-enter-recipe-data
|
<app-enter-recipe-data
|
||||||
[model]="model()"
|
[draft]="model().draft!"
|
||||||
(submit)="onEnterRecipeDataSubmit($event)"
|
(submit)="onEnterRecipeDataSubmit($event)"
|
||||||
(deleteDraft)="onDeleteDraft()"
|
(deleteDraft)="onDeleteDraft()"
|
||||||
></app-enter-recipe-data>
|
></app-enter-recipe-data>
|
||||||
|
|||||||
@ -2,9 +2,9 @@ export interface EnterRecipeDataSubmitEvent {
|
|||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
ingredients: Array<{
|
ingredients: Array<{
|
||||||
amount: string | null;
|
amount?: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
notes: string | null;
|
notes?: string | null;
|
||||||
}>;
|
}>;
|
||||||
rawText: string;
|
rawText: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,3 @@
|
|||||||
.ingredients-table {
|
|
||||||
width: 100ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ingredient-input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -20,6 +12,21 @@ textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.draft-info-container {
|
.draft-info-container {
|
||||||
|
width: 60ch;
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 10px;
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-actions-button {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-reorder {
|
||||||
|
width: 32px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-column-actions {
|
||||||
|
width: 32px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<h2>Enter Recipe</h2>
|
<h2>Enter Recipe</h2>
|
||||||
<div class="draft-info-container">
|
<div class="draft-info-container">
|
||||||
<div>
|
<div>
|
||||||
<p>Draft started: {{ model().draft!.created }}</p>
|
<p>Draft started: {{ draft().created | date: "short" }}</p>
|
||||||
<p>Last saved: {{ model().draft!.modified }}</p>
|
<p>Last saved: {{ draft().modified | date: "short" }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button matButton="text" [matMenuTriggerFor]="draftActionsMenu">
|
<button matButton="text" [matMenuTriggerFor]="draftActionsMenu" class="draft-actions-button">
|
||||||
<fa-icon [icon]="faEllipsis" size="3x"></fa-icon>
|
<fa-icon [icon]="faEllipsis" size="3x"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #draftActionsMenu="matMenu">
|
<mat-menu #draftActionsMenu="matMenu">
|
||||||
@ -25,57 +25,62 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<h3>Ingredients</h3>
|
<h3>Ingredients</h3>
|
||||||
|
|
||||||
<table
|
<table
|
||||||
class="ingredients-table"
|
|
||||||
mat-table
|
|
||||||
#ingredientsTable
|
#ingredientsTable
|
||||||
[dataSource]="recipeFormGroup.controls.ingredients.controls"
|
mat-table
|
||||||
|
[dataSource]="ingredientModels()"
|
||||||
|
cdkDropList
|
||||||
|
(cdkDropListDropped)="onIngredientDrop($event)"
|
||||||
>
|
>
|
||||||
|
<ng-container matColumnDef="reorder">
|
||||||
|
<th mat-header-cell *matHeaderCellDef></th>
|
||||||
|
<td mat-cell *matCellDef="let model">
|
||||||
|
<fa-icon [icon]="faBars"></fa-icon>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="amount">
|
<ng-container matColumnDef="amount">
|
||||||
<th mat-header-cell *matHeaderCellDef>Amount</th>
|
<th mat-header-cell *matHeaderCellDef>Amount</th>
|
||||||
<td mat-cell *matCellDef="let row; let i = index">
|
<td mat-cell *matCellDef="let model">
|
||||||
<mat-form-field class="ingredient-input ingredient-amount-input">
|
{{ model.draft.amount }}
|
||||||
<input
|
|
||||||
#ingredientAmount
|
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
[formControl]="getIngredientControl(i, 'amount')"
|
|
||||||
(keydown)="onIngredientKeydown($event, i)"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<th mat-header-cell *matHeaderCellDef>Name</th>
|
<th mat-header-cell *matHeaderCellDef>Name</th>
|
||||||
<td mat-cell *matCellDef="let row; let i = index">
|
<td mat-cell *matCellDef="let model">
|
||||||
<mat-form-field class="ingredient-input ingredient-name-input">
|
{{ model.draft.name }}
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
[formControl]="getIngredientControl(i, 'name')"
|
|
||||||
(keydown)="onIngredientKeydown($event, i)"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="notes">
|
<ng-container matColumnDef="notes">
|
||||||
<th mat-header-cell *matHeaderCellDef>Notes</th>
|
<th mat-header-cell *matHeaderCellDef>Notes</th>
|
||||||
<td mat-cell *matCellDef="let row; let i = index">
|
<td mat-cell *matCellDef="let model">
|
||||||
<mat-form-field class="ingredient-input ingredient-notes-input">
|
{{ model.draft.notes }}
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
[formControl]="getIngredientControl(i, 'notes')"
|
|
||||||
(keydown)="onIngredientKeydown($event, i)"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="ingredientsColumnsToDisplay"></tr>
|
<ng-container matColumnDef="actions">
|
||||||
<tr mat-row *matRowDef="let row; columns: ingredientsColumnsToDisplay"></tr>
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
|
<td mat-cell *matCellDef="let model">
|
||||||
|
<button matButton="text" [matMenuTriggerFor]="ingredientActionsMenu" type="button">
|
||||||
|
<fa-icon [icon]="faEllipsis" size="2x"></fa-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #ingredientActionsMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="editIngredient(model)" type="button">Edit</button>
|
||||||
|
<button mat-menu-item (click)="onIngredientDelete(model)" type="button">Delete</button>
|
||||||
|
</mat-menu>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['reorder', 'amount', 'name', 'notes', 'actions']"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: ['reorder', 'amount', 'name', 'notes', 'actions']"
|
||||||
|
cdkDrag
|
||||||
|
[cdkDragData]="row"
|
||||||
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
<button matButton="outlined" (click)="addIngredient()" type="button">Add Ingredient</button>
|
<button matButton="outlined" (click)="addIngredient()" type="button">Add Ingredient</button>
|
||||||
|
|
||||||
|
|||||||
@ -8,14 +8,20 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
output,
|
output,
|
||||||
runInInjectionContext,
|
runInInjectionContext,
|
||||||
|
signal,
|
||||||
viewChild,
|
viewChild,
|
||||||
viewChildren,
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { RecipeUploadClientModel } from '../../../../shared/client-models/RecipeUploadClientModel';
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
||||||
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
import { MatFormField, MatInput, MatLabel } from '@angular/material/input';
|
||||||
import { MatButton } from '@angular/material/button';
|
import { MatButton } from '@angular/material/button';
|
||||||
import { EnterRecipeDataSubmitEvent } from './EnterRecipeDataSubmitEvent';
|
import { EnterRecipeDataSubmitEvent } from './EnterRecipeDataSubmitEvent';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { faBars, 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';
|
||||||
|
import { IngredientDialog } from './ingredient-dialog/ingredient-dialog';
|
||||||
|
import { RecipeDraftViewModel } from '../../../../shared/models/RecipeDraftView.model';
|
||||||
import {
|
import {
|
||||||
MatCell,
|
MatCell,
|
||||||
MatCellDef,
|
MatCellDef,
|
||||||
@ -28,11 +34,9 @@ import {
|
|||||||
MatRowDef,
|
MatRowDef,
|
||||||
MatTable,
|
MatTable,
|
||||||
} from '@angular/material/table';
|
} from '@angular/material/table';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { IngredientDraftClientModel } from '../../../../shared/client-models/IngredientDraftClientModel';
|
||||||
import { faEllipsis } from '@fortawesome/free-solid-svg-icons';
|
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
|
import { DatePipe } from '@angular/common';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { ImageUploadDialog } from './image-upload-dialog/image-upload-dialog';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-enter-recipe-data',
|
selector: 'app-enter-recipe-data',
|
||||||
@ -42,6 +46,10 @@ import { ImageUploadDialog } from './image-upload-dialog/image-upload-dialog';
|
|||||||
MatLabel,
|
MatLabel,
|
||||||
MatInput,
|
MatInput,
|
||||||
MatButton,
|
MatButton,
|
||||||
|
FaIconComponent,
|
||||||
|
MatMenuTrigger,
|
||||||
|
MatMenu,
|
||||||
|
MatMenuItem,
|
||||||
MatTable,
|
MatTable,
|
||||||
MatColumnDef,
|
MatColumnDef,
|
||||||
MatHeaderCell,
|
MatHeaderCell,
|
||||||
@ -52,60 +60,48 @@ import { ImageUploadDialog } from './image-upload-dialog/image-upload-dialog';
|
|||||||
MatHeaderRowDef,
|
MatHeaderRowDef,
|
||||||
MatRow,
|
MatRow,
|
||||||
MatRowDef,
|
MatRowDef,
|
||||||
FaIconComponent,
|
CdkDropList,
|
||||||
MatMenuTrigger,
|
CdkDrag,
|
||||||
MatMenu,
|
DatePipe,
|
||||||
MatMenuItem,
|
|
||||||
],
|
],
|
||||||
templateUrl: './enter-recipe-data.html',
|
templateUrl: './enter-recipe-data.html',
|
||||||
styleUrl: './enter-recipe-data.css',
|
styleUrl: './enter-recipe-data.css',
|
||||||
})
|
})
|
||||||
export class EnterRecipeData implements OnInit {
|
export class EnterRecipeData implements OnInit {
|
||||||
public readonly model = input.required<RecipeUploadClientModel>();
|
public readonly draft = input.required<RecipeDraftViewModel>();
|
||||||
public readonly submit = output<EnterRecipeDataSubmitEvent>();
|
public readonly submit = output<EnterRecipeDataSubmitEvent>();
|
||||||
public readonly deleteDraft = output<void>();
|
public readonly deleteDraft = output<void>();
|
||||||
|
|
||||||
protected readonly recipeTextTextarea = viewChild.required<ElementRef<HTMLTextAreaElement>>('recipeTextTextarea');
|
protected readonly recipeTextTextarea = viewChild.required<ElementRef<HTMLTextAreaElement>>('recipeTextTextarea');
|
||||||
protected readonly ingredientsTable = viewChild.required<
|
|
||||||
MatTable<
|
|
||||||
FormGroup<{
|
|
||||||
amount: FormControl<string | null>;
|
|
||||||
name: FormControl<string | null>;
|
|
||||||
notes: FormControl<string | null>;
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
>('ingredientsTable');
|
|
||||||
protected readonly ingredientAmountControls = viewChildren<ElementRef<HTMLInputElement>>('ingredientAmount');
|
|
||||||
|
|
||||||
private readonly dialog = inject(MatDialog);
|
private readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
protected readonly recipeFormGroup = new FormGroup({
|
protected readonly recipeFormGroup = new FormGroup({
|
||||||
title: new FormControl('', Validators.required),
|
title: new FormControl('', Validators.required),
|
||||||
slug: new FormControl('', Validators.required),
|
slug: new FormControl('', Validators.required),
|
||||||
ingredients: new FormArray(
|
|
||||||
[] as Array<
|
|
||||||
FormGroup<{
|
|
||||||
amount: FormControl<string | null>;
|
|
||||||
name: FormControl<string | null>;
|
|
||||||
notes: FormControl<string | null>;
|
|
||||||
}>
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
text: new FormControl('', Validators.required),
|
text: new FormControl('', Validators.required),
|
||||||
});
|
});
|
||||||
|
|
||||||
protected readonly ingredientsColumnsToDisplay = ['amount', 'name', 'notes'];
|
protected readonly ingredientsTable = viewChild<MatTable<unknown>>('ingredientsTable');
|
||||||
|
protected readonly ingredientModels = signal<IngredientDraftClientModel[]>([]);
|
||||||
|
|
||||||
private readonly injector = inject(Injector);
|
private readonly injector = inject(Injector);
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
const model = this.model();
|
const draft = this.draft();
|
||||||
this.recipeFormGroup.patchValue({
|
this.recipeFormGroup.patchValue({
|
||||||
title: model.draft?.title ?? '',
|
title: draft.title ?? '',
|
||||||
slug: model.draft?.slug ?? '',
|
slug: draft.slug ?? '',
|
||||||
text: model.draft?.rawText ?? '',
|
text: draft.rawText ?? '',
|
||||||
ingredients: model.draft?.ingredients ?? [],
|
|
||||||
});
|
});
|
||||||
|
if (draft.ingredients) {
|
||||||
|
this.ingredientModels.set(
|
||||||
|
draft.ingredients.map((ingredient, index) => ({
|
||||||
|
id: index,
|
||||||
|
draft: ingredient,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
runInInjectionContext(this.injector, () => {
|
runInInjectionContext(this.injector, () => {
|
||||||
afterNextRender({
|
afterNextRender({
|
||||||
mixedReadWrite: () => {
|
mixedReadWrite: () => {
|
||||||
@ -130,46 +126,68 @@ export class EnterRecipeData implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected addIngredient() {
|
protected addIngredient() {
|
||||||
const control = new FormGroup({
|
const dialogRef = this.dialog.open<IngredientDialog, IngredientDraftClientModel, IngredientDraftClientModel>(
|
||||||
amount: new FormControl(''),
|
IngredientDialog,
|
||||||
name: new FormControl('', Validators.required),
|
{
|
||||||
notes: new FormControl(''),
|
data: {
|
||||||
|
id: this.ingredientModels().length,
|
||||||
|
draft: {
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
dialogRef.afterClosed().subscribe((ingredientModel) => {
|
||||||
|
if (ingredientModel) {
|
||||||
|
this.ingredientModels.update((ingredientModels) => [...ingredientModels, ingredientModel]);
|
||||||
|
this.ingredientsTable()!.renderRows();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.recipeFormGroup.controls.ingredients.push(control);
|
|
||||||
this.ingredientsTable().renderRows();
|
|
||||||
const addedIndex = this.recipeFormGroup.controls.ingredients.length - 1;
|
|
||||||
const target = this.ingredientAmountControls()[addedIndex];
|
|
||||||
target.nativeElement.focus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected removeIngredient(index: number) {
|
protected editIngredient(model: IngredientDraftClientModel): void {
|
||||||
this.recipeFormGroup.controls.ingredients.removeAt(index);
|
const dialogRef = this.dialog.open<IngredientDialog, IngredientDraftClientModel, IngredientDraftClientModel>(
|
||||||
|
IngredientDialog,
|
||||||
|
{
|
||||||
|
data: model,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
dialogRef.afterClosed().subscribe((model) => {
|
||||||
|
if (model) {
|
||||||
|
this.ingredientModels.update((models) => {
|
||||||
|
const updated: IngredientDraftClientModel[] = [...models];
|
||||||
|
const target = updated.find((search) => search.id === model.id);
|
||||||
|
if (!target) {
|
||||||
|
throw new Error('Ingredient does not exist.');
|
||||||
|
}
|
||||||
|
target.draft = model.draft;
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getIngredientControl(index: number, column: 'amount' | 'name' | 'notes'): FormControl {
|
protected onIngredientDelete(ingredientModel: IngredientDraftClientModel) {
|
||||||
const ingredientGroup = this.recipeFormGroup.controls.ingredients.controls[index].controls;
|
this.ingredientModels.update((ingredientModels) => {
|
||||||
switch (column) {
|
const updated = ingredientModels.filter((model) => model.id !== ingredientModel.id);
|
||||||
case 'amount':
|
updated.sort((model0, model1) => model0.id - model1.id);
|
||||||
return ingredientGroup.amount;
|
updated.forEach((model, index) => {
|
||||||
case 'name':
|
model.id = index;
|
||||||
return ingredientGroup.name;
|
});
|
||||||
case 'notes':
|
return updated;
|
||||||
return ingredientGroup.notes;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onIngredientKeydown(event: KeyboardEvent, index: number) {
|
protected onIngredientDrop(event: CdkDragDrop<IngredientDraftClientModel>): void {
|
||||||
if (event.key === 'Enter') {
|
this.ingredientModels.update((ingredientModels) => {
|
||||||
event.preventDefault();
|
const modelIndex = ingredientModels.findIndex((m) => m.id === event.previousIndex);
|
||||||
this.onIngredientEnterKey(index);
|
moveItemInArray(ingredientModels, modelIndex, event.currentIndex);
|
||||||
}
|
ingredientModels.forEach((model, index) => {
|
||||||
}
|
model.id = index;
|
||||||
|
});
|
||||||
private onIngredientEnterKey(index: number) {
|
return ingredientModels;
|
||||||
if (index === this.recipeFormGroup.controls.ingredients.length - 1) {
|
});
|
||||||
// last control row
|
this.ingredientsTable()!.renderRows();
|
||||||
this.addIngredient();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onSubmit(event: SubmitEvent): void {
|
protected onSubmit(event: SubmitEvent): void {
|
||||||
@ -178,12 +196,7 @@ export class EnterRecipeData implements OnInit {
|
|||||||
this.submit.emit({
|
this.submit.emit({
|
||||||
title: value.title!,
|
title: value.title!,
|
||||||
slug: value.slug!,
|
slug: value.slug!,
|
||||||
ingredients:
|
ingredients: this.ingredientModels().map((ingredientModel) => ingredientModel.draft),
|
||||||
value.ingredients?.map((ingredient) => ({
|
|
||||||
amount: ingredient.amount ?? null,
|
|
||||||
name: ingredient.name!,
|
|
||||||
notes: ingredient.notes ?? null,
|
|
||||||
})) ?? [],
|
|
||||||
rawText: value.text!,
|
rawText: value.text!,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -197,4 +210,5 @@ export class EnterRecipeData implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly faEllipsis = faEllipsis;
|
protected readonly faEllipsis = faEllipsis;
|
||||||
|
protected readonly faBars = faBars;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<div class="image-upload-dialog-content">
|
<app-dialog-container title="Image Upload">
|
||||||
<h3>Image Upload</h3>
|
|
||||||
<form>
|
<form>
|
||||||
@let file = fileToUpload();
|
@let file = fileToUpload();
|
||||||
@if (file !== null) {
|
@if (file !== null) {
|
||||||
@ -46,4 +45,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</app-dialog-container>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula
|
|||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { ImageDoesNotExistValidator } from '../../../../../shared/validators/image-does-not-exist-validator';
|
import { ImageDoesNotExistValidator } from '../../../../../shared/validators/image-does-not-exist-validator';
|
||||||
|
import { DialogContainer } from '../../../../../shared/components/dialog-container/dialog-container';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-image-upload-dialog',
|
selector: 'app-image-upload-dialog',
|
||||||
@ -23,6 +24,7 @@ import { ImageDoesNotExistValidator } from '../../../../../shared/validators/ima
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatCheckbox,
|
MatCheckbox,
|
||||||
MatError,
|
MatError,
|
||||||
|
DialogContainer,
|
||||||
],
|
],
|
||||||
templateUrl: './image-upload-dialog.html',
|
templateUrl: './image-upload-dialog.html',
|
||||||
styleUrl: './image-upload-dialog.css',
|
styleUrl: './image-upload-dialog.css',
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 10px;
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<app-dialog-container title="Edit Ingredient">
|
||||||
|
<form (submit)="onSubmit($event)">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Amount</mat-label>
|
||||||
|
<input matInput [formControl]="ingredientForm.controls.amount" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput [formControl]="ingredientForm.controls.name" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Notes</mat-label>
|
||||||
|
<input matInput [formControl]="ingredientForm.controls.notes" />
|
||||||
|
</mat-form-field>
|
||||||
|
<button matButton="filled" type="submit" [disabled]="ingredientForm.invalid">Submit</button>
|
||||||
|
</form>
|
||||||
|
</app-dialog-container>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { IngredientDialog } from './ingredient-dialog';
|
||||||
|
|
||||||
|
describe('IngredientDialog', () => {
|
||||||
|
let component: IngredientDialog;
|
||||||
|
let fixture: ComponentFixture<IngredientDialog>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [IngredientDialog],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(IngredientDialog);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import { Component, inject, 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 { MatButton } from '@angular/material/button';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { IngredientDraftClientModel } from '../../../../../shared/client-models/IngredientDraftClientModel';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ingredient-dialog',
|
||||||
|
imports: [DialogContainer, MatFormField, MatLabel, MatInput, ReactiveFormsModule, MatButton],
|
||||||
|
templateUrl: './ingredient-dialog.html',
|
||||||
|
styleUrl: './ingredient-dialog.css',
|
||||||
|
})
|
||||||
|
export class IngredientDialog implements OnInit {
|
||||||
|
public readonly dialogRef = inject(MatDialogRef);
|
||||||
|
private readonly model = inject<IngredientDraftClientModel>(MAT_DIALOG_DATA);
|
||||||
|
|
||||||
|
protected readonly ingredientForm = new FormGroup({
|
||||||
|
amount: new FormControl(''),
|
||||||
|
name: new FormControl('', Validators.required),
|
||||||
|
notes: new FormControl(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.ingredientForm.patchValue({
|
||||||
|
amount: this.model.draft.amount,
|
||||||
|
name: this.model.draft.name,
|
||||||
|
notes: this.model.draft.notes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onSubmit(event: SubmitEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
this.dialogRef.close({
|
||||||
|
...this.model,
|
||||||
|
draft: {
|
||||||
|
amount: this.ingredientForm.value.amount,
|
||||||
|
name: this.ingredientForm.value.name,
|
||||||
|
notes: this.ingredientForm.value.notes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { IngredientDraft } from '../models/RecipeDraftView.model';
|
||||||
|
|
||||||
|
export interface IngredientDraftClientModel {
|
||||||
|
id: number;
|
||||||
|
draft: IngredientDraft;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
.dialog-container {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 10px;
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
<div class="dialog-container">
|
||||||
|
<h3>{{ title() }}</h3>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DialogContainer } from './dialog-container';
|
||||||
|
|
||||||
|
describe('DialogContainer', () => {
|
||||||
|
let component: DialogContainer;
|
||||||
|
let fixture: ComponentFixture<DialogContainer>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DialogContainer],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DialogContainer);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dialog-container',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './dialog-container.html',
|
||||||
|
styleUrl: './dialog-container.css',
|
||||||
|
})
|
||||||
|
export class DialogContainer {
|
||||||
|
public readonly title = input.required<string>();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user