mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-31 12:15:42 -04:00
feat(frontend): ✨ Add Initial CookBook Support
This commit is contained in:
parent
2e6352cfbd
commit
83ab858e46
31
frontend/api/class-interfaces/cookbooks.ts
Normal file
31
frontend/api/class-interfaces/cookbooks.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { BaseCRUDAPI } from "./_base";
|
||||||
|
import { Category } from "./categories";
|
||||||
|
import { CategoryBase } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
|
const prefix = "/api";
|
||||||
|
|
||||||
|
export interface CreateCookBook {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CookBook extends CreateCookBook {
|
||||||
|
id: number;
|
||||||
|
slug: string;
|
||||||
|
position: number;
|
||||||
|
group_id: number;
|
||||||
|
categories: Category[] | CategoryBase[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
cookbooks: `${prefix}/groups/cookbooks`,
|
||||||
|
cookbooksId: (id: number) => `${prefix}/groups/cookbooks/${id}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CookbookAPI extends BaseCRUDAPI<CookBook, CreateCookBook> {
|
||||||
|
baseRoute: string = routes.cookbooks;
|
||||||
|
itemRoute = routes.cookbooksId;
|
||||||
|
|
||||||
|
async updateAll(payload: CookBook[]) {
|
||||||
|
return await this.requests.put(this.baseRoute, payload);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import { UtilsAPI } from "./class-interfaces/utils";
|
|||||||
import { NotificationsAPI } from "./class-interfaces/event-notifications";
|
import { NotificationsAPI } from "./class-interfaces/event-notifications";
|
||||||
import { FoodAPI } from "./class-interfaces/recipe-foods";
|
import { FoodAPI } from "./class-interfaces/recipe-foods";
|
||||||
import { UnitAPI } from "./class-interfaces/recipe-units";
|
import { UnitAPI } from "./class-interfaces/recipe-units";
|
||||||
|
import { CookbookAPI } from "./class-interfaces/cookbooks";
|
||||||
import { ApiRequestInstance } from "~/types/api";
|
import { ApiRequestInstance } from "~/types/api";
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
@ -27,6 +28,7 @@ class Api {
|
|||||||
public notifications: NotificationsAPI;
|
public notifications: NotificationsAPI;
|
||||||
public foods: FoodAPI;
|
public foods: FoodAPI;
|
||||||
public units: UnitAPI;
|
public units: UnitAPI;
|
||||||
|
public cookbooks: CookbookAPI;
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
public upload: UploadFile;
|
public upload: UploadFile;
|
||||||
@ -46,6 +48,7 @@ class Api {
|
|||||||
// Users
|
// Users
|
||||||
this.users = new UserApi(requests);
|
this.users = new UserApi(requests);
|
||||||
this.groups = new GroupAPI(requests);
|
this.groups = new GroupAPI(requests);
|
||||||
|
this.cookbooks = new CookbookAPI(requests);
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
this.debug = new DebugAPI(requests);
|
this.debug = new DebugAPI(requests);
|
||||||
|
@ -46,6 +46,7 @@ export default {
|
|||||||
if (props.disableAmount) {
|
if (props.disableAmount) {
|
||||||
return ingredient.note;
|
return ingredient.note;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { quantity, food, unit, note } = ingredient;
|
const { quantity, food, unit, note } = ingredient;
|
||||||
|
|
||||||
let return_qty = "";
|
let return_qty = "";
|
||||||
@ -59,7 +60,7 @@ export default {
|
|||||||
return_qty += ` <sup>${fraction[1]}</sup>⁄<sub>${fraction[2]}</sub>`;
|
return_qty += ` <sup>${fraction[1]}</sup>⁄<sub>${fraction[2]}</sub>`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return_qty = quantity;
|
return_qty = quantity * props.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${return_qty} ${unit?.name || " "} ${food?.name || " "} ${note}`;
|
return `${return_qty} ${unit?.name || " "} ${food?.name || " "} ${note}`;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<v-navigation-drawer :value="value" clipped app width="240px">
|
<v-navigation-drawer :value="value" clipped app width="240px">
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<template v-if="$auth.user">
|
<template v-if="$auth.user">
|
||||||
<v-list-item two-line to="/user/profile">
|
<v-list-item two-line to="/user/profile" exact>
|
||||||
<v-list-item-avatar color="accent" class="white--text">
|
<v-list-item-avatar color="accent" class="white--text">
|
||||||
<v-img :src="require(`~/static/account.png`)" />
|
<v-img :src="require(`~/static/account.png`)" />
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item v-for="child in nav.children" :key="child.title" :to="child.to">
|
<v-list-item v-for="child in nav.children" :key="child.title" exact :to="child.to">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ child.icon }}</v-icon>
|
<v-icon>{{ child.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
v-model="secondarySelected"
|
v-model="secondarySelected"
|
||||||
color="primary"
|
color="primary"
|
||||||
>
|
>
|
||||||
<v-list-item link :to="nav.to">
|
<v-list-item exact link :to="nav.to">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ nav.icon }}</v-icon>
|
<v-icon>{{ nav.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<template v-if="secondaryLinks">
|
<template v-if="secondaryLinks">
|
||||||
<v-subheader v-if="secondaryHeader" class="pb-0">{{ secondaryHeader }}</v-subheader>
|
<v-subheader v-if="secondaryHeader" class="pb-0">{{ secondaryHeader }}</v-subheader>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-list nav dense>
|
<v-list nav dense exact>
|
||||||
<template v-for="nav in secondaryLinks">
|
<template v-for="nav in secondaryLinks">
|
||||||
<!-- Multi Items -->
|
<!-- Multi Items -->
|
||||||
<v-list-group
|
<v-list-group
|
||||||
@ -76,7 +76,7 @@
|
|||||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list-item v-for="child in nav.children" :key="child.title" :to="child.to">
|
<v-list-item v-for="child in nav.children" :key="child.title" exact :to="child.to">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ child.icon }}</v-icon>
|
<v-icon>{{ child.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
<!-- Single Item -->
|
<!-- Single Item -->
|
||||||
<v-list-item-group v-else :key="nav.title + 'single-item'" v-model="secondarySelected" color="primary">
|
<v-list-item-group v-else :key="nav.title + 'single-item'" v-model="secondarySelected" color="primary">
|
||||||
<v-list-item link :to="nav.to">
|
<v-list-item exact link :to="nav.to">
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon>{{ nav.icon }}</v-icon>
|
<v-icon>{{ nav.icon }}</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
@ -105,6 +105,7 @@
|
|||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="nav in bottomLinks"
|
v-for="nav in bottomLinks"
|
||||||
:key="nav.title"
|
:key="nav.title"
|
||||||
|
exact
|
||||||
link
|
link
|
||||||
:to="nav.to || null"
|
:to="nav.to || null"
|
||||||
:href="nav.href || null"
|
:href="nav.href || null"
|
||||||
|
@ -44,6 +44,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
save: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
120
frontend/composables/use-cookbooks.ts
Normal file
120
frontend/composables/use-cookbooks.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { useAsync, ref, reactive, Ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useAsyncKey } from "./use-utils";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { CookBook } from "~/api/class-interfaces/cookbooks";
|
||||||
|
|
||||||
|
let cookbookStore: Ref<CookBook[] | null> | null = null;
|
||||||
|
|
||||||
|
export const useCookbooks = function () {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
const loading = ref(false);
|
||||||
|
const deleteTargetId = ref(0);
|
||||||
|
const validForm = ref(true);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const workingCookbookData: CookBook = reactive({
|
||||||
|
id: 0,
|
||||||
|
name: "",
|
||||||
|
position: 1,
|
||||||
|
categories: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
getAll() {
|
||||||
|
loading.value = true;
|
||||||
|
const units = useAsync(async () => {
|
||||||
|
const { data } = await api.cookbooks.getAll();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, useAsyncKey());
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
return units;
|
||||||
|
},
|
||||||
|
async refreshAll() {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.cookbooks.getAll();
|
||||||
|
|
||||||
|
if (data && cookbookStore) {
|
||||||
|
cookbookStore.value = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
async createOne() {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.cookbooks.createOne({
|
||||||
|
// @ts-ignore. I"m thinking this will always be defined.
|
||||||
|
name: "New Cookbook" + String(cookbookStore?.value?.length + 1 || 1),
|
||||||
|
});
|
||||||
|
if (data && cookbookStore?.value) {
|
||||||
|
cookbookStore.value.unshift(data);
|
||||||
|
} else {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetWorking();
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
async updateOne(updateData: CookBook) {
|
||||||
|
if (!updateData.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.cookbooks.updateOne(updateData.id, updateData);
|
||||||
|
if (data && cookbookStore?.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateOrder() {
|
||||||
|
if (!cookbookStore?.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
cookbookStore.value.forEach((element, index) => {
|
||||||
|
element.position = index + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await api.cookbooks.updateAll(cookbookStore.value);
|
||||||
|
|
||||||
|
if (data && cookbookStore?.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
},
|
||||||
|
async deleteOne(id: string | number) {
|
||||||
|
loading.value = true;
|
||||||
|
const { data } = await api.cookbooks.deleteOne(id);
|
||||||
|
if (data && cookbookStore?.value) {
|
||||||
|
this.refreshAll();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetWorking() {
|
||||||
|
workingCookbookData.id = 0;
|
||||||
|
workingCookbookData.name = "";
|
||||||
|
workingCookbookData.position = 0;
|
||||||
|
workingCookbookData.categories = [];
|
||||||
|
},
|
||||||
|
setWorking(item: CookBook) {
|
||||||
|
workingCookbookData.id = item.id;
|
||||||
|
workingCookbookData.name = item.name;
|
||||||
|
workingCookbookData.position = item.position;
|
||||||
|
workingCookbookData.categories = item.categories;
|
||||||
|
},
|
||||||
|
flushStore() {
|
||||||
|
cookbookStore = null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!cookbookStore) {
|
||||||
|
cookbookStore = actions.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { cookbooks: cookbookStore, workingCookbookData, deleteTargetId, actions, validForm };
|
||||||
|
};
|
@ -2,7 +2,14 @@
|
|||||||
<v-app dark>
|
<v-app dark>
|
||||||
<!-- <TheSnackbar /> -->
|
<!-- <TheSnackbar /> -->
|
||||||
|
|
||||||
<AppSidebar v-model="sidebar" absolute :top-link="topLinks" @input="sidebar = !sidebar" />
|
<AppSidebar
|
||||||
|
v-model="sidebar"
|
||||||
|
absolute
|
||||||
|
:top-link="topLinks"
|
||||||
|
secondary-header="Cookbooks"
|
||||||
|
:secondary-links="cookbookLinks || []"
|
||||||
|
@input="sidebar = !sidebar"
|
||||||
|
/>
|
||||||
|
|
||||||
<AppHeader>
|
<AppHeader>
|
||||||
<v-btn icon @click.stop="sidebar = !sidebar">
|
<v-btn icon @click.stop="sidebar = !sidebar">
|
||||||
@ -20,17 +27,32 @@
|
|||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { computed, defineComponent, useContext } from "@nuxtjs/composition-api";
|
||||||
import AppHeader from "@/components/Layout/AppHeader.vue";
|
import AppHeader from "@/components/Layout/AppHeader.vue";
|
||||||
import AppSidebar from "@/components/Layout/AppSidebar.vue";
|
import AppSidebar from "@/components/Layout/AppSidebar.vue";
|
||||||
import AppFloatingButton from "@/components/Layout/AppFloatingButton.vue";
|
import AppFloatingButton from "@/components/Layout/AppFloatingButton.vue";
|
||||||
|
import { useCookbooks } from "~/composables/use-cookbooks";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AppHeader, AppSidebar, AppFloatingButton },
|
components: { AppHeader, AppSidebar, AppFloatingButton },
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// middleware: process.env.GLOBAL_MIDDLEWARE,
|
// middleware: process.env.GLOBAL_MIDDLEWARE,
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
const { cookbooks } = useCookbooks();
|
||||||
|
// @ts-ignore
|
||||||
|
const { $globals } = useContext();
|
||||||
|
|
||||||
|
const cookbookLinks = computed(() => {
|
||||||
|
if (!cookbooks.value) return [];
|
||||||
|
return cookbooks.value.map((cookbook) => {
|
||||||
|
return {
|
||||||
|
icon: $globals.icons.pages,
|
||||||
|
title: cookbook.name,
|
||||||
|
to: `/cookbooks/${cookbook.slug}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { cookbookLinks };
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -242,6 +242,9 @@ export default {
|
|||||||
icons: false,
|
icons: false,
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
options: {
|
||||||
|
customProperties: true,
|
||||||
|
},
|
||||||
dark: false,
|
dark: false,
|
||||||
themes: {
|
themes: {
|
||||||
dark: {
|
dark: {
|
||||||
|
@ -110,26 +110,35 @@
|
|||||||
/>
|
/>
|
||||||
</draggable>
|
</draggable>
|
||||||
<div class="d-flex justify-end mt-2">
|
<div class="d-flex justify-end mt-2">
|
||||||
|
<RecipeIngredientParserMenu class="mr-1" :ingredients="recipe.recipeIngredient" />
|
||||||
<RecipeDialogBulkAdd class="mr-1" @bulk-data="addIngredient" />
|
<RecipeDialogBulkAdd class="mr-1" @bulk-data="addIngredient" />
|
||||||
<BaseButton class="mr-1" @click="addIngredient"> {{ $t("general.new") }} </BaseButton>
|
<BaseButton @click="addIngredient"> {{ $t("general.new") }} </BaseButton>
|
||||||
<RecipeIngredientParserMenu :ingredients="recipe.recipeIngredient" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-space-between align-center pb-3">
|
<div class="d-flex justify-space-between align-center pb-3">
|
||||||
<v-btn
|
<v-tooltip small top color="secondary darken-1">
|
||||||
v-if="recipe.recipeYield"
|
<template #activator="{ on, attrs }">
|
||||||
dense
|
<v-btn
|
||||||
small
|
v-if="recipe.recipeYield"
|
||||||
:hover="false"
|
dense
|
||||||
type="label"
|
small
|
||||||
:ripple="false"
|
:hover="false"
|
||||||
elevation="0"
|
type="label"
|
||||||
color="secondary darken-1"
|
:ripple="false"
|
||||||
class="rounded-sm static"
|
elevation="0"
|
||||||
>
|
color="secondary darken-1"
|
||||||
{{ scaledYield }}
|
class="rounded-sm static"
|
||||||
</v-btn>
|
v-bind="attrs"
|
||||||
|
@click="scale = 1"
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
{{ scaledYield }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<span> Reset Scale </span>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
<template v-if="!recipe.settings.disableAmount">
|
<template v-if="!recipe.settings.disableAmount">
|
||||||
<v-btn color="secondary darken-1" class="mx-1" small @click="scale > 1 ? scale-- : null">
|
<v-btn color="secondary darken-1" class="mx-1" small @click="scale > 1 ? scale-- : null">
|
||||||
<v-icon>
|
<v-icon>
|
||||||
|
@ -1,24 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<BaseCardSectionTitle title="Group Pages">
|
<BaseCardSectionTitle title="Cookbooks"> </BaseCardSectionTitle>
|
||||||
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Alias error provident, eveniet, laboriosam assumenda
|
<BaseButton create @click="actions.createOne()" />
|
||||||
earum amet quaerat vel consequatur molestias sed enim. Adipisci a consequuntur dolor culpa expedita voluptatem
|
<v-expansion-panels class="mt-2">
|
||||||
praesentium optio iste atque, ea reiciendis iure non aut suscipit modi ducimus ratione, quam numquam quaerat
|
<draggable v-model="cookbooks" handle=".handle" style="width: 100%" @change="actions.updateOrder()">
|
||||||
distinctio illum nemo. Dicta, doloremque!
|
<v-expansion-panel v-for="(cookbook, index) in cookbooks" :key="index" class="my-2 my-border rounded">
|
||||||
</BaseCardSectionTitle>
|
<v-expansion-panel-header disable-icon-rotate class="headline">
|
||||||
|
{{ cookbook.name }}
|
||||||
|
<template #actions>
|
||||||
|
<v-btn color="info" fab small class="ml-auto mr-2">
|
||||||
|
<v-icon color="white">
|
||||||
|
{{ $globals.icons.edit }}
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-icon class="handle">
|
||||||
|
{{ $globals.icons.arrowUpDown }}
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
</v-expansion-panel-header>
|
||||||
|
<v-expansion-panel-content>
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field v-model="cookbooks[index].name" label="Cookbook Name"></v-text-field>
|
||||||
|
<DomainRecipeCategoryTagSelector v-model="cookbooks[index].categories" />
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<BaseButton delete @click="actions.deleteOne(cookbook.id)" />
|
||||||
|
<BaseButton update @click="actions.updateOne(cookbook)"> </BaseButton>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-expansion-panel-content>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</draggable>
|
||||||
|
</v-expansion-panels>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
import { useCookbooks } from "@/composables/use-cookbooks";
|
||||||
|
import draggable from "vuedraggable";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { draggable },
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
const { cookbooks, actions, workingCookbookData, deleteTargetId, validForm } = useCookbooks();
|
||||||
|
|
||||||
|
return {
|
||||||
|
cookbooks,
|
||||||
|
actions,
|
||||||
|
workingCookbookData,
|
||||||
|
deleteTargetId,
|
||||||
|
validForm,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.my-border {
|
||||||
|
border-left: 5px solid var(--v-primary-base);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user