Recipe Context Menu

This commit is contained in:
Kuchenpirat 2025-07-30 10:10:03 +00:00
commit 1cb1a0552b

View file

@ -100,7 +100,7 @@
:open-on-hover="$vuetify.display.mdAndUp"
content-class="d-print-none"
>
<template #activator="{ props }">
<template #activator="{ props: activatorProps }">
<v-btn
icon
:variant="fab ? 'flat' : undefined"
@ -108,7 +108,7 @@
:size="fab ? 'small' : undefined"
:color="fab ? 'info' : 'secondary'"
:fab="fab"
v-bind="props"
v-bind="activatorProps"
@click.prevent
>
<v-icon
@ -150,7 +150,7 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import RecipeDialogAddToShoppingList from "./RecipeDialogAddToShoppingList.vue";
import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue";
import RecipeDialogShare from "./RecipeDialogShare.vue";
@ -186,16 +186,22 @@ export interface ContextMenuItem {
isPublic: boolean;
}
export default defineNuxtComponent({
components: {
RecipeDialogAddToShoppingList,
RecipeDialogPrintPreferences,
RecipeDialogShare,
},
props: {
useItems: {
type: Object as () => ContextMenuIncludes,
default: () => ({
interface Props {
useItems?: ContextMenuIncludes;
appendItems?: ContextMenuItem[];
leadingItems?: ContextMenuItem[];
menuTop?: boolean;
fab?: boolean;
color?: string;
slug: string;
menuIcon?: string | null;
name: string;
recipe?: Recipe;
recipeId: string;
recipeScale?: number;
}
const props = withDefaults(defineProps<Props>(), {
useItems: () => ({
delete: true,
edit: true,
download: true,
@ -207,78 +213,41 @@ export default defineNuxtComponent({
share: true,
recipeActions: true,
}),
},
// Append items are added at the end of the useItems list
appendItems: {
type: Array as () => ContextMenuItem[],
default: () => [],
},
// Append items are added at the beginning of the useItems list
leadingItems: {
type: Array as () => ContextMenuItem[],
default: () => [],
},
menuTop: {
type: Boolean,
default: true,
},
fab: {
type: Boolean,
default: false,
},
color: {
type: String,
default: "primary",
},
slug: {
type: String,
required: true,
},
menuIcon: {
type: String,
default: null,
},
name: {
required: true,
type: String,
},
recipe: {
type: Object as () => Recipe,
default: undefined,
},
recipeId: {
required: true,
type: String,
},
recipeScale: {
type: Number,
default: 1,
},
},
emits: ["delete"],
setup(props, context) {
appendItems: () => [],
leadingItems: () => [],
menuTop: true,
fab: false,
color: "primary",
menuIcon: null,
recipe: undefined,
recipeScale: 1,
});
const emit = defineEmits<{
[key: string]: any;
delete: [slug: string];
}>();
const api = useUserApi();
const state = reactive({
printPreferencesDialog: false,
shareDialog: false,
recipeDeleteDialog: false,
mealplannerDialog: false,
shoppingListDialog: false,
recipeDuplicateDialog: false,
recipeName: props.name,
loading: false,
menuItems: [] as ContextMenuItem[],
newMealdate: new Date(),
newMealType: "dinner" as PlanEntryType,
pickerMenu: false,
});
const printPreferencesDialog = ref(false);
const shareDialog = ref(false);
const recipeDeleteDialog = ref(false);
const mealplannerDialog = ref(false);
const shoppingListDialog = ref(false);
const recipeDuplicateDialog = ref(false);
const recipeName = ref(props.name);
const loading = ref(false);
const menuItems = ref<ContextMenuItem[]>([]);
const newMealdate = ref(new Date());
const newMealType = ref<PlanEntryType>("dinner");
const pickerMenu = ref(false);
const newMealdateString = computed(() => {
// Format the date to YYYY-MM-DD in the same timezone as newMealdate
const year = state.newMealdate.getFullYear();
const month = String(state.newMealdate.getMonth() + 1).padStart(2, "0");
const day = String(state.newMealdate.getDate()).padStart(2, "0");
const year = newMealdate.value.getFullYear();
const month = String(newMealdate.value.getMonth() + 1).padStart(2, "0");
const day = String(newMealdate.value.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
});
@ -365,7 +334,7 @@ export default defineNuxtComponent({
};
// Add leading and Appending Items
state.menuItems = [...state.menuItems, ...props.leadingItems, ...props.appendItems];
menuItems.value = [...menuItems.value, ...props.leadingItems, ...props.appendItems];
const icon = props.menuIcon || $globals.icons.dotsVertical;
@ -398,7 +367,7 @@ export default defineNuxtComponent({
const item = defaultItems[key];
if (item && (item.isPublic || isOwnGroup.value)) {
state.menuItems.push(item);
menuItems.value.push(item);
}
}
@ -438,7 +407,7 @@ export default defineNuxtComponent({
if (data?.slug) {
router.push(`/g/${groupSlug.value}`);
}
context.emit("delete", props.slug);
emit("delete", props.slug);
}
const download = useDownloader();
@ -454,7 +423,7 @@ export default defineNuxtComponent({
async function addRecipeToPlan() {
const { response } = await api.mealplans.createOne({
date: newMealdateString.value,
entryType: state.newMealType,
entryType: newMealType.value,
title: "",
text: "",
recipeId: props.recipeId,
@ -469,7 +438,7 @@ export default defineNuxtComponent({
}
async function duplicateRecipe() {
const { data } = await api.recipes.duplicateOne(props.slug, state.recipeName);
const { data } = await api.recipes.duplicateOne(props.slug, recipeName.value);
if (data && data.slug) {
router.push(`/g/${groupSlug.value}/r/${data.slug}`);
}
@ -479,21 +448,21 @@ export default defineNuxtComponent({
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
const eventHandlers: { [key: string]: () => void | Promise<any> } = {
delete: () => {
state.recipeDeleteDialog = true;
recipeDeleteDialog.value = true;
},
edit: () => router.push(`/g/${groupSlug.value}/r/${props.slug}` + "?edit=true"),
download: handleDownloadEvent,
duplicate: () => {
state.recipeDuplicateDialog = true;
recipeDuplicateDialog.value = true;
},
mealplanner: () => {
state.mealplannerDialog = true;
mealplannerDialog.value = true;
},
printPreferences: async () => {
if (!recipeRef.value) {
await refreshRecipe();
}
state.printPreferencesDialog = true;
printPreferencesDialog.value = true;
},
shoppingList: () => {
const promises: Promise<void>[] = [getShoppingLists()];
@ -502,11 +471,11 @@ export default defineNuxtComponent({
}
Promise.allSettled(promises).then(() => {
state.shoppingListDialog = true;
shoppingListDialog.value = true;
});
},
share: () => {
state.shareDialog = true;
shareDialog.value = true;
},
};
@ -515,34 +484,14 @@ export default defineNuxtComponent({
if (handler && typeof handler === "function") {
handler();
state.loading = false;
loading.value = false;
return;
}
context.emit(eventKey);
state.loading = false;
emit(eventKey);
loading.value = false;
}
const planTypeOptions = usePlanTypeOptions();
return {
...toRefs(state),
newMealdateString,
recipeRef,
recipeRefWithScale,
executeRecipeAction,
recipeActions: groupRecipeActionsStore.recipeActions,
shoppingLists,
duplicateRecipe,
contextMenuEventHandler,
deleteRecipe,
addRecipeToPlan,
icon,
planTypeOptions,
firstDayOfWeek,
isAdminAndNotOwner,
canDelete,
};
},
});
const recipeActions = groupRecipeActionsStore.recipeActions;
</script>