import { afterNextRender, Component, ElementRef, inject, Injector, input, OnInit, output, runInInjectionContext, viewChild, viewChildren, } from '@angular/core'; import { RecipeUploadClientModel } from '../../../../shared/client-models/RecipeUploadClientModel'; import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatFormField, MatInput, MatLabel } from '@angular/material/input'; import { MatButton } from '@angular/material/button'; import { EnterRecipeDataSubmitEvent } from './EnterRecipeDataSubmitEvent'; import { MatCell, MatCellDef, MatColumnDef, MatHeaderCell, MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, MatTable, } from '@angular/material/table'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { faEllipsis } from '@fortawesome/free-solid-svg-icons'; import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu'; @Component({ selector: 'app-enter-recipe-data', imports: [ ReactiveFormsModule, MatFormField, MatLabel, MatInput, MatButton, MatTable, MatColumnDef, MatHeaderCell, MatHeaderCellDef, MatCell, MatCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, FaIconComponent, MatMenuTrigger, MatMenu, MatMenuItem, ], templateUrl: './enter-recipe-data.html', styleUrl: './enter-recipe-data.css', }) export class EnterRecipeData implements OnInit { public readonly model = input.required(); public readonly submit = output(); public readonly deleteDraft = output(); protected recipeTextTextarea = viewChild.required>('recipeTextTextarea'); protected ingredientsTable = viewChild.required< MatTable< FormGroup<{ amount: FormControl; name: FormControl; notes: FormControl; }> > >('ingredientsTable'); protected ingredientAmountControls = viewChildren>('ingredientAmount'); protected readonly recipeFormGroup = new FormGroup({ title: new FormControl('', Validators.required), slug: new FormControl('', Validators.required), ingredients: new FormArray( [] as Array< FormGroup<{ amount: FormControl; name: FormControl; notes: FormControl; }> >, ), text: new FormControl('', Validators.required), }); protected readonly ingredientsColumnsToDisplay = ['amount', 'name', 'notes']; private readonly injector = inject(Injector); public ngOnInit(): void { const model = this.model(); this.recipeFormGroup.patchValue({ title: model.draft?.title ?? '', slug: model.draft?.slug ?? '', text: model.draft?.rawText ?? '', ingredients: model.draft?.ingredients ?? [], }); runInInjectionContext(this.injector, () => { afterNextRender({ mixedReadWrite: () => { this.updateTextareaHeight(this.recipeTextTextarea().nativeElement); }, }); }); } private updateTextareaHeight(textarea: HTMLTextAreaElement) { const windowScrollX = window.scrollX; const windowScrollY = window.scrollY; textarea.style.height = 'auto'; textarea.style.height = textarea.scrollHeight + 'px'; requestAnimationFrame(() => { window.scrollTo(windowScrollX, windowScrollY); }); } protected onRecipeTextChange(event: Event): void { this.updateTextareaHeight(event.target as HTMLTextAreaElement); } protected addIngredient() { const control = new FormGroup({ amount: new FormControl(''), name: new FormControl('', Validators.required), notes: new FormControl(''), }); 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) { this.recipeFormGroup.controls.ingredients.removeAt(index); } protected getIngredientControl(index: number, column: 'amount' | 'name' | 'notes'): FormControl { const ingredientGroup = this.recipeFormGroup.controls.ingredients.controls[index].controls; switch (column) { case 'amount': return ingredientGroup.amount; case 'name': return ingredientGroup.name; case 'notes': return ingredientGroup.notes; } } protected onIngredientKeydown(event: KeyboardEvent, index: number) { if (event.key === 'Enter') { event.preventDefault(); this.onIngredientEnterKey(index); } } private onIngredientEnterKey(index: number) { if (index === this.recipeFormGroup.controls.ingredients.length - 1) { // last control row this.addIngredient(); } } protected onSubmit(event: SubmitEvent): void { event.preventDefault(); const value = this.recipeFormGroup.value; this.submit.emit({ title: value.title!, slug: value.slug!, ingredients: value.ingredients?.map(ingredient => ({ amount: ingredient.amount ?? null, name: ingredient.name!, notes: ingredient.notes ?? null, })) ?? [], rawText: value.text! }) } protected onDraftDelete(): void { this.deleteDraft.emit(); } protected readonly faEllipsis = faEllipsis; }