Major overhaul (`RecipeView` update, sections support, recipes list view, ...)
Topic and Scope
A lot of the UI is touched, with a focus on the RecipeView.
This PRs goal is manifold:
- Introduction of model classes for
schema.orgentities should provide a cleaner way of dealing with recipe data- The correct format of the received JSON data is checked during parsing
- The model classes support complex scenarios like (nested) sections in recipe instructions. Those could have their own set of ingredients and tools by collecting those from child directions, tips, ...
- Added "support" for (until now) unused property
supply, which would allow structuring ingredients
- List of recipes:
- Adding a list view as an alternative to the grid view currently used for selecting recipes. This was requested (e.g., #1244) to be more in line with other Nextcloud apps. However, further appointments with the NC design team surfaced more votes towards the previous grid-style view. Since the implementation was already largely done, there are now both available, though the list view is currently hidden from the user.
- complete rework of filter logic similar to github
- Restructured paths
- With the introduction of the list view which required additional
<router-view>s the need for a different structure of the URL
- With the introduction of the list view which required additional
Some minor changes/fixes that popped up during development include additional helper functions, file restructuring, improving the Jest config and reworking the RecipeView:
- Updated image: No collapsing anymore, click opens image in modal
- Improved spacing
- Improved headline sizing
- Updated recipe-yield input (updated style of buttons and input, changes from default value are highlighted)
- Updated main timer visuals (less intrusive, running timer is highlighted with larger font)
- Updated recipe instructions
- moved to separate component
- non-bold font consistent with rest of the recipe, completed ingredients get muted text color
- ingredients in sections are indented (solely CSS, JS props for indenting are removed)
- Unify indents of ingredients, tools, and nutrition information
- Add dynamic padding and max size to content, such that on large displays the recipe info does not get "stretched" to wide
- Move certain information to a sidebar in the RecipeView
- AppControls omit redundant "Viewing recipe" and adds currently viewed section's name as subtitle. The edit button resizes to icon-only for small viewports
- many more ...
Related: #382 #428 #311 #135 #1875 #1244
Closes #1855 closes #2114
Currently supported in RecipeView
- [x] More complex structures for recipe instructions
- [x] Top-level
HowToSections- [x]
HowToSection's title - [x]
HowToSection's description - [x]
HowToSection's sub-steps - [x]
HowToTips inHowToSection
- [x]
- [x] Top-level array of mixed
HowToSections,HowToSteps and strings - Nested
HowToSections (At some point this might even be confusing for the user) Note: In a meeting w/ the NC design team, we decided not to support nested sections (for now). - [x] Display summarized supply of all nested
HowToDirections in aHowToSection - [x] Display summarized tools of all nested
HowToDirections in aHowToSection - [x]
HowToTips inHowToStep
- [x] Top-level
- [x]
HowToToolobjects inRecipe - [x]
HowToSupplyobjects inRecipe - [x]
NutritionInformationobjects inRecipe
Concerns/issues
Currently the UI does not support
- viewing recipes with sections, tips, etc. in the full glory (see above)
- editing recipes with sections, tips, etc.
We need to consider how to handle recipes where both supply and recipeIngredient are defined.
Supported properties are checked for their correctness, but additional properties, e.g., those allowed by the schema.org standard that are not supported in our app are currently ignored.
Recipes List
- There should be an API endpoint to allow renaming a recipe so we don't need to down-/upload the complete recipe just to change the name
Restructured paths
With the introduction of the list view which required additional <router-view>s the need for a different structure of the URLs appeared that allows defining the selected (filtered) list of recipes as well as a selected recipe. Currently the routes look like:
category/:valuetags/:valuesearch/:value
for filtering/searching the available recipes. These paths can remain the same. However if additional filters are applied, they are added as query parameters.
category/:value?q=tag%3A%2522spicy%2522(?q=tag:"spicy")- etc.
When viewing a recipe the user was directed to
recipe/:id
This would loose the information that the recipe was, e.g., selected from a list of recipes within a specific category, maybe filtered down by certain parameters. This PR introduces new paths in the form of
category/:value/:id?q=tag%3A%2522spicy%2522(?q=tag:"spicy")`- etc.
where id is the recipe identifier. Navigating to the URL will show the same view as before when the list view is selected: A filtered list with the recipe open next to it. For editing the recipe the path
category/:value/:id/edit?q=tag%3A%2522spicy%2522- etc.
is introduced.
Try out locally
Although the backend does not support the current structure it is possible to test this locally. You may start with copying the JSON code to a recipe.json file in a new subdirectory of the Recipes/ folder. If you wish you can download some image and add it as full.jpg to the same folder.
Then you need to make two tweaks to the PHP code. In FixInstructionsFilter.php early return the apply function
public function apply(array &$json): bool {
return true;
// Rest does not matter
}
and in FixToolsFilter.php in the apply function remove the else block and early return
// else {
// $tools = array_map(function ($t) {
// $t = trim($t);
// $t = $this->textCleaner->cleanUp($t, false);
// return $t;
// }, $json[self::TOOLS]);
// $tools = array_filter($tools, fn ($t) => ($t));
// ksort($tools);
// $tools = array_values($tools);
// }
return false;
Preview
Current state of the RecipeView. The following JSON produces below image
Recipe JSON
{
"@context": "https://schema.org/",
"@type": "Recipe",
"name": "Chocolate Cake",
"description": "A delicious and moist chocolate cake recipe.",
"image": "cake.jpg",
"recipeCategory": "Dessert",
"cookTime": "PT40M",
"prepTime": "PT20M",
"totalTime": "PT1H",
"recipeYield": 12,
"url": "https://my-yummy-recipes.io/choc-cake/",
"keywords": ["cake", "chocolate", "juicy", "dessert"],
"recipeIngredient": [
"1/2 teaspoon salt",
"2 large eggs",
"1 cup milk",
"1/2 cup vegetable oil",
"2 teaspoons vanilla extract",
"1 cup boiling water"
],
"supply" : [
{
"@type": "HowToSupply",
"name": "all-purpose flour",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 2,
"unitText": "cups"
},
"description": "Main ingredient for the recipe"
},
{
"@type": "HowToSupply",
"name": "granulated sugar",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 1,
"unitText": "cup"
},
"description": "Sweetens the recipe"
},
{
"@type": "HowToSupply",
"name": "unsweetened cocoa powder",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 1,
"unitText": "cup"
}
},
{
"@type": "HowToSupply",
"name": "baking powder",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 1,
"unitText": "teaspoon"
}
},
{
"@type": "HowToSupply",
"name": "baking soda"
}
],
"tool": [
"mixing bowl",
"whisk",
"sifter",
{
"@type": "HowToTool",
"name": "measuring cups",
"requiredQuantity": 2,
"description": "grandma's **nice** ones"
},
"measuring spoons",
"cake pans"
],
"nutrition": {
"@type": "NutritionInformation",
"servingSize": "1 slice",
"calories": "250 calories",
"fatContent": "12 grams",
"carbohydrateContent": "30 grams",
"proteinContent": "8 grams",
"sodiumContent": "500 milligrams",
"fiberContent": "2 grams",
"sugarContent": "5 grams"
},
"recipeInstructions": [
{
"@type": "HowToSection",
"name": "Prepare Dry Ingredients",
"description": "Prepare the dry Ingredients by mixing them together. Follow the steps closely to get a perfect result!",
"itemListElement": [
{
"@type": "HowToStep",
"text": "Mix together the flour, sugar, cocoa, baking powder, baking soda, and salt in a large bowl.",
"position": 1,
"timeRequired": "PT5M"
},
{
"@type": "HowToStep",
"text": "Sift the dry ingredients to remove any lumps.",
"position": 2,
"timeRequired": "PT2M",
"itemListElement": [
{
"@type": "HowToDirection",
"text": "Pour dry ingredients in a sieve",
"timeRequired": "PT1M",
"tool": [
"mixing bowl",
"sifter",
{
"@type": "HowToTool",
"name": "measuring cups",
"requiredQuantity": 2,
"description": "grandma's **nice** ones"
},
"measuring spoons"
]
},
{
"@type": "HowToDirection",
"text": "Move a spoon clockwise in the sieve to apply some pressure on the ingredients.",
"timeRequired": "PT1M",
"supply" : [
{
"@type": "HowToSupply",
"name": "all-purpose flour",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 2,
"unitText": "cups"
},
"description": "Main ingredient for the recipe"
},
{
"@type": "HowToSupply",
"name": "granulated sugar",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 1,
"unitText": "cup"
},
"description": "Sweetens the recipe"
}
]
},
{
"@type": "HowToTip",
"text": "Use a hair sieve"
}
]
},
{
"@type": "HowToStep",
"position": 3,
"timeRequired": "PT1M",
"itemListElement": [
{
"@type": "HowToDirection",
"text": "Set aside the dry ingredients for later use.",
"timeRequired": "PT1M",
"supply" : [
{
"@type": "HowToSupply",
"name": "baking powder",
"requiredQuantity": {
"@type": "QuantitativeValue",
"value": 1,
"unitText": "teaspoon"
}
},
{
"@type": "HowToSupply",
"name": "baking soda"
}
]
}
]
}
]
},
{
"@type": "HowToSection",
"name": "Wet ingredients",
"description": "Prepare the Wet Ingredients",
"itemListElement": [
{
"@type": "HowToStep",
"text": "In a separate bowl, whisk together the eggs, milk, oil, and vanilla extract.",
"position": 1,
"timeRequired": "PT5M"
},
{
"@type": "HowToStep",
"text": "Gradually add the wet ingredients to the dry ingredients, stirring until well combined.",
"position": 2,
"timeRequired": "PT3M"
},
{
"@type": "HowToStep",
"text": "Pour in the boiling water and mix until smooth.",
"position": 3,
"timeRequired": "PT2M"
}
]
}
]
}
RecipeView:
Details sidebar opened:
Recipe sections
Clicking an image opens a modal with a slideshow of the full images:
Wide screen
The width of the recipe content is limited to maintain readability
Narrow screen
List view
Filtering:
Filter modal opened:
Filter selected (URL gets updated to ${HOST}/index.php/apps/cookbook/#/category/Dinner/412?q=tag%3A%2522spicy%2522):
Formal requirements
There are some formal requirements that should be satisfied. Please mark those by checking the corresponding box.
- [ ] I did check that the app can still be opened and does not throw any browser logs
- [ ] I created tests for newly added PHP code (check this if no PHP changes were made)
- [ ] I updated the OpenAPI specs and added an entry to the API changelog (check if API was not modified)
- [ ] I notified the matrix channel if I introduced an API change
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 80.07%. Comparing base (
153511f) to head (54de35b). Report is 164 commits behind head on master.
:exclamation: Current head 54de35b differs from pull request most recent head 6c27422. Consider uploading reports for the commit 6c27422 to get more accurate results
Additional details and impacted files
@@ Coverage Diff @@
## master #2115 +/- ##
=======================================
Coverage 80.07% 80.07%
=======================================
Files 92 92
Lines 2650 2650
=======================================
Hits 2122 2122
Misses 528 528
| Flag | Coverage Δ | |
|---|---|---|
| integration | 21.43% <ø> (ø) |
|
| migration | 5.69% <ø> (ø) |
|
| unittests | 57.09% <ø> (ø) |
Flags with carried forward coverage won't be shown. Click here to find out more.
Test Results
12 files 572 suites 1m 31s :stopwatch: 552 tests 552 :white_check_mark: 0 :zzz: 0 :x: 2 208 runs 2 207 :white_check_mark: 1 :zzz: 0 :x:
Results for commit 54de35b6.
:recycle: This comment has been updated with latest results.