From 6db1357064452c1b9d0dc54e7dc319c3cff00b20 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Sat, 8 Jan 2022 22:24:34 -0900 Subject: [PATCH] feat: :sparkles: (WIP) base-shoppinglist infra (#911) * feat: :sparkles: base-shoppinglist infra (WIP) * add type checker * implement controllers * apply router fixes * add checked section hide/animation * add label support * formatting * fix overflow images * add experimental banner * fix #912 word break issue * remove any type errors * bump dependencies * remove templates * fix build errors * bump node version * fix template literal --- .github/workflows/frontend-lint.yml | 2 +- .vscode/settings.json | 1 + Dockerfile | 1 - dev/data/templates/recipes.md | 24 + frontend/Dockerfile | 4 +- .../group-multiple-purpose-labels.ts | 22 + .../class-interfaces/group-shopping-lists.ts | 60 + frontend/api/index.ts | 6 + frontend/assets/main.css | 17 + .../Domain/Recipe/RecipeActionMenu.vue | 2 +- .../components/Domain/Recipe/RecipeCard.vue | 1 + .../Domain/Recipe/RecipeCardMobile.vue | 1 + .../Recipe/RecipeCategoryTagToolPage.vue | 2 +- .../Domain/Recipe/RecipeContextMenu.vue | 50 +- .../Domain/ShoppingList/ShoppingListItem.vue | 141 ++ frontend/components/Layout/AppSidebar.vue | 8 +- .../components/global/BannerExperimental.vue | 6 + .../components/global/BaseButtonGroup.vue | 56 + .../components/global/BaseOverflowButton.vue | 13 +- frontend/composables/use-utils.ts | 2 +- frontend/layouts/default.vue | 5 +- frontend/pages/meal-plan/planner.vue | 8 +- frontend/pages/recipe/_slug/index.vue | 13 +- frontend/pages/shopping-list/_id.vue | 21 - frontend/pages/shopping-list/index.vue | 21 - frontend/pages/shopping-lists/_id.vue | 506 ++++ frontend/pages/shopping-lists/index.vue | 102 + frontend/static/svgs/shopping-cart.svg | 1 + frontend/utils/icons/icons.ts | 2 + frontend/yarn.lock | 2241 +++++++++-------- mealie/db/init_db.py | 19 +- mealie/db/models/_all_models.py | 1 + mealie/db/models/_model_utils/auto_init.py | 5 +- mealie/db/models/group/group.py | 3 + mealie/db/models/group/shopping_list.py | 67 +- mealie/db/models/labels.py | 22 + mealie/db/models/recipe/ingredient.py | 6 +- mealie/db/models/recipe/instruction.py | 10 +- mealie/repos/repository_factory.py | 17 + mealie/repos/repository_generic.py | 2 +- mealie/repos/repository_shopping_list.py | 59 + mealie/repos/seed/_abstract_seeder.py | 29 + mealie/repos/seed/init_units_foods.py | 40 - mealie/repos/seed/resources/labels/en-us.json | 65 + mealie/repos/seed/seeders.py | 61 + mealie/routes/__init__.py | 18 +- mealie/routes/_base/controller.py | 182 ++ mealie/routes/_base/dependencies.py | 58 + mealie/routes/_base/mixins.py | 109 + mealie/routes/groups/__init__.py | 17 +- mealie/routes/groups/labels.py | 71 + mealie/routes/groups/shopping_lists.py | 82 + mealie/routes/shopping_lists/__init__.py | 45 - mealie/schema/group/__init__.py | 1 + mealie/schema/group/group_shopping_list.py | 65 + mealie/schema/labels/__init__.py | 36 + mealie/schema/mapper.py | 10 +- mealie/schema/query.py | 6 + mealie/schema/recipe/recipe.py | 14 +- mealie/schema/response/__init__.py | 12 +- mealie/schema/user/user.py | 2 - .../services/group_services/shopping_lists.py | 63 + .../services/scheduler/scheduler_registry.py | 11 +- .../services/scheduler/scheduler_service.py | 15 +- poetry.lock | 199 +- pyproject.toml | 5 +- 66 files changed, 3455 insertions(+), 1311 deletions(-) create mode 100644 dev/data/templates/recipes.md create mode 100644 frontend/api/class-interfaces/group-multiple-purpose-labels.ts create mode 100644 frontend/api/class-interfaces/group-shopping-lists.ts create mode 100644 frontend/components/Domain/ShoppingList/ShoppingListItem.vue create mode 100644 frontend/components/global/BannerExperimental.vue create mode 100644 frontend/components/global/BaseButtonGroup.vue delete mode 100644 frontend/pages/shopping-list/_id.vue delete mode 100644 frontend/pages/shopping-list/index.vue create mode 100644 frontend/pages/shopping-lists/_id.vue create mode 100644 frontend/pages/shopping-lists/index.vue create mode 100644 frontend/static/svgs/shopping-cart.svg create mode 100644 mealie/db/models/labels.py create mode 100644 mealie/repos/repository_shopping_list.py create mode 100644 mealie/repos/seed/_abstract_seeder.py delete mode 100644 mealie/repos/seed/init_units_foods.py create mode 100644 mealie/repos/seed/resources/labels/en-us.json create mode 100644 mealie/repos/seed/seeders.py create mode 100644 mealie/routes/_base/controller.py create mode 100644 mealie/routes/_base/dependencies.py create mode 100644 mealie/routes/_base/mixins.py create mode 100644 mealie/routes/groups/labels.py create mode 100644 mealie/routes/groups/shopping_lists.py delete mode 100644 mealie/routes/shopping_lists/__init__.py create mode 100644 mealie/schema/group/group_shopping_list.py create mode 100644 mealie/schema/labels/__init__.py create mode 100644 mealie/schema/query.py create mode 100644 mealie/services/group_services/shopping_lists.py diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index 3b52072a5e85..32e3370c71d6 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [15] + node: [16] steps: - name: Checkout 🛎 diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b8f80bf053c..cc6bfa5fbab3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,7 @@ "python.testing.pytestArgs": ["tests"], "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, + "python.analysis.typeCheckingMode": "off", "search.mode": "reuseEditor", "vetur.validation.template": false, "python.sortImports.path": "${workspaceFolder}/.venv/bin/isort" diff --git a/Dockerfile b/Dockerfile index 99583e9041a7..c6211f4a77be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -135,7 +135,6 @@ WORKDIR / # copy frontend # COPY --from=frontend-build /app/dist $MEALIE_HOME/dist -COPY ./dev/data/templates $MEALIE_HOME/data/templates COPY ./Caddyfile $MEALIE_HOME # Grab CRF++ Model Release diff --git a/dev/data/templates/recipes.md b/dev/data/templates/recipes.md new file mode 100644 index 000000000000..6ee2d4bb6972 --- /dev/null +++ b/dev/data/templates/recipes.md @@ -0,0 +1,24 @@ + + +![Recipe Image](../../images/{{ recipe.slug }}/original.jpg) + +# {{ recipe.name }} +{{ recipe.description }} + +## Ingredients +{% for ingredient in recipe.recipeIngredient %} +- [ ] {{ ingredient }} {% endfor %} + +## Instructions +{% for step in recipe.recipeInstructions %} +- [ ] {{ step.text }} {% endfor %} + +{% for note in recipe.notes %} +**{{ note.title }}:** {{ note.text }} +{% endfor %} + +--- + +Tags: {{ recipe.tags }} +Categories: {{ recipe.categories }} +Original URL: {{ recipe.orgURL }} \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 69ea80b91faf..940b971f3d23 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts as builder +FROM node:16 as builder WORKDIR /app @@ -21,7 +21,7 @@ RUN rm -rf node_modules && \ --non-interactive \ --production=true -FROM node:15-alpine +FROM node:16-alpine WORKDIR /app diff --git a/frontend/api/class-interfaces/group-multiple-purpose-labels.ts b/frontend/api/class-interfaces/group-multiple-purpose-labels.ts new file mode 100644 index 000000000000..8d587fd9ee72 --- /dev/null +++ b/frontend/api/class-interfaces/group-multiple-purpose-labels.ts @@ -0,0 +1,22 @@ +import { BaseCRUDAPI } from "../_base"; + +const prefix = "/api"; + +const routes = { + labels: `${prefix}/groups/labels`, + labelsId: (id: string | number) => `${prefix}/groups/labels/${id}`, +}; + +export interface CreateLabel { + name: string; +} + +export interface Label extends CreateLabel { + id: string; + groupId: string; +} + +export class MultiPurposeLabelsApi extends BaseCRUDAPI { + baseRoute = routes.labels; + itemRoute = routes.labelsId; +} diff --git a/frontend/api/class-interfaces/group-shopping-lists.ts b/frontend/api/class-interfaces/group-shopping-lists.ts new file mode 100644 index 000000000000..aa3f1da4706d --- /dev/null +++ b/frontend/api/class-interfaces/group-shopping-lists.ts @@ -0,0 +1,60 @@ +import { BaseCRUDAPI } from "../_base"; +import { ApiRequestInstance } from "~/types/api"; +import { IngredientFood, IngredientUnit } from "~/types/api-types/recipe"; + +const prefix = "/api"; + +const routes = { + shoppingLists: `${prefix}/groups/shopping/lists`, + shoppingListsId: (id: string) => `${prefix}/groups/shopping/lists/${id}`, + shoppingListIdAddRecipe: (id: string, recipeId: number) => `${prefix}/groups/shopping/lists/${id}/recipe/${recipeId}`, +}; + +export interface ShoppingListItemCreate { + id: string; + shoppingListId: string; + checked: boolean; + position: number; + note: string; + quantity: number; + + isFood: boolean; + unit: IngredientUnit | null; + food: IngredientFood | null; + + labelId: string | null; + label?: { + id: string; + name: string; + }; +} + +export interface ShoppingListCreate { + name: string; +} + +export interface ShoppingListSummary extends ShoppingListCreate { + id: string; + groupId: string; +} + +export interface ShoppingList extends ShoppingListSummary { + listItems: ShoppingListItemCreate[]; +} + +export class ShoppingListsApi extends BaseCRUDAPI { + baseRoute = routes.shoppingLists; + itemRoute = routes.shoppingListsId; + + async addRecipe(itemId: string, recipeId: number) { + return await this.requests.post(routes.shoppingListIdAddRecipe(itemId, recipeId), {}); + } +} + +export class ShoppingApi { + public lists: ShoppingListsApi; + + constructor(requests: ApiRequestInstance) { + this.lists = new ShoppingListsApi(requests); + } +} diff --git a/frontend/api/index.ts b/frontend/api/index.ts index e3e4189df549..5de55dfcf5ff 100644 --- a/frontend/api/index.ts +++ b/frontend/api/index.ts @@ -21,6 +21,8 @@ import { AdminAPI } from "./admin-api"; import { ToolsApi } from "./class-interfaces/tools"; import { GroupMigrationApi } from "./class-interfaces/group-migrations"; import { GroupReportsApi } from "./class-interfaces/group-reports"; +import { ShoppingApi } from "./class-interfaces/group-shopping-lists"; +import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose-labels"; import { ApiRequestInstance } from "~/types/api"; class Api { @@ -46,6 +48,8 @@ class Api { public groupReports: GroupReportsApi; public grouperServerTasks: GroupServerTaskAPI; public tools: ToolsApi; + public shopping: ShoppingApi; + public multiPurposeLabels: MultiPurposeLabelsApi; // Utils public upload: UploadFile; @@ -74,6 +78,8 @@ class Api { // Group this.groupMigration = new GroupMigrationApi(requests); this.groupReports = new GroupReportsApi(requests); + this.shopping = new ShoppingApi(requests); + this.multiPurposeLabels = new MultiPurposeLabelsApi(requests); // Admin this.events = new EventsAPI(requests); diff --git a/frontend/assets/main.css b/frontend/assets/main.css index b3ae261c65d8..5dd067b74ef6 100644 --- a/frontend/assets/main.css +++ b/frontend/assets/main.css @@ -22,3 +22,20 @@ .theme--dark.v-card { background-color: #2b2b2b !important; } + +.left-border { + border-left: 5px solid var(--v-primary-base) !important; +} + +.handle { + cursor: grab; +} + +.hidden { + visibility: hidden !important; +} + +.v-card__text, +.v-card__title { + word-break: normal !important; +} diff --git a/frontend/components/Domain/Recipe/RecipeActionMenu.vue b/frontend/components/Domain/Recipe/RecipeActionMenu.vue index ad3b3dfe2d86..4ef8d50826aa 100644 --- a/frontend/components/Domain/Recipe/RecipeActionMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeActionMenu.vue @@ -58,7 +58,6 @@ show-print :menu-top="false" :slug="slug" - :name="name" :menu-icon="$globals.icons.mdiDotsHorizontal" fab color="info" @@ -69,6 +68,7 @@ edit: false, download: true, mealplanner: true, + shoppingList: true, print: true, share: true, }" diff --git a/frontend/components/Domain/Recipe/RecipeCard.vue b/frontend/components/Domain/Recipe/RecipeCard.vue index b9819869685a..474b9fbfb68d 100644 --- a/frontend/components/Domain/Recipe/RecipeCard.vue +++ b/frontend/components/Domain/Recipe/RecipeCard.vue @@ -38,6 +38,7 @@ edit: true, download: true, mealplanner: true, + shoppingList: true, print: false, share: true, }" diff --git a/frontend/components/Domain/Recipe/RecipeCardMobile.vue b/frontend/components/Domain/Recipe/RecipeCardMobile.vue index 3b3f6492593b..597473a8d3d9 100644 --- a/frontend/components/Domain/Recipe/RecipeCardMobile.vue +++ b/frontend/components/Domain/Recipe/RecipeCardMobile.vue @@ -39,6 +39,7 @@ edit: true, download: true, mealplanner: true, + shoppingList: true, print: false, share: true, }" diff --git a/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue b/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue index f5d3057ed33e..e5b4b943fd1c 100644 --- a/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue +++ b/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue @@ -11,7 +11,7 @@ - + {{ icon }} diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue index 12f29ed7346c..db7413f820b9 100644 --- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue @@ -46,6 +46,21 @@ + + + + + {{ list.name }} + + + + diff --git a/frontend/components/Layout/AppSidebar.vue b/frontend/components/Layout/AppSidebar.vue index b8b0bdfeea39..69e0f78b8c1b 100644 --- a/frontend/components/Layout/AppSidebar.vue +++ b/frontend/components/Layout/AppSidebar.vue @@ -60,7 +60,9 @@