From 12f480eb75bcc6a45f02dc917856bec472fd95e7 Mon Sep 17 00:00:00 2001
From: Hayden <64056131+hay-kot@users.noreply.github.com>
Date: Fri, 3 Jun 2022 20:12:32 -0800
Subject: [PATCH] refactor: unify recipe-organizer components (#1340)
* use generic context menu
* implement organizer stores
* add basic organizer types
* refactor selectors to apply for all organizers
* remove legacy organizer composables
---
.../Domain/Group/GroupMealPlanRuleForm.vue | 13 +-
.../Domain/Recipe/RecipeCategoryTagDialog.vue | 111 ----------
.../Recipe/RecipeCategoryTagSelector.vue | 164 --------------
.../RecipeCategoryTagToolContextMenu.vue | 207 ------------------
.../Recipe/RecipeCategoryTagToolPage.vue | 123 -----------
.../Domain/Recipe/RecipeOrganizerDialog.vue | 152 +++++++++++++
.../Domain/Recipe/RecipeOrganizerPage.vue | 139 ++++++++++++
.../Domain/Recipe/RecipeOrganizerSelector.vue | 88 ++++++--
frontend/components/global/ContextMenu.vue | 56 +++++
.../partials/use-actions-factory.ts | 13 +-
frontend/composables/recipes/index.ts | 1 -
.../recipes/use-tags-categories.ts | 65 ------
frontend/composables/store/index.ts | 3 +
.../composables/store/use-category-store.ts | 47 ++++
frontend/composables/store/use-tag-store.ts | 47 ++++
frontend/composables/store/use-tool-store.ts | 49 +++++
frontend/composables/use-context-presents.ts | 30 +++
frontend/pages/group/cookbooks.vue | 22 +-
frontend/pages/group/data/recipes.vue | 8 +-
frontend/pages/recipe/_slug/index.vue | 45 ++--
frontend/pages/recipe/create/bulk.vue | 17 +-
frontend/pages/recipes/categories/index.vue | 46 ++--
frontend/pages/recipes/tags/index.vue | 46 ++--
frontend/pages/recipes/tools/index.vue | 48 ++--
frontend/pages/search.vue | 29 ++-
frontend/types/recipe/organizers.ts | 7 +
26 files changed, 719 insertions(+), 857 deletions(-)
delete mode 100644 frontend/components/Domain/Recipe/RecipeCategoryTagDialog.vue
delete mode 100644 frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue
delete mode 100644 frontend/components/Domain/Recipe/RecipeCategoryTagToolContextMenu.vue
delete mode 100644 frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue
create mode 100644 frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
create mode 100644 frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
create mode 100644 frontend/components/global/ContextMenu.vue
delete mode 100644 frontend/composables/recipes/use-tags-categories.ts
create mode 100644 frontend/composables/store/use-category-store.ts
create mode 100644 frontend/composables/store/use-tag-store.ts
create mode 100644 frontend/composables/store/use-tool-store.ts
create mode 100644 frontend/composables/use-context-presents.ts
create mode 100644 frontend/types/recipe/organizers.ts
diff --git a/frontend/components/Domain/Group/GroupMealPlanRuleForm.vue b/frontend/components/Domain/Group/GroupMealPlanRuleForm.vue
index 04f3efba034d..81d4b570b12c 100644
--- a/frontend/components/Domain/Group/GroupMealPlanRuleForm.vue
+++ b/frontend/components/Domain/Group/GroupMealPlanRuleForm.vue
@@ -5,8 +5,8 @@
-
-
+
+
{{ inputDay === "unset" ? "This rule will apply to all days" : `This rule applies on ${inputDay}s` }}
{{ inputEntryType === "unset" ? "for all meal types" : ` and for ${inputEntryType} meal types` }}
@@ -15,7 +15,8 @@
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue b/frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue
deleted file mode 100644
index 43160f93a6e5..000000000000
--- a/frontend/components/Domain/Recipe/RecipeCategoryTagSelector.vue
+++ /dev/null
@@ -1,164 +0,0 @@
-//TODO: Prevent fetching Categories/Tags multiple time when selector is on page multiple times
-
-
-
-
-
- {{ data.item.name || data.item }}
-
-
-
-
-
-
-
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeCategoryTagToolContextMenu.vue b/frontend/components/Domain/Recipe/RecipeCategoryTagToolContextMenu.vue
deleted file mode 100644
index 6016e3fd5187..000000000000
--- a/frontend/components/Domain/Recipe/RecipeCategoryTagToolContextMenu.vue
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
- Are you sure you want to delete this {{ itemName }}?
-
-
-
-
- {{ icon }}
-
-
-
-
-
-
- {{ item.icon }}
-
-
- {{ item.title }}
-
-
-
-
-
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue b/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue
deleted file mode 100644
index 96dfa1505f37..000000000000
--- a/frontend/components/Domain/Recipe/RecipeCategoryTagToolPage.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
- {{ icon }}
-
- {{ headline }}
-
-
-
-
-
-
-
-
-
- {{ icon }}
-
-
- {{ item.name }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
new file mode 100644
index 000000000000..e0db9549798c
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+ {{ itemType === Organizer.Tool ? $globals.icons.potSteam : $globals.icons.tags }}
+
+
+
+ {{ properties.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue b/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
new file mode 100644
index 000000000000..a581d6701714
--- /dev/null
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+ Are you sure you want to delete this {{ deleteTarget.name }}?
+
+
+
+ {{ icon }}
+
+
+
+ {{ headline }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ icon }}
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
index 26a55c5a881e..9ac2cddcf93d 100644
--- a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
@@ -1,14 +1,14 @@
@@ -17,6 +17,7 @@
:key="data.index"
class="ma-1"
:input-value="data.selected"
+ small
close
label
color="accent"
@@ -26,41 +27,55 @@
{{ data.item.name || data.item }}
+
+
+
+ {{ $globals.icons.create }}
+
+
+
+
diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts
index fbb8e2d6feb2..884f6217b26f 100644
--- a/frontend/composables/partials/use-actions-factory.ts
+++ b/frontend/composables/partials/use-actions-factory.ts
@@ -3,7 +3,7 @@ import { useAsyncKey } from "../use-utils";
import { BaseCRUDAPI } from "~/api/_base";
type BoundT = {
- id: string | number;
+ id?: string | number;
};
interface StoreActions {
@@ -29,7 +29,12 @@ export function useStoreActions(
loading.value = true;
const allItems = useAsync(async () => {
const { data } = await api.getAll();
- return data;
+
+ if (allRef) {
+ allRef.value = data;
+ }
+
+ return data ?? [];
}, useAsyncKey());
loading.value = false;
@@ -73,8 +78,8 @@ export function useStoreActions(
async function deleteOne(id: string | number) {
loading.value = true;
- const { data } = await api.deleteOne(id);
- if (data && allRef?.value) {
+ const { response } = await api.deleteOne(id);
+ if (response && allRef?.value) {
refresh();
}
loading.value = false;
diff --git a/frontend/composables/recipes/index.ts b/frontend/composables/recipes/index.ts
index 2f46abef0038..364ffe915575 100644
--- a/frontend/composables/recipes/index.ts
+++ b/frontend/composables/recipes/index.ts
@@ -1,7 +1,6 @@
export { useFraction } from "./use-fraction";
export { useRecipe } from "./use-recipe";
export { useRecipes, recentRecipes, allRecipes, useLazyRecipes, useSorter } from "./use-recipes";
-export { useTags, useCategories, allCategories, allTags } from "./use-tags-categories";
export { parseIngredientText } from "./use-recipe-ingredients";
export { useRecipeSearch } from "./use-recipe-search";
export { useTools } from "./use-recipe-tools";
diff --git a/frontend/composables/recipes/use-tags-categories.ts b/frontend/composables/recipes/use-tags-categories.ts
deleted file mode 100644
index f022f8b6ba42..000000000000
--- a/frontend/composables/recipes/use-tags-categories.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { Ref, ref, useAsync } from "@nuxtjs/composition-api";
-import { useUserApi } from "../api";
-import { useAsyncKey } from "../use-utils";
-import { CategoriesAPI } from "~/api/class-interfaces/organizer-categories";
-import { TagsAPI } from "~/api/class-interfaces/organizer-tags";
-import { RecipeTag, RecipeCategory } from "~/types/api-types/recipe";
-
-export const allCategories = ref([]);
-export const allTags = ref([]);
-
-function baseTagsCategories(
- reference: Ref | Ref,
- api: TagsAPI | CategoriesAPI
-) {
- function useAsyncGetAll() {
- useAsync(async () => {
- await refreshItems();
- }, useAsyncKey());
- }
-
- async function refreshItems() {
- const { data } = await api.getAll();
- // @ts-ignore hotfix
- reference.value = data;
- }
-
- async function createOne(payload: { name: string }) {
- const { data } = await api.createOne(payload);
- if (data) {
- refreshItems();
- }
- }
-
- async function deleteOne(slug: string) {
- const { data } = await api.deleteOne(slug);
- if (data) {
- refreshItems();
- }
- }
-
- async function updateOne(slug: string, payload: { name: string }) {
- // @ts-ignore // TODO: Fix Typescript Issue - Unsure how to fix this while also keeping mixins
- const { data } = await api.updateOne(slug, payload);
- if (data) {
- refreshItems();
- }
- }
-
- return { useAsyncGetAll, refreshItems, createOne, deleteOne, updateOne };
-}
-
-export const useTags = function () {
- const api = useUserApi();
- return {
- allTags,
- ...baseTagsCategories(allTags, api.tags),
- };
-};
-export const useCategories = function () {
- const api = useUserApi();
- return {
- allCategories,
- ...baseTagsCategories(allCategories, api.categories),
- };
-};
diff --git a/frontend/composables/store/index.ts b/frontend/composables/store/index.ts
index 6a01f40213fc..e00aba57e1a1 100644
--- a/frontend/composables/store/index.ts
+++ b/frontend/composables/store/index.ts
@@ -1,3 +1,6 @@
export { useFoodStore, useFoodData } from "./use-food-store";
export { useUnitStore, useUnitData } from "./use-unit-store";
export { useLabelStore, useLabelData } from "./use-label-store";
+export { useToolStore, useToolData } from "./use-tool-store";
+export { useCategoryStore, useCategoryData } from "./use-category-store";
+export { useTagStore, useTagData } from "./use-tag-store";
diff --git a/frontend/composables/store/use-category-store.ts b/frontend/composables/store/use-category-store.ts
new file mode 100644
index 000000000000..10206d0d7bfc
--- /dev/null
+++ b/frontend/composables/store/use-category-store.ts
@@ -0,0 +1,47 @@
+import { reactive, ref, Ref } from "@nuxtjs/composition-api";
+import { useStoreActions } from "../partials/use-actions-factory";
+import { useUserApi } from "~/composables/api";
+import { RecipeCategory } from "~/types/api-types/admin";
+
+const categoryStore: Ref = ref([]);
+
+export function useCategoryData() {
+ const data = reactive({
+ id: "",
+ name: "",
+ slug: undefined,
+ });
+
+ function reset() {
+ data.id = "";
+ data.name = "";
+ data.slug = undefined;
+ }
+
+ return {
+ data,
+ reset,
+ };
+}
+
+export function useCategoryStore() {
+ const api = useUserApi();
+ const loading = ref(false);
+
+ const actions = {
+ ...useStoreActions(api.categories, categoryStore, loading),
+ flushStore() {
+ categoryStore.value = [];
+ },
+ };
+
+ if (!categoryStore.value || categoryStore.value?.length === 0) {
+ actions.getAll();
+ }
+
+ return {
+ items: categoryStore,
+ actions,
+ loading,
+ };
+}
diff --git a/frontend/composables/store/use-tag-store.ts b/frontend/composables/store/use-tag-store.ts
new file mode 100644
index 000000000000..401ef612ee6d
--- /dev/null
+++ b/frontend/composables/store/use-tag-store.ts
@@ -0,0 +1,47 @@
+import { reactive, ref, Ref } from "@nuxtjs/composition-api";
+import { useStoreActions } from "../partials/use-actions-factory";
+import { useUserApi } from "~/composables/api";
+import { RecipeTag } from "~/types/api-types/admin";
+
+const items: Ref = ref([]);
+
+export function useTagData() {
+ const data = reactive({
+ id: "",
+ name: "",
+ slug: undefined,
+ });
+
+ function reset() {
+ data.id = "";
+ data.name = "";
+ data.slug = undefined;
+ }
+
+ return {
+ data,
+ reset,
+ };
+}
+
+export function useTagStore() {
+ const api = useUserApi();
+ const loading = ref(false);
+
+ const actions = {
+ ...useStoreActions(api.tags, items, loading),
+ flushStore() {
+ items.value = [];
+ },
+ };
+
+ if (!items.value || items.value?.length === 0) {
+ actions.getAll();
+ }
+
+ return {
+ items,
+ actions,
+ loading,
+ };
+}
diff --git a/frontend/composables/store/use-tool-store.ts b/frontend/composables/store/use-tool-store.ts
new file mode 100644
index 000000000000..1a5fcb5c47f4
--- /dev/null
+++ b/frontend/composables/store/use-tool-store.ts
@@ -0,0 +1,49 @@
+import { reactive, ref, Ref } from "@nuxtjs/composition-api";
+import { useStoreActions } from "../partials/use-actions-factory";
+import { useUserApi } from "~/composables/api";
+import { RecipeTool } from "~/types/api-types/recipe";
+
+const toolStore: Ref = ref([]);
+
+export function useToolData() {
+ const data = reactive({
+ id: "",
+ name: "",
+ slug: undefined,
+ onHand: false,
+ });
+
+ function reset() {
+ data.id = "";
+ data.name = "";
+ data.slug = undefined;
+ data.onHand = false;
+ }
+
+ return {
+ data,
+ reset,
+ };
+}
+
+export function useToolStore() {
+ const api = useUserApi();
+ const loading = ref(false);
+
+ const actions = {
+ ...useStoreActions(api.tools, toolStore, loading),
+ flushStore() {
+ toolStore.value = [];
+ },
+ };
+
+ if (!toolStore.value || toolStore.value?.length === 0) {
+ actions.getAll();
+ }
+
+ return {
+ items: toolStore,
+ actions,
+ loading,
+ };
+}
diff --git a/frontend/composables/use-context-presents.ts b/frontend/composables/use-context-presents.ts
new file mode 100644
index 000000000000..42b3b7390903
--- /dev/null
+++ b/frontend/composables/use-context-presents.ts
@@ -0,0 +1,30 @@
+import { useContext } from "@nuxtjs/composition-api";
+
+export interface ContextMenuItem {
+ title: string;
+ icon: string;
+ event: string;
+ color?: string;
+}
+
+export function useContextPresets(): { [key: string]: ContextMenuItem } {
+ const { $globals, i18n } = useContext();
+
+ return {
+ delete: {
+ title: i18n.tc("general.delete"),
+ icon: $globals.icons.delete,
+ event: "delete",
+ },
+ edit: {
+ title: i18n.tc("general.edit"),
+ icon: $globals.icons.edit,
+ event: "edit",
+ },
+ save: {
+ title: i18n.tc("general.save"),
+ icon: $globals.icons.save,
+ event: "save",
+ },
+ };
+}
diff --git a/frontend/pages/group/cookbooks.vue b/frontend/pages/group/cookbooks.vue
index 64791860ddcc..f5ad3847a89a 100644
--- a/frontend/pages/group/cookbooks.vue
+++ b/frontend/pages/group/cookbooks.vue
@@ -36,14 +36,9 @@
-
-
-
-
+
+
+
Public Cookbook
@@ -102,26 +97,15 @@ import { defineComponent } from "@nuxtjs/composition-api";
import draggable from "vuedraggable";
import { useCookbooks } from "@/composables/use-group-cookbooks";
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
-import { useCategories, useTags, useTools } from "~/composables/recipes";
export default defineComponent({
components: { draggable, RecipeOrganizerSelector },
setup() {
const { cookbooks, actions } = useCookbooks();
- const { tools } = useTools();
- const { allCategories, useAsyncGetAll: getAllCategories } = useCategories();
- const { allTags, useAsyncGetAll: getAllTags } = useTags();
-
- getAllCategories();
- getAllTags();
-
return {
- allCategories,
- allTags,
cookbooks,
actions,
- tools,
};
},
head() {
diff --git a/frontend/pages/group/data/recipes.vue b/frontend/pages/group/data/recipes.vue
index f4cd1d7ab863..8cc7626a2328 100644
--- a/frontend/pages/group/data/recipes.vue
+++ b/frontend/pages/group/data/recipes.vue
@@ -22,10 +22,10 @@
@submit="dialog.callback"
>
-
+
-
+
Are you sure you want to delete the following recipes? This action cannot be undone.
@@ -149,7 +149,7 @@
diff --git a/frontend/pages/recipes/tags/index.vue b/frontend/pages/recipes/tags/index.vue
index ea99bbaba65f..cc9d59e43e7d 100644
--- a/frontend/pages/recipes/tags/index.vue
+++ b/frontend/pages/recipes/tags/index.vue
@@ -1,44 +1,36 @@
-
+
+ Tags
+
diff --git a/frontend/pages/recipes/tools/index.vue b/frontend/pages/recipes/tools/index.vue
index 3429a2417dbf..659cdd860ac8 100644
--- a/frontend/pages/recipes/tools/index.vue
+++ b/frontend/pages/recipes/tools/index.vue
@@ -1,44 +1,38 @@
-
+
+ Tools
+
diff --git a/frontend/pages/search.vue b/frontend/pages/search.vue
index d3e340234d9e..74e6ec6d49f9 100644
--- a/frontend/pages/search.vue
+++ b/frontend/pages/search.vue
@@ -35,23 +35,30 @@
-
-
@@ -106,7 +113,7 @@
import Fuse from "fuse.js";
import { defineComponent, toRefs, computed, reactive } from "@nuxtjs/composition-api";
import RecipeSearchFilterSelector from "~/components/Domain/Recipe/RecipeSearchFilterSelector.vue";
-import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
+import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useRecipes, allRecipes } from "~/composables/recipes";
import { RecipeSummary } from "~/types/api-types/recipe";
@@ -121,7 +128,7 @@ interface GenericFilter {
export default defineComponent({
components: {
- RecipeCategoryTagSelector,
+ RecipeOrganizerSelector,
RecipeSearchFilterSelector,
RecipeCardSection,
},
diff --git a/frontend/types/recipe/organizers.ts b/frontend/types/recipe/organizers.ts
new file mode 100644
index 000000000000..ba949d91d715
--- /dev/null
+++ b/frontend/types/recipe/organizers.ts
@@ -0,0 +1,7 @@
+export type RecipeOrganizer = "categories" | "tags" | "tools";
+
+export enum Organizer {
+ Category = "categories",
+ Tag = "tags",
+ Tool = "tools",
+}