diff options
Diffstat (limited to 'public/projects/angular-small-apps/apps/recipes/src/app/components/recipe')
4 files changed, 329 insertions, 0 deletions
diff --git a/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.html b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.html new file mode 100644 index 0000000..9f875d9 --- /dev/null +++ b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.html @@ -0,0 +1,111 @@ +<article class="recipe"> + <header class="recipe__header"> + <toolbar (isEditionMode)="isContentEditable($event)"></toolbar> + <h2 class="recipe__title"> + <ng-container + *ngIf="isEditable; then editableTitle; else staticTitle" + ></ng-container> + <ng-template #editableTitle> + <input + type="text" + value="{{ recipe.strMeal }}" + name="strMeal" + (input)="updateRecipe($event)" + class="recipe__field" + /> + </ng-template> + <ng-template #staticTitle>{{ recipe.strMeal }}</ng-template> + </h2> + <dl class="meta"> + <dt class="meta__term">Category:</dt> + <dd class="meta__description"> + <ng-container + *ngIf="isEditable; then editableCategory; else staticCategory" + ></ng-container> + <ng-template #staticCategory> + {{ recipe.strCategory }} + </ng-template> + <ng-template #editableCategory> + <input + type="text" + name="strCategory" + value="{{ recipe.strCategory }}" + (input)="updateRecipe($event)" + class="recipe__field" + /> + </ng-template> + </dd> + </dl> + </header> + <div class="recipe__body"> + <div class="recipe__preview"> + <h3>Preview</h3> + <img + [src]="getPreview()" + alt="{{ recipe.strMeal }} preview" + class="recipe__thumb" + /> + <ng-container *ngIf="isEditable"> + <label class="recipe__label btn" for="recipe-thumb"> + Upload a new image + <input + type="file" + name="recipe-thumb" + id="recipe-thumb" + accept="image/png, image/jpeg" + class="recipe__field recipe__field--file" + (change)="updatePreview($event)" + /> + </label> + </ng-container> + </div> + <div class="recipe__ingredients"> + <h3>Ingredients</h3> + <ul class="ingredients-list"> + <li + *ngFor="let ingredient of getIngredients(); index as i" + class="ingredients-list__item" + > + <ng-container + *ngIf="isEditable; then editableIngredients; else staticIngredients" + ></ng-container> + <ng-template #staticIngredients> + {{ ingredient }} + </ng-template> + <ng-template #editableIngredients> + <input + type="text" + name="strIngredient{{ i + 1 }}" + value="{{ ingredient }}" + (input)="updateRecipe($event)" + class="recipe__field" + /> + </ng-template> + </li> + </ul> + </div> + <div class="recipe__instructions"> + <h3>Instructions</h3> + <ng-container + *ngIf="isEditable; then editableInstructions; else staticInstructions" + ></ng-container> + <ng-template #editableInstructions> + <textarea + textareaResize + name="strInstructions" + (input)="updateRecipe($event)" + class="recipe__field recipe__field--textarea" + >{{ recipe.strInstructions }}</textarea + > + </ng-template> + <ng-template #staticInstructions>{{ + recipe.strInstructions + }}</ng-template> + </div> + </div> + <footer class="recipe__footer"> + <a routerLink="" class="btn"> + <span class="btn__icon">← </span>Back to your recipes + </a> + </footer> +</article> diff --git a/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.scss b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.scss new file mode 100644 index 0000000..82294b8 --- /dev/null +++ b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.scss @@ -0,0 +1,54 @@ +.recipe { + background: #fff; + border: 1px solid #d7d7d7; + border-radius: 5px; + box-shadow: 0 0 5px -3px #000000a5; + padding: clamp(4rem, 3vw, 5rem) clamp(1rem, 3vw, 2rem) clamp(2rem, 3vw, 4rem); + position: relative; + + &__body { + display: flex; + flex-flow: row wrap; + align-items: flex-start; + gap: clamp(1rem, 3vw, 2rem); + margin: 1rem 0; + } + + &__thumb { + max-width: min(100vw - 4rem, 300px); + } + + &__instructions { + flex: 0 0 100%; + white-space: pre-line; + } + + &__footer { + margin: 2rem 0 0; + } + + &__field { + all: inherit; + width: 100%; + + &--textarea { + width: 100%; + min-height: 10rem; + } + + &--file { + display: none; + } + } + + &__label { + display: block; + cursor: pointer; + width: max-content; + margin: auto; + } +} + +.ingredients-list { + padding: 0 1rem; +} diff --git a/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.spec.ts b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.spec.ts new file mode 100644 index 0000000..55a83e9 --- /dev/null +++ b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RecipeComponent } from './recipe.component'; + +describe('RecipeComponent', () => { + let component: RecipeComponent; + let fixture: ComponentFixture<RecipeComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RecipeComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RecipeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.ts b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.ts new file mode 100644 index 0000000..070220f --- /dev/null +++ b/public/projects/angular-small-apps/apps/recipes/src/app/components/recipe/recipe.component.ts @@ -0,0 +1,140 @@ +import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute, Event, Router } from '@angular/router'; +import { Recipes } from 'src/app/shared/recipes'; +import { LocalStorageService } from 'src/app/shared/services/local-storage.service'; +import { RecipesService } from 'src/app/shared/services/recipes.service'; + +@Component({ + templateUrl: './recipe.component.html', + styleUrls: ['./recipe.component.scss'], + encapsulation: ViewEncapsulation.Emulated, +}) +export class RecipeComponent implements OnInit { + recipe: Partial<Recipes> = {}; + isEditable: boolean = false; + savedRecipes: Recipes[] = this.storage.get('recipes'); + preview: string = this.recipe.strMealThumb!; + + constructor( + private storage: LocalStorageService, + private route: ActivatedRoute, + private recipes: RecipesService, + private router: Router + ) {} + + ngOnInit(): void { + const slug = this.route.snapshot.paramMap.get('slug') || ''; + this.setRecipe(slug); + this.preview = this.recipe.strMealThumb!; + } + + setRecipe(slug: string): void { + const allRecipes = this.storage.get('recipes'); + const filteredRecipes = allRecipes.filter( + (meal: Recipes) => meal.slug === slug + ); + + if (filteredRecipes.length === 0) { + const recipeId = history.state?.id; + if (recipeId) { + this.recipes.getRecipeById(recipeId).subscribe((recipes: any) => { + this.recipe = recipes.meals[0]; + this.preview = this.recipe.strMealThumb!; + }); + } else { + this.router.navigateByUrl('/404'); + } + } else { + this.recipe = filteredRecipes[0] ? filteredRecipes[0] : {}; + } + } + + getIngredients(): string[] { + const ingredients = []; + + for (let i = 1; i <= 20; i++) { + const currentIngredient = `strIngredient${i}` as keyof Recipes; + let ingredient; + + if (this.recipe[currentIngredient]) { + const currentMeasure = `strMeasure${i}` as keyof Recipes; + + if (this.recipe[currentMeasure]) { + ingredient = `${this.recipe[currentMeasure]} ${this.recipe[currentIngredient]}`; + } else { + ingredient = `${this.recipe[currentIngredient]}`; + } + } + + if (ingredient) ingredients.push(ingredient); + } + + return ingredients; + } + + isContentEditable(value: boolean): void { + this.isEditable = value; + } + + updateRecipe(e: any) { + const recipeProperty = e.target.name as keyof Recipes; + + if (!recipeProperty) { + return; + } + + this.recipe[recipeProperty] = e.target.value; + + if (recipeProperty.startsWith('strIngredient')) { + const ingredientNumber = recipeProperty.replace('strIngredient', ''); + const measureProperty = `strMeasure${ingredientNumber}` as keyof Recipes; + this.recipe[measureProperty] = undefined; + } + + const updatedRecipes = this.savedRecipes.map((recipe: Recipes) => { + if (recipe.idMeal === this.recipe.idMeal) { + return { ...this.recipe }; + } + return recipe; + }); + this.storage.set('recipes', updatedRecipes); + } + + getPreview() { + return this.preview; + } + + getImage(img: any) { + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + var ctx = canvas.getContext('2d')!; + ctx.drawImage(img, 0, 0); + + var dataURL = canvas.toDataURL('image/png'); + + return dataURL.replace(/^data:image\/(png|jpg);base64,/, ''); + } + + updatePreview(e: any) { + const newPreview = e.target.files[0]; + + if (FileReader && newPreview) { + const fileReader = new FileReader(); + fileReader.onload = (reader) => { + if (reader.target?.result) { + const updatedRecipes = this.savedRecipes.map((recipe: Recipes) => { + if (recipe.idMeal === this.recipe.idMeal) { + return { ...recipe, strMealThumb: reader.target?.result }; + } + return recipe; + }); + this.preview = reader.target?.result as string; + this.storage.set('recipes', updatedRecipes); + } + }; + fileReader.readAsDataURL(newPreview); + } + } +} |
