1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-19 13:19:41 +02:00

feat: Migrate to Nuxt 3 framework (#5184)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Hoa (Kyle) Trinh 2025-06-20 00:09:12 +07:00 committed by GitHub
parent 89ab7fac25
commit c24d532608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
403 changed files with 23959 additions and 19557 deletions

View file

@ -2,58 +2,69 @@
<div class="text-center">
<BaseDialog
v-model="recipeEventEditDialog"
:title="$tc('recipe.edit-timeline-event')"
:title="$t('recipe.edit-timeline-event')"
:icon="$globals.icons.edit"
:submit-text="$tc('general.save')"
@submit="$emit('update')"
can-submit
:submit-text="$t('general.save')"
@submit="submitEdit"
>
<v-card-text>
<v-form ref="domMadeThisForm">
<v-text-field
v-model="event.subject"
:label="$tc('general.subject')"
/>
<v-textarea
v-model="event.eventMessage"
:label="$tc('general.message')"
rows="4"
/>
</v-form>
</v-card-text>
<v-card-text>
<v-form ref="domEditEventForm">
<v-text-field v-model="localEvent.subject" :label="$t('general.subject')" />
<v-textarea v-model="localEvent.eventMessage" :label="$t('general.message')" rows="4" />
</v-form>
</v-card-text>
</BaseDialog>
<BaseDialog
v-model="recipeEventDeleteDialog"
:title="$tc('events.delete-event')"
:title="$t('events.delete-event')"
color="error"
:icon="$globals.icons.alertCircle"
can-confirm
@confirm="$emit('delete')"
>
<v-card-text>
{{ $t("events.event-delete-confirmation") }}
{{ $t('events.event-delete-confirmation') }}
</v-card-text>
</BaseDialog>
<v-menu
offset-y
left
:bottom="!menuTop"
:nudge-bottom="!menuTop ? '5' : '0'"
:top="menuTop"
:nudge-top="menuTop ? '5' : '0'"
start
:bottom="!props.menuTop"
:nudge-bottom="!props.menuTop ? '5' : '0'"
:top="props.menuTop"
:nudge-top="props.menuTop ? '5' : '0'"
allow-overflow
close-delay="125"
:open-on-hover="!useMobileFormat"
:open-on-hover="!props.useMobileFormat"
content-class="d-print-none"
>
<template #activator="{ on, attrs }">
<v-btn :fab="fab" :x-small="fab" :elevation="elevation" :color="color" :icon="!fab" v-bind="attrs" v-on="on" @click.prevent>
<template #activator="{ props: btnProps }">
<v-btn
:class="{ 'rounded-circle': props.fab }"
:x-small="props.fab"
:elevation="props.elevation ?? undefined"
:color="props.color"
:icon="!props.fab"
v-bind="btnProps"
@click.prevent
>
<v-icon>{{ icon }}</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item v-for="(item, index) in menuItems" :key="index" @click="contextMenuEventHandler(item.event)">
<v-list-item-icon>
<v-icon :color="item.color"> {{ item.icon }} </v-icon>
</v-list-item-icon>
<v-list density="compact">
<v-list-item
v-for="(item, index) in menuItems"
:key="index"
@click="contextMenuEventHandler(item.event)"
>
<template #prepend>
<v-icon :color="item.color">
{{ item.icon }}
</v-icon>
</template>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
@ -61,10 +72,9 @@
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
import { VForm } from "~/types/vuetify";
import { RecipeTimelineEventOut } from "~/lib/api/types/recipe";
<script lang="ts" setup>
import { useI18n, useNuxtApp } from "#imports";
import type { RecipeTimelineEventOut } from "~/lib/api/types/recipe";
export interface TimelineContextMenuIncludes {
edit: boolean;
@ -78,129 +88,90 @@ export interface ContextMenuItem {
event: string;
}
export default defineComponent({
props: {
useItems: {
type: Object as () => TimelineContextMenuIncludes,
default: () => ({
edit: true,
delete: 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,
},
elevation: {
type: Number,
default: null
},
color: {
type: String,
default: "primary",
},
event: {
type: Object as () => RecipeTimelineEventOut,
required: true,
},
menuIcon: {
type: String,
default: null,
},
useMobileFormat: {
type: Boolean,
default: true,
}
const props = defineProps<{
useItems?: TimelineContextMenuIncludes;
appendItems?: ContextMenuItem[];
leadingItems?: ContextMenuItem[];
menuTop?: boolean;
fab?: boolean;
elevation?: number | null;
color?: string;
event: RecipeTimelineEventOut;
menuIcon?: string | null;
useMobileFormat?: boolean;
}>();
const emit = defineEmits(["delete", "update"]);
const domEditEventForm = ref();
const recipeEventEditDialog = ref(false);
const recipeEventDeleteDialog = ref(false);
const loading = ref(false);
const i18n = useI18n();
const { $globals } = useNuxtApp();
const defaultItems: { [key: string]: ContextMenuItem } = {
edit: {
title: i18n.t("general.edit"),
icon: $globals.icons.edit,
color: undefined,
event: "edit",
},
setup(props, context) {
const domEditEventForm = ref<VForm>();
const state = reactive({
recipeEventEditDialog: false,
recipeEventDeleteDialog: false,
loading: false,
menuItems: [] as ContextMenuItem[],
});
const { i18n, $globals } = useContext();
// ===========================================================================
// Context Menu Setup
const defaultItems: { [key: string]: ContextMenuItem } = {
edit: {
title: i18n.tc("general.edit"),
icon: $globals.icons.edit,
color: undefined,
event: "edit",
},
delete: {
title: i18n.tc("general.delete"),
icon: $globals.icons.delete,
color: "error",
event: "delete",
},
};
// Get Default Menu Items Specified in Props
for (const [key, value] of Object.entries(props.useItems)) {
if (value) {
const item = defaultItems[key];
if (item) {
state.menuItems.push(item);
}
}
}
// Add Leading and Appending Items
state.menuItems = [...state.menuItems, ...props.leadingItems, ...props.appendItems];
const icon = props.menuIcon || $globals.icons.dotsVertical;
// ===========================================================================
// Context Menu Event Handler
const eventHandlers: { [key: string]: () => void | Promise<any> } = {
edit: () => {
state.recipeEventEditDialog = true;
},
delete: () => {
state.recipeEventDeleteDialog = true;
},
};
function contextMenuEventHandler(eventKey: string) {
const handler = eventHandlers[eventKey];
if (handler && typeof handler === "function") {
handler();
state.loading = false;
return;
}
context.emit(eventKey);
state.loading = false;
}
return {
...toRefs(state),
contextMenuEventHandler,
domEditEventForm,
icon,
};
delete: {
title: i18n.t("general.delete"),
icon: $globals.icons.delete,
color: "error",
event: "delete",
},
};
const menuItems = computed(() => {
const items: ContextMenuItem[] = [];
const useItems = props.useItems ?? { edit: true, delete: true };
for (const [key, value] of Object.entries(useItems)) {
if (value) {
const item = defaultItems[key];
if (item) items.push(item);
}
}
return [
...items,
...(props.leadingItems ?? []),
...(props.appendItems ?? []),
];
});
const icon = computed(() => props.menuIcon || $globals.icons.dotsVertical);
const localEvent = ref({ ...props.event });
watch(() => props.event, (val) => {
localEvent.value = { ...val };
});
function openEditDialog() {
localEvent.value = { ...props.event };
recipeEventEditDialog.value = true;
}
function openDeleteDialog() {
recipeEventDeleteDialog.value = true;
}
function contextMenuEventHandler(eventKey: string) {
if (eventKey === "edit") {
openEditDialog();
loading.value = false;
return;
}
if (eventKey === "delete") {
openDeleteDialog();
loading.value = false;
return;
}
emit(eventKey as "delete" | "update");
loading.value = false;
}
function submitEdit() {
emit("update", { ...localEvent.value });
recipeEventEditDialog.value = false;
}
</script>