mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-22 06:51:35 -04:00
feat(frontend): 🚧 CRUD Functionality
This commit is contained in:
parent
00a8fdda41
commit
afcad2f701
@ -44,13 +44,13 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer nofollow"
|
rel="noreferrer nofollow"
|
||||||
>
|
>
|
||||||
{{ $t('new-recipe.google-ld-json-info') }}
|
{{ $t("new-recipe.google-ld-json-info") }}
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/hay-kot/mealie/issues" target="_blank" rel="noreferrer nofollow">
|
<a href="https://github.com/hay-kot/mealie/issues" target="_blank" rel="noreferrer nofollow">
|
||||||
{{ $t('new-recipe.github-issues') }}
|
{{ $t("new-recipe.github-issues") }}
|
||||||
</a>
|
</a>
|
||||||
<a href="https://schema.org/Recipe" target="_blank" rel="noreferrer nofollow">
|
<a href="https://schema.org/Recipe" target="_blank" rel="noreferrer nofollow">
|
||||||
{{ $t('new-recipe.recipe-markup-specification') }}
|
{{ $t("new-recipe.recipe-markup-specification") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-end">
|
<div class="d-flex justify-end">
|
||||||
@ -61,7 +61,7 @@
|
|||||||
@click="addRecipe = false"
|
@click="addRecipe = false"
|
||||||
>
|
>
|
||||||
<v-icon left> {{ $globals.icons.externalLink }} </v-icon>
|
<v-icon left> {{ $globals.icons.externalLink }} </v-icon>
|
||||||
{{ $t('new-recipe.view-scraped-data') }}
|
{{ $t("new-recipe.view-scraped-data") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<TheUploadBtn class="mx-auto" :text-btn="false" @uploaded="setFile" :post="false"> </TheUploadBtn>
|
<AppButtonUpload class="mx-auto" :text-btn="false" @uploaded="setFile" :post="false"> </AppButtonUpload>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
<v-speed-dial v-model="fab" :open-on-hover="absolute" :fixed="absolute" :bottom="absolute" :right="absolute">
|
<v-speed-dial v-model="fab" :open-on-hover="absolute" :fixed="absolute" :bottom="absolute" :right="absolute">
|
||||||
@ -140,11 +140,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn.vue";
|
import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload.vue";
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog.vue";
|
import BaseDialog from "@/components/UI/Dialogs/BaseDialog.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TheUploadBtn,
|
AppButtonUpload,
|
||||||
BaseDialog,
|
BaseDialog,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -232,8 +232,9 @@ export default {
|
|||||||
this.processing = false;
|
this.processing = false;
|
||||||
},
|
},
|
||||||
isValidWebUrl(url) {
|
isValidWebUrl(url) {
|
||||||
let regEx = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,256}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
let regEx =
|
||||||
return regEx.test(url) ? true : this.$t('new-recipe.must-be-a-valid-url');
|
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,256}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
||||||
|
return regEx.test(url) ? true : this.$t("new-recipe.must-be-a-valid-url");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -29,13 +29,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="d-flex row py-3 justify-end">
|
<div class="d-flex row py-3 justify-end">
|
||||||
<TheUploadBtn url="/api/backups/upload" @uploaded="getAvailableBackups">
|
<AppButtonUpload url="/api/backups/upload" @uploaded="getAvailableBackups">
|
||||||
<template v-slot="{ isSelecting, onButtonClick }">
|
<template v-slot="{ isSelecting, onButtonClick }">
|
||||||
<v-btn :loading="isSelecting" class="mx-2" small color="info" @click="onButtonClick">
|
<v-btn :loading="isSelecting" class="mx-2" small color="info" @click="onButtonClick">
|
||||||
<v-icon left> {{ $globals.icons.upload }} </v-icon> {{ $t("general.upload") }}
|
<v-icon left> {{ $globals.icons.upload }} </v-icon> {{ $t("general.upload") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
</TheUploadBtn>
|
</AppButtonUpload>
|
||||||
<BackupDialog :color="color" />
|
<BackupDialog :color="color" />
|
||||||
|
|
||||||
<v-btn :loading="loading" class="mx-2" small color="success" @click="createBackup">
|
<v-btn :loading="loading" class="mx-2" small color="success" @click="createBackup">
|
||||||
@ -74,7 +74,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload";
|
||||||
import ImportSummaryDialog from "@/components/ImportSummaryDialog";
|
import ImportSummaryDialog from "@/components/ImportSummaryDialog";
|
||||||
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
|
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
@ -85,7 +85,7 @@ const IMPORT_EVENT = "import";
|
|||||||
const DELETE_EVENT = "delete";
|
const DELETE_EVENT = "delete";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog, BackupDialog, ConfirmationDialog },
|
components: { StatCard, ImportDialog, AppButtonUpload, ImportSummaryDialog, BackupDialog, ConfirmationDialog },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
color: "accent",
|
color: "accent",
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<v-data-table :headers="headers" :items="links" sort-by="calories">
|
<v-data-table :headers="headers" :items="links" sort-by="calories">
|
||||||
<template v-slot:item.token="{ item }">
|
<template v-slot:item.token="{ item }">
|
||||||
{{ `${baseURL}/sign-up/${item.token}` }}
|
{{ `${baseURL}/sign-up/${item.token}` }}
|
||||||
<TheCopyButton :copy-text="`${baseURL}/sign-up/${item.token}`" />
|
<AppCopyButton :copy-text="`${baseURL}/sign-up/${item.token}`" />
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.admin="{ item }">
|
<template v-slot:item.admin="{ item }">
|
||||||
<v-btn small :color="item.admin ? 'success' : 'error'" text>
|
<v-btn small :color="item.admin ? 'success' : 'error'" text>
|
||||||
@ -94,12 +94,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheCopyButton from "@/components/UI/Buttons/TheCopyButton";
|
import AppCopyButton from "@/components/UI/Buttons/AppCopyButton";
|
||||||
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
|
import ConfirmationDialog from "@/components/UI/Dialogs/ConfirmationDialog";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
export default {
|
export default {
|
||||||
components: { ConfirmationDialog, TheCopyButton },
|
components: { ConfirmationDialog, AppCopyButton },
|
||||||
mixins: [validators],
|
mixins: [validators],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<span>
|
<span>
|
||||||
<TheUploadBtn
|
<AppButtonUpload
|
||||||
class="mt-1"
|
class="mt-1"
|
||||||
:url="`/api/migrations/${folder}/upload`"
|
:url="`/api/migrations/${folder}/upload`"
|
||||||
fileName="archive"
|
fileName="archive"
|
||||||
@ -55,7 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import MigrationDialog from "./MigrationDialog";
|
import MigrationDialog from "./MigrationDialog";
|
||||||
export default {
|
export default {
|
||||||
@ -66,7 +66,7 @@ export default {
|
|||||||
available: Array,
|
available: Array,
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
TheUploadBtn,
|
AppButtonUpload,
|
||||||
MigrationDialog,
|
MigrationDialog,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -90,7 +90,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions class="pb-1 pt-3">
|
<v-card-actions class="pb-1 pt-3">
|
||||||
<TheUploadBtn
|
<AppButtonUpload
|
||||||
:icon="$globals.icons.fileImage"
|
:icon="$globals.icons.fileImage"
|
||||||
:text="$t('user.upload-photo')"
|
:text="$t('user.upload-photo')"
|
||||||
:url="userProfileImage"
|
:url="userProfileImage"
|
||||||
@ -106,14 +106,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||||
import StatCard from "@/components/UI/StatCard";
|
import StatCard from "@/components/UI/StatCard";
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
import { validators } from "@/mixins/validators";
|
import { validators } from "@/mixins/validators";
|
||||||
import { initials } from "@/mixins/initials";
|
import { initials } from "@/mixins/initials";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseDialog,
|
BaseDialog,
|
||||||
TheUploadBtn,
|
AppButtonUpload,
|
||||||
StatCard,
|
StatCard,
|
||||||
},
|
},
|
||||||
mixins: [validators, initials],
|
mixins: [validators, initials],
|
||||||
|
@ -38,9 +38,9 @@
|
|||||||
{{ $t("shopping-list.shopping-list") }}
|
{{ $t("shopping-list.shopping-list") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<TheCopyButton color="info" :copy-text="mealPlanURL(mealplan.uid)">
|
<AppCopyButton color="info" :copy-text="mealPlanURL(mealplan.uid)">
|
||||||
{{ $t("general.link-copied") }}
|
{{ $t("general.link-copied") }}
|
||||||
</TheCopyButton>
|
</AppCopyButton>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
||||||
<v-list class="mt-0 pt-0">
|
<v-list class="mt-0 pt-0">
|
||||||
@ -90,12 +90,12 @@ import { api } from "@/api";
|
|||||||
import { utils } from "@/utils";
|
import { utils } from "@/utils";
|
||||||
import NewMeal from "@/components/MealPlan/MealPlanNew";
|
import NewMeal from "@/components/MealPlan/MealPlanNew";
|
||||||
import EditPlan from "@/components/MealPlan/MealPlanEditor";
|
import EditPlan from "@/components/MealPlan/MealPlanEditor";
|
||||||
import TheCopyButton from "@/components/UI/Buttons/TheCopyButton";
|
import AppCopyButton from "@/components/UI/Buttons/AppCopyButton";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NewMeal,
|
NewMeal,
|
||||||
EditPlan,
|
EditPlan,
|
||||||
TheCopyButton,
|
AppCopyButton,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
plannedMeals: [],
|
plannedMeals: [],
|
||||||
@ -120,7 +120,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
editPlan(id) {
|
editPlan(id) {
|
||||||
this.plannedMeals.forEach(element => {
|
this.plannedMeals.forEach((element) => {
|
||||||
if (element.uid === id) {
|
if (element.uid === id) {
|
||||||
this.editMealPlan = element;
|
this.editMealPlan = element;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<v-card v-else-if="activeList">
|
<v-card v-else-if="activeList">
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
<TheCopyButton v-if="!edit" :copy-text="listAsText" color="info" />
|
<AppCopyButton v-if="!edit" :copy-text="listAsText" color="info" />
|
||||||
<v-text-field label="Name" single-line dense v-if="edit" v-model="activeList.name"> </v-text-field>
|
<v-text-field label="Name" single-line dense v-if="edit" v-model="activeList.name"> </v-text-field>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ activeList.name }}
|
{{ activeList.name }}
|
||||||
@ -141,14 +141,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
||||||
import SearchDialog from "@/components/UI/Dialogs/SearchDialog";
|
import SearchDialog from "@/components/UI/Dialogs/SearchDialog";
|
||||||
import TheCopyButton from "@/components/UI/Buttons/TheCopyButton";
|
import AppCopyButton from "@/components/UI/Buttons/AppCopyButton";
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BaseDialog,
|
BaseDialog,
|
||||||
SearchDialog,
|
SearchDialog,
|
||||||
TheCopyButton,
|
AppCopyButton,
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -176,7 +176,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
listAsText() {
|
listAsText() {
|
||||||
const formatList = this.activeList.items.map(x => {
|
const formatList = this.activeList.items.map((x) => {
|
||||||
return `${x.quantity} - ${x.text}`;
|
return `${x.quantity} - ${x.text}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ export default {
|
|||||||
|
|
||||||
const recipe = response.data;
|
const recipe = response.data;
|
||||||
|
|
||||||
const ingredients = recipe.recipeIngredient.map(x => ({
|
const ingredients = recipe.recipeIngredient.map((x) => ({
|
||||||
title: "",
|
title: "",
|
||||||
text: x.note,
|
text: x.note,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
@ -217,14 +217,14 @@ export default {
|
|||||||
this.consolidateList();
|
this.consolidateList();
|
||||||
},
|
},
|
||||||
consolidateList() {
|
consolidateList() {
|
||||||
const allText = this.activeList.items.map(x => x.text);
|
const allText = this.activeList.items.map((x) => x.text);
|
||||||
|
|
||||||
const uniqueText = allText.filter((item, index) => {
|
const uniqueText = allText.filter((item, index) => {
|
||||||
return allText.indexOf(item) === index;
|
return allText.indexOf(item) === index;
|
||||||
});
|
});
|
||||||
|
|
||||||
const newItems = uniqueText.map(x => {
|
const newItems = uniqueText.map((x) => {
|
||||||
let matchingItems = this.activeList.items.filter(y => y.text === x);
|
let matchingItems = this.activeList.items.filter((y) => y.text === x);
|
||||||
matchingItems[0].quantity = this.sumQuantiy(matchingItems);
|
matchingItems[0].quantity = this.sumQuantiy(matchingItems);
|
||||||
return matchingItems[0];
|
return matchingItems[0];
|
||||||
});
|
});
|
||||||
@ -233,7 +233,7 @@ export default {
|
|||||||
},
|
},
|
||||||
sumQuantiy(itemList) {
|
sumQuantiy(itemList) {
|
||||||
let quantity = 0;
|
let quantity = 0;
|
||||||
itemList.forEach(element => {
|
itemList.forEach((element) => {
|
||||||
quantity += element.quantity;
|
quantity += element.quantity;
|
||||||
});
|
});
|
||||||
return quantity;
|
return quantity;
|
||||||
@ -241,7 +241,7 @@ export default {
|
|||||||
setActiveList() {
|
setActiveList() {
|
||||||
if (!this.list) return null;
|
if (!this.list) return null;
|
||||||
if (!this.group.shoppingLists) return null;
|
if (!this.group.shoppingLists) return null;
|
||||||
this.activeList = this.group.shoppingLists.find(x => x.id == this.list);
|
this.activeList = this.group.shoppingLists.find((x) => x.id == this.list);
|
||||||
},
|
},
|
||||||
async createNewList() {
|
async createNewList() {
|
||||||
this.newList.group = this.group.name;
|
this.newList.group = this.group.name;
|
||||||
|
@ -1,10 +1,49 @@
|
|||||||
import { ApiRequestInstance } from "~/types/api";
|
import { ApiRequestInstance } from "~/types/api";
|
||||||
|
|
||||||
export class BaseAPIClass {
|
export interface CrudAPIInterface {
|
||||||
requests: ApiRequestInstance
|
requests: ApiRequestInstance;
|
||||||
|
|
||||||
constructor(requests: ApiRequestInstance) {
|
// Route Properties / Methods
|
||||||
this.requests = requests;
|
baseRoute: string;
|
||||||
}
|
itemRoute(itemId: string): string;
|
||||||
|
|
||||||
|
// Methods
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class BaseAPIClass<T> implements CrudAPIInterface {
|
||||||
|
requests: ApiRequestInstance;
|
||||||
|
|
||||||
|
abstract baseRoute: string;
|
||||||
|
abstract itemRoute(itemId: string): string;
|
||||||
|
|
||||||
|
constructor(requests: ApiRequestInstance) {
|
||||||
|
this.requests = requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(start = 0, limit = 9999) {
|
||||||
|
return await this.requests.get<T[]>(this.baseRoute, {
|
||||||
|
params: { start, limit },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOne(itemId: string) {
|
||||||
|
return await this.requests.get<T>(this.itemRoute(itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOne(payload: T) {
|
||||||
|
return await this.requests.post(this.baseRoute, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOne(itemId: string, payload: T){
|
||||||
|
return await this.requests.put<T>(this.itemRoute(itemId), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchOne(itemId: string, payload: T) {
|
||||||
|
return await this.requests.patch(this.itemRoute(itemId), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteOne(itemId: string) {
|
||||||
|
return await this.requests.delete<T>(this.itemRoute(itemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -11,30 +11,35 @@ const routes = {
|
|||||||
recipesCreateUrl: `${prefix}/recipes/create-url`,
|
recipesCreateUrl: `${prefix}/recipes/create-url`,
|
||||||
recipesCreateFromZip: `${prefix}/recipes/create-from-zip`,
|
recipesCreateFromZip: `${prefix}/recipes/create-from-zip`,
|
||||||
|
|
||||||
|
recipesCategory: `${prefix}/recipes/category`,
|
||||||
|
|
||||||
recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`,
|
recipesRecipeSlug: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}`,
|
||||||
recipesRecipeSlugZip: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/zip`,
|
recipesRecipeSlugZip: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/zip`,
|
||||||
recipesRecipeSlugImage: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/image`,
|
recipesRecipeSlugImage: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/image`,
|
||||||
recipesRecipeSlugAssets: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/assets`,
|
recipesRecipeSlugAssets: (recipe_slug: string) => `${prefix}/recipes/${recipe_slug}/assets`,
|
||||||
};
|
};
|
||||||
|
|
||||||
class RecipeAPI extends BaseAPIClass {
|
class RecipeAPI extends BaseAPIClass<Recipe> {
|
||||||
async getAll(start = 0, limit = 9999) {
|
baseRoute: string = routes.recipesSummary;
|
||||||
return await this.requests.get<Recipe[]>(routes.recipesSummary, {
|
itemRoute = (itemid: string) => routes.recipesRecipeSlug(itemid);
|
||||||
params: { start, limit },
|
|
||||||
|
|
||||||
|
async getAllByCategory(categories: string[]) {
|
||||||
|
return await this.requests.get<Recipe[]>(routes.recipesCategory, {
|
||||||
|
categories
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOne(slug: string) {
|
// @ts-ignore - Override method doesn't take same arguments are parent class
|
||||||
return await this.requests.get<Recipe>(routes.recipesRecipeSlug(slug));
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOne(name: string) {
|
async createOne(name: string) {
|
||||||
return await this.requests.post(routes.recipesBase, { name });
|
return await this.requests.post(routes.recipesBase, { name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createOneByUrl(url: string) {
|
||||||
|
return await this.requests.post(routes.recipesCreateUrl, { url });
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Methods to Generate reference urls for assets/images *
|
||||||
|
|
||||||
recipeImage(recipeSlug: string, version = null, key = null) {
|
recipeImage(recipeSlug: string, version = null, key = null) {
|
||||||
return `/api/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`;
|
return `/api/media/recipes/${recipeSlug}/images/original.webp?&rnd=${key}&version=${version}`;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
:icon="$globals.icons.alertCircle"
|
:icon="$globals.icons.alertCircle"
|
||||||
@confirm="emitDelete()"
|
@confirm="emitDelete()"
|
||||||
>
|
>
|
||||||
{{ $t("recipe.delete-confirmation") }}
|
<v-card-text>
|
||||||
|
{{ $t("recipe.delete-confirmation") }}
|
||||||
|
</v-card-text>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
@ -26,7 +26,7 @@
|
|||||||
<v-btn color="error" icon top @click="deleteAsset(i)">
|
<v-btn color="error" icon top @click="deleteAsset(i)">
|
||||||
<v-icon>{{ $globals.icons.delete }}</v-icon>
|
<v-icon>{{ $globals.icons.delete }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<TheCopyButton :copy-text="copyLink(item.fileName)" />
|
<AppCopyButton :copy-text="copyLink(item.fileName)" />
|
||||||
</div>
|
</div>
|
||||||
</v-list-item-action>
|
</v-list-item-action>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
</v-card>
|
</v-card>
|
||||||
<div class="d-flex ml-auto mt-2">
|
<div class="d-flex ml-auto mt-2">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<BaseDialog :title="$t('asset.new-asset')" :title-icon="getIconDefinition(newAsset.icon).icon" @submit="addAsset">
|
<BaseDialog :title="$t('asset.new-asset')" :icon="getIconDefinition(newAsset.icon).icon" @submit="addAsset">
|
||||||
<template #open="{ open }">
|
<template #open="{ open }">
|
||||||
<v-btn v-if="edit" color="secondary" dark @click="open">
|
<v-btn v-if="edit" color="secondary" dark @click="open">
|
||||||
<v-icon>{{ $globals.icons.create }}</v-icon>
|
<v-icon>{{ $globals.icons.create }}</v-icon>
|
||||||
@ -61,7 +61,7 @@
|
|||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</template>
|
</template>
|
||||||
</v-select>
|
</v-select>
|
||||||
<TheUploadBtn :post="false" file-name="file" :text-btn="false" @uploaded="setFileObject" />
|
<AppButtonUpload :post="false" file-name="file" :text-btn="false" @uploaded="setFileObject" />
|
||||||
</div>
|
</div>
|
||||||
{{ fileObject.name }}
|
{{ fileObject.name }}
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
@ -71,26 +71,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheCopyButton from "@/components/UI/Buttons/TheCopyButton";
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
|
||||||
import BaseDialog from "@/components/UI/Dialogs/BaseDialog";
|
|
||||||
import { api } from "@/api";
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
BaseDialog,
|
|
||||||
TheUploadBtn,
|
|
||||||
TheCopyButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
slug: String,
|
slug: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
|
||||||
|
return { api };
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fileObject: {},
|
fileObject: {},
|
||||||
@ -109,34 +110,34 @@ export default {
|
|||||||
{
|
{
|
||||||
name: "mdi-file",
|
name: "mdi-file",
|
||||||
title: this.$i18n.t("asset.file"),
|
title: this.$i18n.t("asset.file"),
|
||||||
icon: this.$globals.icons.file
|
icon: this.$globals.icons.file,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mdi-file-pdf-box",
|
name: "mdi-file-pdf-box",
|
||||||
title: this.$i18n.t("asset.pdf"),
|
title: this.$i18n.t("asset.pdf"),
|
||||||
icon: this.$globals.icons.filePDF
|
icon: this.$globals.icons.filePDF,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mdi-file-image",
|
name: "mdi-file-image",
|
||||||
title: this.$i18n.t("asset.image"),
|
title: this.$i18n.t("asset.image"),
|
||||||
icon: this.$globals.icons.fileImage
|
icon: this.$globals.icons.fileImage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mdi-code-json",
|
name: "mdi-code-json",
|
||||||
title: this.$i18n.t("asset.code"),
|
title: this.$i18n.t("asset.code"),
|
||||||
icon: this.$globals.icons.codeJson
|
icon: this.$globals.icons.codeJson,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mdi-silverware-fork-knife",
|
name: "mdi-silverware-fork-knife",
|
||||||
title: this.$i18n.t("asset.recipe"),
|
title: this.$i18n.t("asset.recipe"),
|
||||||
icon: this.$globals.icons.primary
|
icon: this.$globals.icons.primary,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getIconDefinition(val) {
|
getIconDefinition(val) {
|
||||||
return this.iconOptions.find(({ name }) => name === val );
|
return this.iconOptions.find(({ name }) => name === val);
|
||||||
},
|
},
|
||||||
assetURL(assetName) {
|
assetURL(assetName) {
|
||||||
return api.recipes.recipeAssetPath(this.slug, assetName);
|
return api.recipes.recipeAssetPath(this.slug, assetName);
|
@ -1,40 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-hover v-slot="{ hover }" :open-delay="50">
|
<v-lazy>
|
||||||
<v-card
|
<v-hover v-slot="{ hover }" :open-delay="50">
|
||||||
:class="{ 'on-hover': hover }"
|
<v-card
|
||||||
:elevation="hover ? 12 : 2"
|
:class="{ 'on-hover': hover }"
|
||||||
:to="route ? `/recipe/${slug}` : ''"
|
:elevation="hover ? 12 : 2"
|
||||||
min-height="275"
|
:to="route ? `/recipe/${slug}` : ''"
|
||||||
@click="$emit('click')"
|
min-height="275"
|
||||||
>
|
@click="$emit('click')"
|
||||||
<RecipeCardImage icon-size="200" :slug="slug" small :image-version="image">
|
>
|
||||||
<v-expand-transition v-if="description">
|
<RecipeCardImage icon-size="200" :slug="slug" small :image-version="image">
|
||||||
<div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal" style="height: 100%">
|
<v-expand-transition v-if="description">
|
||||||
<v-card-text class="v-card--text-show white--text">
|
<div v-if="hover" class="d-flex transition-fast-in-fast-out secondary v-card--reveal" style="height: 100%">
|
||||||
{{ description | truncate(300) }}
|
<v-card-text class="v-card--text-show white--text">
|
||||||
</v-card-text>
|
{{ description }}
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</v-expand-transition>
|
||||||
|
</RecipeCardImage>
|
||||||
|
<v-card-title class="my-n3 mb-n6">
|
||||||
|
<div class="headerClass">
|
||||||
|
{{ name }}
|
||||||
</div>
|
</div>
|
||||||
</v-expand-transition>
|
</v-card-title>
|
||||||
</RecipeCardImage>
|
|
||||||
<v-card-title class="my-n3 mb-n6">
|
|
||||||
<div class="headerClass">
|
|
||||||
{{ name }}
|
|
||||||
</div>
|
|
||||||
</v-card-title>
|
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
||||||
<RecipeRating :value="rating" :name="name" :slug="slug" :small="true" />
|
<RecipeRating :value="rating" :name="name" :slug="slug" :small="true" />
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" :is-category="false" />
|
<RecipeChips :truncate="true" :items="tags" :title="false" :limit="2" :small="true" :is-category="false" />
|
||||||
<RecipeContextMenu :slug="slug" :name="name" />
|
<RecipeContextMenu :slug="slug" :name="name" />
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-hover>
|
</v-hover>
|
||||||
|
</v-lazy>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
||||||
import RecipeChips from "./RecipeChips";
|
import RecipeChips from "./RecipeChips";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu";
|
import RecipeContextMenu from "./RecipeContextMenu";
|
||||||
@ -82,11 +83,6 @@ export default {
|
|||||||
return this.$store.getters.getIsLoggedIn;
|
return this.$store.getters.getIsLoggedIn;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
getImage(slug) {
|
|
||||||
return api.recipes.recipeSmallImage(slug, this.image);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useApi } from "~/composables/use-api";
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
tiny: {
|
tiny: {
|
||||||
@ -51,7 +51,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const api = useApi();
|
const api = useApiSingleton();
|
||||||
|
|
||||||
return { api };
|
return { api };
|
||||||
},
|
},
|
||||||
|
@ -1,52 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-expand-transition>
|
<v-lazy>
|
||||||
<v-card
|
<v-expand-transition>
|
||||||
:ripple="false"
|
<v-card
|
||||||
class="mx-auto"
|
:ripple="false"
|
||||||
hover
|
class="mx-auto"
|
||||||
:to="$listeners.selected ? undefined : `/recipe/${slug}`"
|
hover
|
||||||
@click="$emit('selected')"
|
:to="$listeners.selected ? undefined : `/recipe/${slug}`"
|
||||||
>
|
@click="$emit('selected')"
|
||||||
<v-list-item three-line>
|
>
|
||||||
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
<v-list-item three-line>
|
||||||
<v-img
|
<v-list-item-avatar tile size="125" class="v-mobile-img rounded-sm my-0 ml-n4">
|
||||||
v-if="!fallBackImage"
|
<v-img
|
||||||
:src="getImage(slug)"
|
v-if="!fallBackImage"
|
||||||
@load="fallBackImage = false"
|
:src="getImage(slug)"
|
||||||
@error="fallBackImage = true"
|
@load="fallBackImage = false"
|
||||||
></v-img>
|
@error="fallBackImage = true"
|
||||||
<v-icon v-else color="primary" class="icon-position" size="100">
|
></v-img>
|
||||||
{{ $globals.icons.primary }}
|
<v-icon v-else color="primary" class="icon-position" size="100">
|
||||||
</v-icon>
|
{{ $globals.icons.primary }}
|
||||||
</v-list-item-avatar>
|
</v-icon>
|
||||||
<v-list-item-content>
|
</v-list-item-avatar>
|
||||||
<v-list-item-title class="mb-1">{{ name }} </v-list-item-title>
|
<v-list-item-content>
|
||||||
<v-list-item-subtitle> {{ description }} </v-list-item-subtitle>
|
<v-list-item-title class="mb-1">{{ name }} </v-list-item-title>
|
||||||
<div class="d-flex justify-center align-center">
|
<v-list-item-subtitle> {{ description }} </v-list-item-subtitle>
|
||||||
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
<div class="d-flex justify-center align-center">
|
||||||
<v-rating
|
<RecipeFavoriteBadge v-if="loggedIn" :slug="slug" show-always />
|
||||||
color="secondary"
|
<v-rating
|
||||||
class="ml-auto"
|
color="secondary"
|
||||||
background-color="secondary lighten-3"
|
class="ml-auto"
|
||||||
dense
|
background-color="secondary lighten-3"
|
||||||
length="5"
|
dense
|
||||||
size="15"
|
length="5"
|
||||||
:value="rating"
|
size="15"
|
||||||
></v-rating>
|
:value="rating"
|
||||||
<v-spacer></v-spacer>
|
></v-rating>
|
||||||
<RecipeContextMenu :slug="slug" :menu-icon="$globals.icons.dotsHorizontal" :name="name" />
|
<v-spacer></v-spacer>
|
||||||
</div>
|
<RecipeContextMenu :slug="slug" :menu-icon="$globals.icons.dotsHorizontal" :name="name" />
|
||||||
</v-list-item-content>
|
</div>
|
||||||
</v-list-item>
|
</v-list-item-content>
|
||||||
</v-card>
|
</v-list-item>
|
||||||
</v-expand-transition>
|
</v-card>
|
||||||
|
</v-expand-transition>
|
||||||
|
</v-lazy>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
import RecipeFavoriteBadge from "./RecipeFavoriteBadge";
|
||||||
import RecipeContextMenu from "./RecipeContextMenu";
|
import RecipeContextMenu from "./RecipeContextMenu";
|
||||||
export default {
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
RecipeFavoriteBadge,
|
RecipeFavoriteBadge,
|
||||||
RecipeContextMenu,
|
RecipeContextMenu,
|
||||||
@ -81,6 +84,11 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
|
||||||
|
return { api };
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fallBackImage: false,
|
fallBackImage: false,
|
||||||
@ -93,10 +101,10 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getImage(slug) {
|
getImage(slug) {
|
||||||
return api.recipes.recipeSmallImage(slug, this.image);
|
return this.api.recipes.recipeSmallImage(slug, this.image);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -117,7 +117,7 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
titleIcon: {
|
icon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@ -172,7 +172,7 @@ export default {
|
|||||||
return Math.min(this.hardLimit, this.recipes.length);
|
return Math.min(this.hardLimit, this.recipes.length);
|
||||||
},
|
},
|
||||||
displayTitleIcon() {
|
displayTitleIcon() {
|
||||||
return this.titleIcon || this.$globals.icons.tags;
|
return this.icon || this.$globals.icons.tags;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -223,7 +223,6 @@ export default {
|
|||||||
console.log("Unknown Event", sortType);
|
console.log("Unknown Event", sortType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit(SORT_EVENT, sortTarget);
|
this.$emit(SORT_EVENT, sortTarget);
|
||||||
this.sortLoading = false;
|
this.sortLoading = false;
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
ref="deleteRecipieConfirm"
|
ref="confirmDelete"
|
||||||
:title="$t('recipe.delete-recipe')"
|
:title="$t('recipe.delete-recipe')"
|
||||||
:message="$t('recipe.delete-confirmation')"
|
|
||||||
color="error"
|
color="error"
|
||||||
:icon="$globals.icons.alertCircle"
|
:icon="$globals.icons.alertCircle"
|
||||||
@confirm="deleteRecipe()"
|
@confirm="deleteRecipe()"
|
||||||
/>
|
>
|
||||||
|
<v-card-text>
|
||||||
|
{{ $t("recipe.delete-confirmation") }}
|
||||||
|
</v-card-text>
|
||||||
|
</BaseDialog>
|
||||||
<v-menu
|
<v-menu
|
||||||
offset-y
|
offset-y
|
||||||
left
|
left
|
||||||
@ -38,9 +41,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from "@/api";
|
|
||||||
import { utils } from "@/utils";
|
import { utils } from "@/utils";
|
||||||
export default {
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
menuTop: {
|
menuTop: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -60,11 +64,14 @@ export default {
|
|||||||
},
|
},
|
||||||
slug: {
|
slug: {
|
||||||
type: String,
|
type: String,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
menuIcon: {
|
menuIcon: {
|
||||||
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
|
required: true,
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
cardMenu: {
|
cardMenu: {
|
||||||
@ -72,6 +79,11 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
const confirmDelete = ref(null);
|
||||||
|
return { api, confirmDelete };
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -82,7 +94,7 @@ export default {
|
|||||||
return this.menuIcon ? this.menuIcon : this.$globals.icons.dotsVertical;
|
return this.menuIcon ? this.menuIcon : this.$globals.icons.dotsVertical;
|
||||||
},
|
},
|
||||||
loggedIn() {
|
loggedIn() {
|
||||||
return this.$store.getters.getIsLoggedIn;
|
return this.$auth.loggedIn;
|
||||||
},
|
},
|
||||||
baseURL() {
|
baseURL() {
|
||||||
return window.location.origin;
|
return window.location.origin;
|
||||||
@ -145,12 +157,12 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async menuAction(action) {
|
menuAction(action) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "delete":
|
case "delete":
|
||||||
this.$refs.deleteRecipieConfirm.open();
|
this.confirmDelete.open();
|
||||||
break;
|
break;
|
||||||
case "share":
|
case "share":
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
@ -183,7 +195,8 @@ export default {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
async deleteRecipe() {
|
async deleteRecipe() {
|
||||||
await api.recipes.delete(this.slug);
|
console.log("Delete Called");
|
||||||
|
await this.api.recipes.deleteOne(this.slug);
|
||||||
},
|
},
|
||||||
updateClipboard() {
|
updateClipboard() {
|
||||||
const copyText = this.recipeURL;
|
const copyText = this.recipeURL;
|
||||||
@ -196,5 +209,5 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -39,18 +39,18 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user() {
|
user() {
|
||||||
return this.$store.getters.getUserData;
|
return this.$auth.user;
|
||||||
},
|
},
|
||||||
isFavorite() {
|
isFavorite() {
|
||||||
return this.user.favoriteRecipes.includes(this.slug);
|
return this.$auth.user.favoriteRecipes.includes(this.slug);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async toggleFavorite() {
|
async toggleFavorite() {
|
||||||
if (!this.isFavorite) {
|
if (!this.isFavorite) {
|
||||||
await api.users.addFavorite(this.user.id, this.slug);
|
await api.users.addFavorite(this.$auth.user.id, this.slug);
|
||||||
} else {
|
} else {
|
||||||
await api.users.removeFavorite(this.user.id, this.slug);
|
await api.users.removeFavorite(this.$auth.user.id, this.slug);
|
||||||
}
|
}
|
||||||
this.$store.dispatch("requestUserData");
|
this.$store.dispatch("requestUserData");
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<div>
|
<div>
|
||||||
{{ $t("recipe.recipe-image") }}
|
{{ $t("recipe.recipe-image") }}
|
||||||
</div>
|
</div>
|
||||||
<TheUploadBtn
|
<AppButtonUpload
|
||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
url="none"
|
url="none"
|
||||||
file-name="image"
|
file-name="image"
|
||||||
@ -40,14 +40,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
const REFRESH_EVENT = "refresh";
|
const REFRESH_EVENT = "refresh";
|
||||||
const UPLOAD_EVENT = "upload";
|
const UPLOAD_EVENT = "upload";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
|
||||||
TheUploadBtn,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
slug: String,
|
slug: String,
|
||||||
},
|
},
|
@ -49,7 +49,7 @@
|
|||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
<div class="d-flex row justify-end">
|
<div class="d-flex row justify-end">
|
||||||
<BulkAdd class="mr-2" @bulk-data="addIngredient" />
|
<RecipeDialogBulkAdd class="mr-2" @bulk-data="addIngredient" />
|
||||||
<v-btn color="secondary" dark class="mr-4" @click="addIngredient">
|
<v-btn color="secondary" dark class="mr-4" @click="addIngredient">
|
||||||
<v-icon>{{ $globals.icons.create }}</v-icon>
|
<v-icon>{{ $globals.icons.create }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@ -71,13 +71,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BulkAdd from "@/components/Recipe/Parts/Helpers/BulkAdd";
|
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
import draggable from "vuedraggable";
|
import draggable from "vuedraggable";
|
||||||
import { utils } from "@/utils";
|
import { utils } from "@/utils";
|
||||||
|
import RecipeDialogBulkAdd from "./RecipeDialogBulkAdd";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BulkAdd,
|
RecipeDialogBulkAdd,
|
||||||
draggable,
|
draggable,
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
@ -101,18 +101,18 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
value: {
|
value: {
|
||||||
handler() {
|
handler() {
|
||||||
this.showTitleEditor = this.value.map(x => this.validateTitle(x.title));
|
this.showTitleEditor = this.value.map((x) => this.validateTitle(x.title));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.checked = this.value.map(() => false);
|
this.checked = this.value.map(() => false);
|
||||||
this.showTitleEditor = this.value.map(x => this.validateTitle(x.title));
|
this.showTitleEditor = this.value.map((x) => this.validateTitle(x.title));
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addIngredient(ingredients = null) {
|
addIngredient(ingredients = null) {
|
||||||
if (ingredients.length) {
|
if (ingredients.length) {
|
||||||
const newIngredients = ingredients.map(x => {
|
const newIngredients = ingredients.map((x) => {
|
||||||
return {
|
return {
|
||||||
title: null,
|
title: null,
|
||||||
note: x,
|
note: x,
|
@ -61,8 +61,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue";
|
|
||||||
import VueMarkdown from "@adapttive/vue-markdown";
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
|
import RecipeTimeCard from "./RecipeTimeCard.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
RecipeTimeCard,
|
RecipeTimeCard,
|
@ -48,7 +48,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
loggedIn() {
|
loggedIn() {
|
||||||
return this.$store.getters.getIsLoggedIn;
|
return this.$auth.loggedIn;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -13,16 +13,25 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
prepTime: String,
|
prepTime: {
|
||||||
totalTime: String,
|
type: String,
|
||||||
performTime: String,
|
default: null,
|
||||||
|
},
|
||||||
|
totalTime: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
performTime: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showCards() {
|
showCards() {
|
||||||
return [this.prepTime, this.totalTime, this.performTime].some(x => !this.isEmpty(x));
|
return [this.prepTime, this.totalTime, this.performTime].some((x) => !this.isEmpty(x));
|
||||||
},
|
},
|
||||||
allTimes() {
|
allTimes() {
|
||||||
return [this.validateTotalTime, this.validatePrepTime, this.validatePerformTime].filter(x => x !== null);
|
return [this.validateTotalTime, this.validatePrepTime, this.validatePerformTime].filter((x) => x !== null);
|
||||||
},
|
},
|
||||||
validateTotalTime() {
|
validateTotalTime() {
|
||||||
return !this.isEmpty(this.totalTime) ? { name: this.$t("recipe.total-time"), value: this.totalTime } : null;
|
return !this.isEmpty(this.totalTime) ? { name: this.$t("recipe.total-time"), value: this.totalTime } : null;
|
@ -7,9 +7,9 @@
|
|||||||
:submit-text="$t('general.create')"
|
:submit-text="$t('general.create')"
|
||||||
:loading="processing"
|
:loading="processing"
|
||||||
width="600px"
|
width="600px"
|
||||||
@submit="uploadZip"
|
@submit="createOnByUrl"
|
||||||
>
|
>
|
||||||
<v-form ref="urlForm" @submit.prevent="createRecipe">
|
<v-form ref="domImportFromUrlForm" @submit.prevent="createOnByUrl">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="recipeURL"
|
v-model="recipeURL"
|
||||||
@ -19,7 +19,7 @@
|
|||||||
filled
|
filled
|
||||||
rounded
|
rounded
|
||||||
class="rounded-lg"
|
class="rounded-lg"
|
||||||
:rules="[isValidWebUrl]"
|
:rules="[validators.url]"
|
||||||
:hint="$t('new-recipe.url-form-hint')"
|
:hint="$t('new-recipe.url-form-hint')"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<!-- <TheUploadBtn class="mx-auto" :text-btn="false" :post="false" @uploaded="setFile"> </TheUploadBtn> -->
|
<!-- <AppButtonUpload class="mx-auto" :text-btn="false" :post="false" @uploaded="setFile"> </AppButtonUpload> -->
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
@ -137,10 +137,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn.vue";
|
// import AppButtonUpload from "@/components/UI/Buttons/AppButtonUpload.vue";
|
||||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||||
import { fieldTypes } from "~/composables/forms";
|
import { fieldTypes } from "~/composables/forms";
|
||||||
import { useApi } from "~/composables/use-api";
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@ -151,12 +152,26 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const domCreateDialog = ref(null);
|
const domCreateDialog = ref(null);
|
||||||
|
const domCreateForm = ref<VForm | null>(null);
|
||||||
|
|
||||||
const domUploadZipDialog = ref(null);
|
const domUploadZipDialog = ref(null);
|
||||||
|
const domUploadZipForm = ref<VForm | null>(null);
|
||||||
|
|
||||||
const domImportFromUrlDialog = ref(null);
|
const domImportFromUrlDialog = ref(null);
|
||||||
|
const domImportFromUrlForm = ref<VForm | null>(null);
|
||||||
|
|
||||||
const api = useApi();
|
const api = useApiSingleton();
|
||||||
|
|
||||||
return { domCreateDialog, domUploadZipDialog, domImportFromUrlDialog, api };
|
return {
|
||||||
|
domCreateDialog,
|
||||||
|
domCreateForm,
|
||||||
|
domUploadZipDialog,
|
||||||
|
domUploadZipForm,
|
||||||
|
domImportFromUrlDialog,
|
||||||
|
domImportFromUrlForm,
|
||||||
|
api,
|
||||||
|
validators,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -204,43 +219,46 @@ export default defineComponent({
|
|||||||
mounted() {
|
mounted() {
|
||||||
if (this.$route.query.recipe_import_url) {
|
if (this.$route.query.recipe_import_url) {
|
||||||
this.addRecipe = true;
|
this.addRecipe = true;
|
||||||
this.createRecipe();
|
this.createOnByUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async manualCreateRecipe() {
|
reset() {
|
||||||
console.log(this.createRecipeData.form);
|
this.fab = false;
|
||||||
await this.api.recipes.createOne(this.createRecipeData.form.name);
|
this.error = false;
|
||||||
|
this.addRecipe = false;
|
||||||
|
this.recipeURL = "";
|
||||||
|
this.processing = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
resetVars() {
|
resetVars() {
|
||||||
this.uploadData = {
|
this.uploadData = {
|
||||||
fileName: "archive",
|
fileName: "archive",
|
||||||
file: null,
|
file: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setFile(file) {
|
setFile(file: any) {
|
||||||
this.uploadData.file = file;
|
this.uploadData.file = file;
|
||||||
console.log("Uploaded");
|
console.log("Uploaded");
|
||||||
},
|
},
|
||||||
openZipUploader() {
|
|
||||||
this.resetVars();
|
|
||||||
this.$refs.uploadZipDialog.open();
|
|
||||||
},
|
|
||||||
async uploadZip() {
|
async uploadZip() {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(this.uploadData.fileName, this.uploadData.file);
|
formData.append(this.uploadData.fileName, this.uploadData.file);
|
||||||
|
|
||||||
const response = await api.utils.uploadFile("/api/recipes/create-from-zip", formData);
|
const response = await this.api.utils.uploadFile("/api/recipes/create-from-zip", formData);
|
||||||
|
|
||||||
this.$router.push(`/recipe/${response.data.slug}`);
|
this.$router.push(`/recipe/${response.data.slug}`);
|
||||||
},
|
},
|
||||||
async createRecipe() {
|
async manualCreateRecipe() {
|
||||||
|
await this.api.recipes.createOne(this.createRecipeData.form.name);
|
||||||
|
},
|
||||||
|
async createOnByUrl() {
|
||||||
this.error = false;
|
this.error = false;
|
||||||
if (this.$refs.urlForm === undefined || this.$refs.urlForm.validate()) {
|
console.log(this.domImportFromUrlForm?.validate());
|
||||||
|
|
||||||
|
if (this.domImportFromUrlForm?.validate()) {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
const response = await api.recipes.createByURL(this.recipeURL);
|
const response = await this.api.recipes.createOneByUrl(this.recipeURL);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
if (response) {
|
if (response) {
|
||||||
this.addRecipe = false;
|
this.addRecipe = false;
|
||||||
@ -251,18 +269,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
|
||||||
this.fab = false;
|
|
||||||
this.error = false;
|
|
||||||
this.addRecipe = false;
|
|
||||||
this.recipeURL = "";
|
|
||||||
this.processing = false;
|
|
||||||
},
|
|
||||||
isValidWebUrl(url: string) {
|
|
||||||
const regEx =
|
|
||||||
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,256}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
|
||||||
return regEx.test(url) ? true : this.$t("new-recipe.must-be-a-valid-url");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
71
frontend/components/global/AppButtonCopy.vue
Normal file
71
frontend/components/global/AppButtonCopy.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<v-tooltip
|
||||||
|
ref="copyToolTip"
|
||||||
|
v-model="show"
|
||||||
|
color="success lighten-1"
|
||||||
|
top
|
||||||
|
:open-on-hover="false"
|
||||||
|
:open-on-click="true"
|
||||||
|
close-delay="500"
|
||||||
|
transition="slide-y-transition"
|
||||||
|
>
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
:color="color"
|
||||||
|
retain-focus-on-click
|
||||||
|
@click="
|
||||||
|
on.click;
|
||||||
|
textToClipboard();
|
||||||
|
"
|
||||||
|
@blur="on.blur"
|
||||||
|
>
|
||||||
|
<v-icon>{{ $globals.icons.contentCopy }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<span>
|
||||||
|
<v-icon left dark>
|
||||||
|
{{ $globals.icons.clipboardCheck }}
|
||||||
|
</v-icon>
|
||||||
|
<slot> {{ $t("general.copied") }}! </slot>
|
||||||
|
</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
copyText: {
|
||||||
|
default: "Default Copy Text",
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
default: "primary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleBlur() {
|
||||||
|
this.$refs.copyToolTip.deactivate();
|
||||||
|
},
|
||||||
|
textToClipboard() {
|
||||||
|
this.show = true;
|
||||||
|
const copyText = this.copyText;
|
||||||
|
navigator.clipboard.writeText(copyText).then(
|
||||||
|
() => console.log("Copied", copyText),
|
||||||
|
() => console.log("Copied Failed", copyText)
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.toggleBlur();
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
89
frontend/components/global/AppButtonUpload.vue
Normal file
89
frontend/components/global/AppButtonUpload.vue
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<v-form ref="file">
|
||||||
|
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||||
|
<slot v-bind="{ isSelecting, onButtonClick }">
|
||||||
|
<v-btn :loading="isSelecting" :small="small" color="accent" :text="textBtn" @click="onButtonClick">
|
||||||
|
<v-icon left> {{ effIcon }}</v-icon>
|
||||||
|
{{ text ? text : defaultText }}
|
||||||
|
</v-btn>
|
||||||
|
</slot>
|
||||||
|
</v-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { api } from "@/api";
|
||||||
|
const UPLOAD_EVENT = "uploaded";
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
small: {
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
post: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
url: String,
|
||||||
|
text: String,
|
||||||
|
icon: { default: null },
|
||||||
|
fileName: { default: "archive" },
|
||||||
|
textBtn: {
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
file: null,
|
||||||
|
isSelecting: false,
|
||||||
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
effIcon() {
|
||||||
|
return this.icon ? this.icon : this.$globals.icons.upload;
|
||||||
|
},
|
||||||
|
defaultText() {
|
||||||
|
return this.$t("general.upload");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async upload() {
|
||||||
|
if (this.file != null) {
|
||||||
|
this.isSelecting = true;
|
||||||
|
|
||||||
|
if (!this.post) {
|
||||||
|
this.$emit(UPLOAD_EVENT, this.file);
|
||||||
|
this.isSelecting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append(this.fileName, this.file);
|
||||||
|
|
||||||
|
const response = await api.utils.uploadFile(this.url, formData);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
this.$emit(UPLOAD_EVENT, response);
|
||||||
|
}
|
||||||
|
this.isSelecting = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onButtonClick() {
|
||||||
|
this.isSelecting = true;
|
||||||
|
window.addEventListener(
|
||||||
|
"focus",
|
||||||
|
() => {
|
||||||
|
this.isSelecting = false;
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$refs.uploader.click();
|
||||||
|
},
|
||||||
|
onFileChanged(e) {
|
||||||
|
this.file = e.target.files[0];
|
||||||
|
this.upload();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-btn
|
<v-btn
|
||||||
:color="btnAttrs.color"
|
:color="color || btnAttrs.color"
|
||||||
:small="small"
|
:small="small"
|
||||||
:x-small="xSmall"
|
:x-small="xSmall"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@ -76,6 +76,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<BaseButton v-if="$listeners.delete" delete secondary @click="deleteEvent" />
|
<BaseButton v-if="$listeners.delete" delete secondary @click="deleteEvent" />
|
||||||
<BaseButton v-if="$listeners.confirm" :color="color" type="submit" @click="submitEvent">
|
<BaseButton v-if="$listeners.confirm" :color="color" type="submit" @click="$emit('confirm')">
|
||||||
{{ $t("general.confirm") }}
|
{{ $t("general.confirm") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton v-else-if="$listeners.submit" type="submit" @click="submitEvent">
|
<BaseButton v-else-if="$listeners.submit" type="submit" @click="submitEvent">
|
||||||
|
@ -56,7 +56,7 @@ function getRequests(axoisInstance: NuxtAxiosInstance): ApiRequestInstance {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useApi = function (): Api {
|
export const useApiSingleton = function (): Api {
|
||||||
const { $axios } = useContext();
|
const { $axios } = useContext();
|
||||||
const requests = getRequests($axios);
|
const requests = getRequests($axios);
|
||||||
|
|
||||||
|
38
frontend/composables/use-recipe-context.ts
Normal file
38
frontend/composables/use-recipe-context.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useAsync, ref } from "@nuxtjs/composition-api";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { Recipe } from "~/types/api-types/recipe";
|
||||||
|
|
||||||
|
export const useRecipeContext = function () {
|
||||||
|
const api = useApiSingleton();
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
|
||||||
|
function getBySlug(slug: string) {
|
||||||
|
loading.value = true
|
||||||
|
const recipe = useAsync(async () => {
|
||||||
|
const { data } = await api.recipes.getOne(slug);
|
||||||
|
return data;
|
||||||
|
}, slug);
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
return recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRecipe(slug: string) {
|
||||||
|
loading.value = true
|
||||||
|
const { data } = await api.recipes.deleteOne(slug);
|
||||||
|
loading.value = false
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRecipe(slug: string, recipe: Recipe) {
|
||||||
|
loading.value = true
|
||||||
|
const { data } = await api.recipes.updateOne(slug, recipe);
|
||||||
|
loading.value = false
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {loading, getBySlug, deleteRecipe, updateRecipe}
|
||||||
|
};
|
@ -1,8 +1,11 @@
|
|||||||
const EMAIL_REGEX =
|
const EMAIL_REGEX =
|
||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
|
const URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,256}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/gm;
|
||||||
|
|
||||||
export const validators = {
|
export const validators = {
|
||||||
required: (v: string) => !!v || "This Field is Required",
|
required: (v: string) => !!v || "This Field is Required",
|
||||||
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
|
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
|
||||||
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed"
|
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed",
|
||||||
|
url: (v: string) => !v || URL_REGEX.test(v) || "Must Be A Valid URL",
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ import AppFloatingButton from "@/components/Layout/AppFloatingButton.vue";
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { AppHeader, AppSidebar, AppFloatingButton },
|
components: { AppHeader, AppSidebar, AppFloatingButton },
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
middleware: process.env.PUBLIC_SITE ? null : "auth",
|
middleware: process.env.GLOBAL_MIDDLEWARE,
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,10 @@ export default {
|
|||||||
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
|
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
router: {
|
||||||
|
base: process.env.SUB_PATH || "",
|
||||||
|
},
|
||||||
|
|
||||||
layoutTransition: {
|
layoutTransition: {
|
||||||
name: "layout",
|
name: "layout",
|
||||||
mode: "out-in",
|
mode: "out-in",
|
||||||
@ -36,9 +40,13 @@ export default {
|
|||||||
"@nuxtjs/composition-api/module",
|
"@nuxtjs/composition-api/module",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
publicRuntimeConfig: {
|
||||||
|
GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
|
||||||
|
ALLOW_SIGNUP: process.env.ALLOW_SIGNUP || true,
|
||||||
|
},
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
PUBLIC_SITE: process.env.PUBLIC_SITE || true,
|
GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
|
||||||
BASE_URL: process.env.BASE_URL || "",
|
|
||||||
ALLOW_SIGNUP: process.env.ALLOW_SIGNUP || true,
|
ALLOW_SIGNUP: process.env.ALLOW_SIGNUP || true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||||
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
import { useApi } from "~/composables/use-api";
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
import { Recipe } from "~/types/api-types/admin";
|
import { Recipe } from "~/types/api-types/admin";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RecipeCardSection },
|
components: { RecipeCardSection },
|
||||||
setup() {
|
setup() {
|
||||||
const api = useApi();
|
const api = useApiSingleton();
|
||||||
|
|
||||||
const recipes = ref<Recipe[] | null>([]);
|
const recipes = ref<Recipe[] | null>([]);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -1,16 +1,265 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<v-container>
|
||||||
|
<v-card v-if="skeleton" :color="`white ${false ? 'darken-2' : 'lighten-4'}`" class="pa-3">
|
||||||
|
<v-skeleton-loader class="mx-auto" height="700px" type="card"></v-skeleton-loader>
|
||||||
|
</v-card>
|
||||||
|
<v-card v-else-if="recipe">
|
||||||
|
<v-img
|
||||||
|
:key="imageKey"
|
||||||
|
:height="hideImage ? '50' : imageHeight"
|
||||||
|
:src="api.recipes.recipeImage(recipe.slug)"
|
||||||
|
class="d-print-none"
|
||||||
|
@error="hideImage = true"
|
||||||
|
>
|
||||||
|
<RecipeTimeCard
|
||||||
|
:class="true ? undefined : 'force-bottom'"
|
||||||
|
:prep-time="recipe.prepTime"
|
||||||
|
:total-time="recipe.totalTime"
|
||||||
|
:perform-time="recipe.performTime"
|
||||||
|
/>
|
||||||
|
</v-img>
|
||||||
|
<RecipeActionMenu
|
||||||
|
v-model="form"
|
||||||
|
:slug="recipe.slug"
|
||||||
|
:name="recipe.name"
|
||||||
|
:logged-in="$auth.loggedIn"
|
||||||
|
:open="form"
|
||||||
|
class="ml-auto"
|
||||||
|
@close="form = false"
|
||||||
|
@json="jsonEditor = !jsonEditor"
|
||||||
|
@edit="
|
||||||
|
jsonEditor = false;
|
||||||
|
form = true;
|
||||||
|
"
|
||||||
|
@save="updateRecipe(recipe.slug, recipe)"
|
||||||
|
@delete="deleteRecipe(recipe.slug)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<v-card-text>
|
||||||
|
<div v-if="form" class="d-flex justify-start align-center">
|
||||||
|
<RecipeImageUploadBtn class="my-1" :slug="recipe.slug" @upload="uploadImage" @refresh="$emit('upload')" />
|
||||||
|
<RecipeSettingsMenu class="my-1 mx-1" :value="recipe.settings" @upload="null" />
|
||||||
|
</div>
|
||||||
|
<!-- Recipe Title Section -->
|
||||||
|
<template v-if="!form">
|
||||||
|
<v-card-title class="pa-0 ma-0 headline">
|
||||||
|
{{ recipe.name }}
|
||||||
|
</v-card-title>
|
||||||
|
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<v-text-field
|
||||||
|
v-model="recipe.name"
|
||||||
|
class="my-3"
|
||||||
|
:label="$t('recipe.recipe-name')"
|
||||||
|
:rules="[validators.required]"
|
||||||
|
>
|
||||||
|
</v-text-field>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
|
<v-text-field v-model="recipe.totalTime" class="mx-2" :label="$t('recipe.total-time')"></v-text-field>
|
||||||
|
<v-text-field v-model="recipe.prepTime" class="mx-2" :label="$t('recipe.prep-time')"></v-text-field>
|
||||||
|
<v-text-field v-model="recipe.performTime" class="mx-2" :label="$t('recipe.perform-time')"></v-text-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-textarea v-model="recipe.description" auto-grow min-height="100" :label="$t('recipe.description')">
|
||||||
|
</v-textarea>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="d-flex justify-space-between align-center">
|
||||||
|
<v-btn
|
||||||
|
v-if="recipe.recipeYield"
|
||||||
|
dense
|
||||||
|
small
|
||||||
|
:hover="false"
|
||||||
|
type="label"
|
||||||
|
:ripple="false"
|
||||||
|
elevation="0"
|
||||||
|
color="secondary darken-1"
|
||||||
|
class="rounded-sm static"
|
||||||
|
>
|
||||||
|
{{ recipe.recipeYield }}
|
||||||
|
</v-btn>
|
||||||
|
<RecipeRating :key="recipe.slug" :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
||||||
|
</div>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="12" md="4" lg="4">
|
||||||
|
<RecipeIngredients :value="recipe.recipeIngredient" :edit="form" />
|
||||||
|
<div v-if="$vuetify.breakpoint.mdAndUp">
|
||||||
|
<v-card v-if="recipe.recipeCategory.length > 0" class="mt-2">
|
||||||
|
<v-card-title class="py-2">
|
||||||
|
{{ $t("recipe.categories") }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider class="mx-2"></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<RecipeChips :items="recipe.recipeCategory" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<v-card v-if="recipe.tags.length > 0" class="mt-2">
|
||||||
|
<v-card-title class="py-2">
|
||||||
|
{{ $t("tag.tags") }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider class="mx-2"></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<RecipeChips :items="recipe.tags" :is-category="false" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
<RecipeNutrition v-if="true || form" v-model="recipe.nutrition" class="mt-10" :edit="form" />
|
||||||
|
<RecipeAssets
|
||||||
|
v-if="recipe.settings.showAssets || form"
|
||||||
|
v-model="recipe.assets"
|
||||||
|
:edit="form"
|
||||||
|
:slug="recipe.slug"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
<v-divider v-if="$vuetify.breakpoint.mdAndUp" class="my-divider" :vertical="true"></v-divider>
|
||||||
|
|
||||||
|
<v-col cols="12" sm="12" md="8" lg="8">
|
||||||
|
<RecipeInstructions v-model="recipe.recipeInstructions" :edit="form" />
|
||||||
|
<RecipeNotes v-model="recipe.notes" :edit="form" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
|
||||||
|
// @ts-ignore
|
||||||
|
import VueMarkdown from "@adapttive/vue-markdown";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { validators } from "~/composables/use-validators";
|
||||||
|
import { useRecipeContext } from "~/composables/use-recipe-context";
|
||||||
|
import RecipeActionMenu from "~/components/Domain/Recipe/RecipeActionMenu.vue";
|
||||||
|
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
|
||||||
|
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
|
||||||
|
import RecipeRating from "~/components/Domain/Recipe/RecipeRating.vue";
|
||||||
|
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||||
|
import RecipeAssets from "~/components/Domain/Recipe/RecipeAssets.vue";
|
||||||
|
import RecipeNutrition from "~/components/Domain/Recipe/RecipeNutrition.vue";
|
||||||
|
import RecipeInstructions from "~/components/Domain/Recipe/RecipeInstructions.vue";
|
||||||
|
import RecipeNotes from "~/components/Domain/Recipe/RecipeNotes.vue";
|
||||||
|
import RecipeImageUploadBtn from "~/components/Domain/Recipe/RecipeImageUploadBtn.vue";
|
||||||
|
import RecipeSettingsMenu from "~/components/Domain/Recipe/RecipeSettingsMenu.vue";
|
||||||
|
import { Recipe } from "~/types/api-types/admin";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
RecipeActionMenu,
|
||||||
|
RecipeAssets,
|
||||||
|
RecipeChips,
|
||||||
|
RecipeIngredients,
|
||||||
|
RecipeInstructions,
|
||||||
|
RecipeNotes,
|
||||||
|
RecipeNutrition,
|
||||||
|
RecipeRating,
|
||||||
|
RecipeTimeCard,
|
||||||
|
RecipeImageUploadBtn,
|
||||||
|
RecipeSettingsMenu,
|
||||||
|
VueMarkdown,
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {}
|
const route = useRoute();
|
||||||
}
|
const router = useRouter();
|
||||||
})
|
const slug = route.value.params.slug;
|
||||||
|
const api = useApiSingleton();
|
||||||
|
|
||||||
|
const { getBySlug, loading } = useRecipeContext();
|
||||||
|
|
||||||
|
const recipe = getBySlug(slug);
|
||||||
|
|
||||||
|
const form = ref<boolean>(false);
|
||||||
|
|
||||||
|
async function updateRecipe(slug: string, recipe: Recipe) {
|
||||||
|
const { data } = await api.recipes.updateOne(slug, recipe);
|
||||||
|
form.value = false;
|
||||||
|
if (data?.slug) {
|
||||||
|
router.push("/recipe/" + data.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRecipe(slug: string) {
|
||||||
|
const { data } = await api.recipes.deleteOne(slug);
|
||||||
|
if (data?.slug) {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
recipe,
|
||||||
|
api,
|
||||||
|
form,
|
||||||
|
loading,
|
||||||
|
deleteRecipe,
|
||||||
|
updateRecipe,
|
||||||
|
|
||||||
|
validators,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageKey: 1,
|
||||||
|
hideImage: false,
|
||||||
|
loadFailed: false,
|
||||||
|
skeleton: false,
|
||||||
|
jsonEditor: false,
|
||||||
|
jsonEditorOptions: {
|
||||||
|
mode: "code",
|
||||||
|
search: false,
|
||||||
|
mainMenuBar: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageHeight() {
|
||||||
|
// @ts-ignore
|
||||||
|
return this.$vuetify.breakpoint.xs ? "200" : "400";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
printPage() {
|
||||||
|
window.print();
|
||||||
|
},
|
||||||
|
// validateRecipe() {
|
||||||
|
// if (this.jsonEditor) {
|
||||||
|
// return true;
|
||||||
|
// } else {
|
||||||
|
// return this.$refs.recipeEditor.validateRecipe();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// async saveImage(overrideSuccessMsg = false) {
|
||||||
|
// if (this.fileObject) {
|
||||||
|
// const newVersion = await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject, overrideSuccessMsg);
|
||||||
|
// if (newVersion) {
|
||||||
|
// this.recipeDetails.image = newVersion.data.version;
|
||||||
|
// this.imageKey += 1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// async saveRecipe() {
|
||||||
|
// if (this.validateRecipe()) {
|
||||||
|
// const slug = await this.api.recipes.updateOne(this.recipeDetails);
|
||||||
|
// if (!slug) return;
|
||||||
|
|
||||||
|
// if (this.fileObject) {
|
||||||
|
// this.saveImage(true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.form = false;
|
||||||
|
// if (slug !== this.recipe.slug) {
|
||||||
|
// this.$router.push(`/recipe/${slug}`);
|
||||||
|
// }
|
||||||
|
// window.URL.revokeObjectURL(this.api.recipes.(this.recipe.slug));
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
</style>
|
|
||||||
|
@ -1,16 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<v-container>
|
||||||
</template>
|
<RecipeCardSection
|
||||||
|
:icon="$globals.icons.primary"
|
||||||
|
:title="$t('page.all-recipes')"
|
||||||
|
:recipes="recipes"
|
||||||
|
@sort="assignSorted"
|
||||||
|
></RecipeCardSection>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
import { defineComponent, onMounted, ref } from "@nuxtjs/composition-api";
|
||||||
|
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
|
||||||
|
import { useApiSingleton } from "~/composables/use-api";
|
||||||
|
import { Recipe } from "~/types/api-types/admin";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
components: { RecipeCardSection },
|
||||||
return {}
|
setup() {
|
||||||
}
|
const api = useApiSingleton();
|
||||||
})
|
|
||||||
</script>
|
const recipes = ref<Recipe[] | null>([]);
|
||||||
|
onMounted(async () => {
|
||||||
|
const { data } = await api.recipes.getAll();
|
||||||
|
recipes.value = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { api, recipes };
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
assignSorted(val: Array<Recipe>) {
|
||||||
|
this.recipes = val;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -178,7 +178,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-btn v-if="allowSignup" class="mx-auto" text to="/user/sign-up"> Sign Up </v-btn>
|
<v-btn v-if="$config.ALLOW_SIGNUP" class="mx-auto" text to="/user/sign-up"> Sign Up </v-btn>
|
||||||
<v-btn v-else class="mx-auto" text disabled> Invite Only </v-btn>
|
<v-btn v-else class="mx-auto" text disabled> Invite Only </v-btn>
|
||||||
</v-card>
|
</v-card>
|
||||||
<!-- <v-col class="fill-height"> </v-col> -->
|
<!-- <v-col class="fill-height"> </v-col> -->
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
mdiEmail,
|
mdiEmail,
|
||||||
mdiLock,
|
mdiLock,
|
||||||
mdiEye,
|
mdiEye,
|
||||||
|
mdiDrag,
|
||||||
mdiEyeOff,
|
mdiEyeOff,
|
||||||
mdiCalendarMinus,
|
mdiCalendarMinus,
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
@ -38,7 +39,6 @@ import {
|
|||||||
mdiFilePdfBox,
|
mdiFilePdfBox,
|
||||||
mdiFileImage,
|
mdiFileImage,
|
||||||
mdiCodeJson,
|
mdiCodeJson,
|
||||||
mdiArrowUpDown,
|
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiSort,
|
mdiSort,
|
||||||
mdiOrderAlphabeticalAscending,
|
mdiOrderAlphabeticalAscending,
|
||||||
@ -103,7 +103,7 @@ const icons = {
|
|||||||
alertCircle: mdiAlertCircle,
|
alertCircle: mdiAlertCircle,
|
||||||
api: mdiApi,
|
api: mdiApi,
|
||||||
arrowLeftBold: mdiArrowLeftBold,
|
arrowLeftBold: mdiArrowLeftBold,
|
||||||
arrowUpDown: mdiArrowUpDown,
|
arrowUpDown: mdiDrag,
|
||||||
backupRestore: mdiBackupRestore,
|
backupRestore: mdiBackupRestore,
|
||||||
bellAlert: mdiBellAlert,
|
bellAlert: mdiBellAlert,
|
||||||
broom: mdiBroom,
|
broom: mdiBroom,
|
||||||
|
3
frontend/template.env
Normal file
3
frontend/template.env
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
GLOBAL_MIDDLEWARE=null # null or 'auth'
|
||||||
|
BASE_URL = ""
|
||||||
|
ALLOW_SIGNUP=true
|
@ -62,8 +62,8 @@ export interface RecipeCategoryResponse {
|
|||||||
}
|
}
|
||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name: string;
|
||||||
slug?: string;
|
slug: string;
|
||||||
image?: unknown;
|
image?: unknown;
|
||||||
description?: string;
|
description?: string;
|
||||||
recipeCategory?: string[];
|
recipeCategory?: string[];
|
||||||
|
@ -49,8 +49,8 @@ export interface Nutrition {
|
|||||||
}
|
}
|
||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name: string;
|
||||||
slug?: string;
|
slug: string;
|
||||||
image?: unknown;
|
image?: unknown;
|
||||||
description?: string;
|
description?: string;
|
||||||
recipeCategory?: string[];
|
recipeCategory?: string[];
|
||||||
|
@ -7,10 +7,10 @@ interface RequestResponse<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiRequestInstance {
|
export interface ApiRequestInstance {
|
||||||
get<T>(url: string, data?: object): Promise<RequestResponse<T>>;
|
get<T>(url: string, data?: T | object): Promise<RequestResponse<T>>;
|
||||||
post<T>(url: string, data: object): Promise<RequestResponse<T>>;
|
post<T>(url: string, data: T | object): Promise<RequestResponse<T>>;
|
||||||
put<T>(url: string, data: object): Promise<RequestResponse<T>>;
|
put<T>(url: string, data: T | object): Promise<RequestResponse<T>>;
|
||||||
patch<T>(url: string, data: object): Promise<RequestResponse<T>>;
|
patch<T>(url: string, data: T | object): Promise<RequestResponse<T>>;
|
||||||
delete<T>(url: string, data: object): Promise<RequestResponse<T>>;
|
delete<T>(url: string, data?: T | object): Promise<RequestResponse<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
frontend/types/ts-shim.d.ts
vendored
Normal file
8
frontend/types/ts-shim.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from "vue"
|
||||||
|
export default Vue
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VForm extends HTMLFormElement {
|
||||||
|
validate(): boolean;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user