1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 05:25:26 +02:00

Feature/import export single recipe (#576)

* remove duplicate keys

* show context menu when not logged in

* remove console.log

* hide menu when printing

* add response to event

* add type definitions

* show context menu always

* add image name enums

* upload/download single recipe

* cleanup menu views+ localization

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-06-21 16:25:37 -07:00 committed by GitHub
parent 3220595a83
commit f5faff66d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 207 additions and 35 deletions

View file

@ -4,8 +4,6 @@ import i18n from "@/i18n.js";
export const utilsAPI = {
// import { api } from "@/api";
uploadFile(url, fileObject) {
console.log("API Called");
return apiReq.post(
url,
fileObject,

View file

@ -18,6 +18,7 @@
allow-overflow
close-delay="125"
open-on-hover
content-class="d-print-none"
>
<template v-slot:activator="{ on, attrs }">
<v-btn :fab="fab" :small="fab" :color="color" :icon="!fab" dark v-bind="attrs" v-on="on" @click.prevent>
@ -25,11 +26,7 @@
</v-btn>
</template>
<v-list dense>
<v-list-item
v-for="(item, index) in loggedIn && cardMenu ? userMenu : defaultMenu"
:key="index"
@click="menuAction(item.action)"
>
<v-list-item v-for="(item, index) in displayedMenu" :key="index" @click="menuAction(item.action)">
<v-list-item-icon>
<v-icon v-text="item.icon" :color="item.color"></v-icon>
</v-list-item-icon>
@ -53,6 +50,10 @@ export default {
type: Boolean,
default: true,
},
showPrint: {
type: Boolean,
default: false,
},
fab: {
type: Boolean,
default: false,
@ -88,20 +89,28 @@ export default {
recipeURL() {
return `${this.baseURL}/recipe/${this.slug}`;
},
printerMenu() {
return {
title: this.$t("general.print"),
icon: this.$globals.icons.printer,
color: "accent",
action: "print",
};
},
defaultMenu() {
return [
{
title: this.$t("general.print"),
icon: this.$globals.icons.printer,
color: "accent",
action: "print",
},
{
title: this.$t("general.share"),
icon: this.$globals.icons.shareVariant,
color: "accent",
action: "share",
},
{
title: this.$t("general.download"),
icon: this.$globals.icons.download,
color: "accent",
action: "download",
},
];
},
userMenu() {
@ -118,9 +127,18 @@ export default {
color: "accent",
action: "edit",
},
...this.defaultMenu,
];
},
displayedMenu() {
let menu = this.defaultMenu;
if (this.loggedIn && this.cardMenu) {
menu = [...this.userMenu, ...menu];
}
if (this.showPrint) {
menu = [this.printerMenu, ...menu];
}
return menu;
},
recipeText() {
return this.$t("recipe.share-recipe-message", [this.name]);
},
@ -159,6 +177,9 @@ export default {
case "print":
this.$router.push(`/recipe/${this.slug}` + "?print=true");
break;
case "download":
window.open(`/api/recipes/${this.slug}/zip`);
break;
default:
break;
}

View file

@ -20,13 +20,23 @@
<div v-if="!value" class="custom-btn-group ma-1">
<v-tooltip bottom color="info">
<template v-slot:activator="{ on, attrs }">
<v-btn fab small class="mx-1" color="info" v-bind="attrs" v-on="on" @click="$emit('input', true)">
<v-btn
v-if="loggedIn"
fab
small
class="mx-1"
color="info"
v-bind="attrs"
v-on="on"
@click="$emit('input', true)"
>
<v-icon> {{ $globals.icons.edit }} </v-icon>
</v-btn>
</template>
<span>{{ $t("general.edit") }}</span>
</v-tooltip>
<ContextMenu
show-print
:menu-top="false"
:slug="slug"
:name="name"
@ -74,6 +84,10 @@ export default {
type: Boolean,
default: false,
},
loggedIn: {
type: Boolean,
default: false,
},
},
data() {
return {

View file

@ -58,8 +58,10 @@ export default {
let formData = new FormData();
formData.append(this.fileName, this.file);
if (await api.utils.uploadFile(this.url, formData)) {
this.$emit(UPLOAD_EVENT);
const response = await api.utils.uploadFile(this.url, formData);
if (response) {
this.$emit(UPLOAD_EVENT, response);
}
this.isSelecting = false;
}

View file

@ -45,7 +45,7 @@
</template>
<script>
import i18n from "@/i18n";
import i18n from "@/i18n.js";
export default {
props: {
color: {
@ -65,12 +65,14 @@ export default {
},
top: {
default: null,
type: Boolean,
},
submitText: {
default: () => i18n.t("general.create"),
},
keepOpen: {
default: false,
type: Boolean,
},
},
data() {
@ -101,16 +103,17 @@ export default {
this.$emit("submit");
this.submitted = true;
},
deleteEvent() {
this.$emit("delete");
this.submitted = true;
},
open() {
console.log("Open Dialog");
this.dialog = true;
},
close() {
this.dialog = false;
},
deleteEvent() {
this.$emit("delete");
this.submitted = true;
},
},
};
</script>

View file

@ -84,6 +84,26 @@
</v-form>
</v-card>
</v-dialog>
<BaseDialog
title="Upload a Recipe"
:titleIcon="$globals.icons.zip"
:submit-text="$t('general.import')"
ref="uploadZipDialog"
@submit="uploadZip"
:loading="processing"
>
<v-card-text class="mt-1 pb-0">
{{ $t("new-recipe.upload-individual-zip-file") }}
<div class="headline mx-auto mb-0 pb-0 text-center">
{{ this.fileName }}
</div>
</v-card-text>
<v-card-actions>
<TheUploadBtn class="mx-auto" :text-btn="false" @uploaded="setFile" :post="false"> </TheUploadBtn>
</v-card-actions>
</BaseDialog>
<v-speed-dial v-model="fab" :open-on-hover="absolute" :fixed="absolute" :bottom="absolute" :right="absolute">
<template v-slot:activator>
<v-btn v-model="fab" :color="absolute ? 'accent' : 'white'" dark :icon="!absolute" :fab="absolute">
@ -106,13 +126,27 @@
</template>
<span>{{ $t("general.new") }}</span>
</v-tooltip>
<v-tooltip left dark color="info">
<template v-slot:activator="{ on, attrs }">
<v-btn fab dark small color="info" v-bind="attrs" v-on="on" @click="openZipUploader">
<v-icon>{{ $globals.icons.zip }}</v-icon>
</v-btn>
</template>
<span>{{ $t("general.upload") }}</span>
</v-tooltip>
</v-speed-dial>
</div>
</template>
<script>
import { api } from "@/api";
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn.vue";
import BaseDialog from "@/components/UI/Dialogs/BaseDialog.vue";
export default {
components: {
TheUploadBtn,
BaseDialog,
},
props: {
absolute: {
default: false,
@ -124,6 +158,10 @@ export default {
fab: false,
addRecipe: false,
processing: false,
uploadData: {
fileName: "archive",
file: null,
},
};
},
@ -143,9 +181,34 @@ export default {
return this.$route.query.recipe_import_url || "";
},
},
fileName() {
return this.uploadData.file?.name || "";
},
},
methods: {
resetVars() {
this.uploadData = {
fileName: "archive",
file: null,
};
},
setFile(file) {
this.uploadData.file = file;
console.log("Uploaded");
},
openZipUploader() {
this.resetVars();
this.$refs.uploadZipDialog.open();
},
async uploadZip() {
let formData = new FormData();
formData.append(this.uploadData.fileName, this.uploadData.file);
const response = await api.utils.uploadFile("/api/recipes/create-from-zip", formData);
this.$router.push(`/recipe/${response.data.slug}`);
},
async createRecipe() {
this.error = false;
if (this.$refs.urlForm === undefined || this.$refs.urlForm.validate()) {
@ -161,7 +224,6 @@ export default {
}
}
},
reset() {
this.fab = false;
this.error = false;
@ -173,10 +235,6 @@ export default {
let 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 : "Must be a Valid URL";
},
bookmark() {
return `javascript:(function()%7Bvar url %3D document.URL %3B%0Avar mealie %3D "http%3A%2F%2Flocalhost%3A8080%2F%23"%0Avar dest %3D mealie %2B "%2F%3Frecipe_import_url%3D" %2B url%0Awindow.open(dest%2C '_blank')%7D)()%3B`;
},
},
};
</script>

View file

@ -37,9 +37,7 @@
"apprise-url": "Apprise URL",
"database": "Database",
"new-notification-form-description": "Mealie uses the Apprise library to generate notifications. They offer many options for services to use for notifications. Refer to their wiki for a comprehensive guide on how to create the URL for your service. If available, selecting the type of your notification may include extra features.",
"new-version": "New version available!",
"notification": "Notification",
"refresh": "Refresh",
"scheduled": "Scheduled",
"something-went-wrong": "Something Went Wrong!",
"subscribed-events": "Subscribed Events",
@ -191,6 +189,7 @@
"from-url": "Import a Recipe",
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list",
"recipe-url": "Recipe URL",
"upload-individual-zip-file": "Upload an individual .zip file exported from another Mealie instance.",
"url-form-hint": "Copy and paste a link from your favorite recipe website"
},
"page": {

View file

@ -10,7 +10,13 @@
</v-img>
<br v-else />
<RecipePageActionMenu :value="true" @json="jsonEditor = true" @edit="jsonEditor = false" @save="createRecipe" />
<RecipePageActionMenu
logged-in
:value="true"
@json="jsonEditor = true"
@edit="jsonEditor = false"
@save="createRecipe"
/>
<div v-if="jsonEditor">
<!-- Probably not the best way, but it works! -->

View file

@ -24,7 +24,7 @@
:slug="recipeDetails.slug"
:name="recipeDetails.name"
v-model="form"
v-if="loggedIn"
:logged-in="loggedIn"
:open="showIcons"
@close="form = false"
@json="jsonEditor = !jsonEditor"

View file

@ -6,7 +6,7 @@ import { mealRoutes } from "./meal";
import { generalRoutes } from "./general";
import { store } from "@/store";
import VueRouter from "vue-router";
import { loadLanguageAsync } from "@/i18n"
import { loadLanguageAsync } from "@/i18n";
import Vue from "vue";
import i18n from "@/i18n.js";

View file

@ -91,6 +91,7 @@ import {
mdiArrowLeftBold,
mdiMinus,
mdiWindowClose,
mdiFolderZipOutline,
} from "@mdi/js";
const icons = {
@ -176,6 +177,7 @@ const icons = {
weatherSunny: mdiWeatherSunny,
webhook: mdiWebhook,
windowClose: mdiWindowClose,
zip: mdiFolderZipOutline,
// Crud
createAlt: mdiPlus,