mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-03 20:45:23 +02:00
v0.2.0 (#143)
* Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog * Dropping Mongo From Dev Branch (#89) * Fix link to Docker Hub Found an extra s. DESTROYED it. * initial pass * second pass cleanup * backup card framework * backup card functionality * translation * upload button vile creation * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * dev bug: change data location to prevent reloads * api docs * api docs bug * workflow update Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * Add French Translation (#93) * New tests (#94) * dev-bug: fixed vscode freezes * test: refactor database init to support tests Co-authored-by: Hayden <hay-kot@pm.me> * Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden <hay-kot@pm.me> * Fix typos (#96) * Settings, Themes and Migration Route Tests (#100) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing Co-authored-by: Hayden <hay-kot@pm.me> * Refactor + New Docker File (#105) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding Co-authored-by: Hayden <hay-kot@pm.me> * Meal planner improvements (#107) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing * restructure test folder * git attributes * tests: migration, settings, theme routes testing * docker-file shrink * rebuild * refactor: moving directories around * adding funding * mealplan redesign Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#108) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me> * Upload component (#113) * unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript * UI updates + name validation * docs: changelog + sp * fixed route links * changelog Co-authored-by: Hayden <hay-kot@pm.me> * fixed menu links * fixed poetry install on docker.dev build * Migration redesign (#119) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes Co-authored-by: Hayden <hay-kot@pm.me> * Fix link to dev-notes.md (#110) * translation: add swedish (#128) * language: da is Danish * translations: add swedish * scraper: unescape html in instructions (#129) Some urls erroneously deliver escaped html their instructions, sometimes they are even escaped on multiple levels like here: https://www.ica.se/recept/kladdig-kladdkaka-722982/ ``` >>> normalize_instruction("S&auml;tt ugnen p&aring; 200&deg;C.") 'Sätt ugnen på 200°C.' ``` * v0.2.0 Updates (#130) * migration redesign init * new color picker * changelog * added UI language selection * fix layout issue on recipe editor * remove git as dependency * added UI editor for original URL * CI/CD Tests * test: fixed migration routes * test todos * bug/added docker volume * chowdow test data * partial image recipe image testing * added card section card * settings form * homepage cetegory ui * frontend category placeholder * fixed broken scheduler * remove old files * removed temp test Co-authored-by: Hayden <hay-kot@pm.me> * Fix missing translations key (#133) * translation: add simplified & traditional chinese * Fix missing translations * fix chinese translations * v0.2.0 Release Candidate (#141) * Fix link to Docker Hub Found an extra s. DESTROYED it. * Release v0.1.0 Candidate (#85) * Changed uvicorn port to 80 * Changed port in docker-compose to match dockerfile * Readded environment variables in docker-compose * production image rework * Use opengraph metadata to make basic recipe cards when full recipe metadata is not available * fixed instrucitons on parse * add last_recipe * automated testing * roadmap update * Sqlite (#75) * file structure * auto-test * take 2 * refactor ap scheduler and startup process * fixed scraper error * database abstraction * database abstraction * port recipes over to new schema * meal migration * start settings migration * finale mongo port * backup improvements * migration imports to new DB structure * unused import cleanup * docs strings * settings and theme import logic * cleanup * fixed tinydb error * requirements * fuzzy search * remove scratch file * sqlalchemy models * improved search ui * recipe models almost done * sql modal population * del scratch * rewrite database model mixins * mostly grabage * recipe updates * working sqllite * remove old files and reorganize * final cleanup Co-authored-by: Hayden <hay-kot@pm.me> * Backup card (#78) * backup / import dialog * upgrade to new tag method * New import card * rename settings.py to app_config.py * migrate to poetry for development * fix failing test Co-authored-by: Hayden <hay-kot@pm.me> * added mkdocs to docker-compose * Translations (#72) * Translations + danish * changed back proxy target to use ENV * Resolved more merge conflicts * Removed test in translation * Documentation of translations * Updated translations * removed old packages Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com> * fail to start bug fixes * feature: prep/cook/total time slots (#80) Co-authored-by: Hayden <hay-kot@pm.me> * missing bind attributes * Bug fixes (#81) * fix: url remains after succesful import * docs: changelog + update todos * arm image * arm compose * compose updates * update poetry * arm support Co-authored-by: Hayden <hay-kot@pm.me> * dockerfile hotfix * dockerfile hotfix * Version Release Final Touches (#84) * Remove slim * bug: opacity issues * bug: startup failure with no database * ci/cd on dev branch * formatting * v0.1.0 documentation Co-authored-by: Hayden <hay-kot@pm.me> * db init hotfix * bug: fix crash in mongo * fix mongo bug * fixed version notifier * finale changelog Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> * build container * webscraper hotfix * notes hot fix * bug: mongo updates fail #99 * Fix error message (#101) * gh funding * Create Issue Templates (#125) * Create bug_report.md * Create config.yml Included a link to feature requests. * Update config.yml Fixed link I had for testing to the actual link * Update bug_report.md fix capitalization * Update .github/ISSUE_TEMPLATE/bug_report.md Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * merge kentors changes * refactor/recipe routers * category/tag database relationship and endpoints * frontend category management * update branch todos * bug/normalize recipe steps html * remove console.log + refactor categories * fix categories database errors * refactor/ router endpoint * refactor/ remove old code * drag and drop ingredients * general cleanup * route refactoring * changelog * api refactoring + random cleanup * fixed backwards sort * Update mkdocs.yml (#137) Fix warning from Deploy Docs github action * fixed navigate on enter in search * refactor/create global css * added category scroll * cleanup todos * debug routes * docs/new gifs & general updates * cleanup * fix list test Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com> * fix build * fix duplicate editor * fixed docker mount problem * python 3.9 * added tasks for non-docker development * remove old scripts * dev updates * fixed no image upload option * get version from backend * final docs pass * .gitignore Co-authored-by: kentora <=> Co-authored-by: Hayden <hay-kot@pm.me> Co-authored-by: Richard Mitic <richard.h.mitic@gmail.com> Co-authored-by: kentora <kentora@kentora.dk> Co-authored-by: David Young <davidy@funkypenguin.co.nz> Co-authored-by: Bastien <43323819+Batgame@users.noreply.github.com> Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com> Co-authored-by: Nick CJ <17556895+nickcj931@users.noreply.github.com> Co-authored-by: dekvall <dkvldev@gmail.com> Co-authored-by: wengtad <wengtad93@gmail.com> Co-authored-by: Alexei Pesic <pesic.alexei@gmail.com> Co-authored-by: Andrew <dpieski@gmail.com> Co-authored-by: Stephen Brown II <Stephen.Brown2@gmail.com>
This commit is contained in:
parent
3ec0f2ec21
commit
b3573dc078
233 changed files with 11756 additions and 2491 deletions
|
@ -1,10 +1,6 @@
|
|||
<template>
|
||||
<v-row>
|
||||
<MealSelect
|
||||
:forceDialog="dialog"
|
||||
@close="dialog = false"
|
||||
@select="setSlug($event)"
|
||||
/>
|
||||
<SearchDialog ref="mealselect" @select="setSlug" />
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="12"
|
||||
|
@ -19,10 +15,10 @@
|
|||
<v-img
|
||||
height="200"
|
||||
:src="getImage(meal.slug)"
|
||||
@click="selectRecipe(index)"
|
||||
@click="openSearch(index)"
|
||||
></v-img>
|
||||
<v-card-title class="my-n3 mb-n6">{{ meal.dateText }}</v-card-title>
|
||||
<v-card-subtitle> {{ meal.slug }}</v-card-subtitle>
|
||||
<v-card-subtitle> {{ meal.name }}</v-card-subtitle>
|
||||
</v-card>
|
||||
</v-hover>
|
||||
</v-col>
|
||||
|
@ -31,10 +27,10 @@
|
|||
|
||||
<script>
|
||||
import utils from "../../utils";
|
||||
import MealSelect from "./MealSelect";
|
||||
import SearchDialog from "../UI/SearchDialog";
|
||||
export default {
|
||||
components: {
|
||||
MealSelect,
|
||||
SearchDialog,
|
||||
},
|
||||
props: {
|
||||
value: Array,
|
||||
|
@ -44,7 +40,6 @@ export default {
|
|||
recipeData: [],
|
||||
cardData: [],
|
||||
activeIndex: 0,
|
||||
dialog: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -53,20 +48,14 @@ export default {
|
|||
return utils.getImageURL(slug);
|
||||
}
|
||||
},
|
||||
setSlug(slug) {
|
||||
setSlug(name, slug) {
|
||||
let index = this.activeIndex;
|
||||
this.value[index]["slug"] = slug;
|
||||
this.value[index]["name"] = name;
|
||||
},
|
||||
selectRecipe(index) {
|
||||
openSearch(index) {
|
||||
this.activeIndex = index;
|
||||
this.dialog = true;
|
||||
},
|
||||
getProperty(index, property) {
|
||||
try {
|
||||
return this.recipeData[index][property];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
this.$refs.mealselect.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="headline"> {{$t('meal-plan.edit-meal-plan')}} </v-card-title>
|
||||
<v-card-title class="headline">
|
||||
{{ $t("meal-plan.edit-meal-plan") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
<MealPlanCard v-model="mealPlan.meals" />
|
||||
<v-row align="center" justify="end">
|
||||
<v-card-actions>
|
||||
<v-btn color="success" text @click="update"> {{$t('general.update')}} </v-btn>
|
||||
<v-btn color="success" text @click="update">
|
||||
{{ $t("general.update") }}
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
</v-card-actions>
|
||||
</v-row>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="headline">
|
||||
{{$t('meal-plan.create-a-new-meal-plan')}}
|
||||
{{ $t("meal-plan.create-a-new-meal-plan") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
|
@ -71,9 +71,11 @@
|
|||
<v-row align="center" justify="end">
|
||||
<v-card-actions>
|
||||
<v-btn color="success" @click="random" v-if="meals[1]" text>
|
||||
{{$t('general.random')}}
|
||||
{{ $t("general.random") }}
|
||||
</v-btn>
|
||||
<v-btn color="success" @click="save" text>
|
||||
{{ $t("general.save") }}
|
||||
</v-btn>
|
||||
<v-btn color="success" @click="save" text> {{$t('general.save')}} </v-btn>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="show = !show"> </v-btn>
|
||||
|
@ -149,11 +151,13 @@ export default {
|
|||
methods: {
|
||||
get_random(list) {
|
||||
const object = list[Math.floor(Math.random() * list.length)];
|
||||
return object.slug;
|
||||
return object;
|
||||
},
|
||||
random() {
|
||||
this.meals.forEach((element, index) => {
|
||||
this.meals[index]["slug"] = this.get_random(this.items);
|
||||
let recipe = this.get_random(this.items);
|
||||
this.meals[index]["slug"] = recipe.slug;
|
||||
this.meals[index]["name"] = recipe.name;
|
||||
});
|
||||
},
|
||||
processTime(index) {
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
<template>
|
||||
<v-row justify="center">
|
||||
<v-dialog v-model="dialog" persistent max-width="800">
|
||||
<v-card>
|
||||
<v-card-title class="headline"> {{$t('meal-plan.choose-a-recipe')}} </v-card-title>
|
||||
<v-card-text>
|
||||
<v-autocomplete
|
||||
:items="availableRecipes"
|
||||
v-model="selected"
|
||||
clearable
|
||||
return
|
||||
dense
|
||||
hide-details
|
||||
hide-selected
|
||||
item-text="slug"
|
||||
:label="$t('search.search-for-a-recipe')"
|
||||
single-line
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-slot:item="{ item }">
|
||||
<v-row align="center" @click="dialog = false">
|
||||
<v-col sm="2">
|
||||
<v-img
|
||||
max-height="100"
|
||||
max-width="100"
|
||||
:src="getImage(item.image)"
|
||||
></v-img>
|
||||
</v-col>
|
||||
<v-col sm="10">
|
||||
<h3>
|
||||
{{ item.name }}
|
||||
</h3>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.close')}} </v-btn>
|
||||
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.select')}} </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from "../../utils";
|
||||
export default {
|
||||
props: {
|
||||
forceDialog: Boolean,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
selected: "",
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
forceDialog() {
|
||||
this.dialog = this.forceDialog;
|
||||
},
|
||||
selected() {
|
||||
if (this.selected) {
|
||||
this.$emit("select", this.selected);
|
||||
}
|
||||
},
|
||||
dialog() {
|
||||
if (this.dialog === false) {
|
||||
this.$emit("close");
|
||||
} else {
|
||||
this.selected = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
availableRecipes() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getImage(slug) {
|
||||
return utils.getImageURL(slug);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -2,11 +2,11 @@
|
|||
<div class="text-center">
|
||||
<v-dialog v-model="dialog" width="700">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn color="accent" dark v-bind="attrs" v-on="on"> API Extras </v-btn>
|
||||
<v-btn color="accent" dark v-bind="attrs" v-on="on"> {{ $t("recipe.api-extras") }} </v-btn>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title> API Extras </v-card-title>
|
||||
<v-card-title> {{ $t("recipe.api-extras") }} </v-card-title>
|
||||
|
||||
<v-card-text :key="formKey">
|
||||
<v-row
|
||||
|
@ -28,14 +28,14 @@
|
|||
</v-col>
|
||||
<v-col cols="12" md="3" sm="6">
|
||||
<v-text-field
|
||||
label="Object Key"
|
||||
:label="$t('recipe.object-key')"
|
||||
:value="key"
|
||||
@input="updateKey(index)"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="8" sm="6">
|
||||
<v-text-field label="Object Value" v-model="extras[key]">
|
||||
<v-text-field :label="$t('recipe.object-value')" v-model="extras[key]">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
@ -46,17 +46,17 @@
|
|||
<v-card-actions>
|
||||
<v-form ref="addKey">
|
||||
<v-text-field
|
||||
label="New Key Name"
|
||||
:label="$t('recipe.new-key-name')"
|
||||
v-model="newKeyName"
|
||||
class="pr-4"
|
||||
:rules="[rules.required, rules.whiteSpace]"
|
||||
></v-text-field>
|
||||
</v-form>
|
||||
<v-btn color="info" text @click="append"> Add Key</v-btn>
|
||||
<v-btn color="info" text @click="append"> {{ $t("recipe.add-key") }} </v-btn>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn color="success" text @click="save"> Save </v-btn>
|
||||
<v-btn color="success" text @click="save"> {{ $t("general.save") }} </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
@ -74,9 +74,9 @@ export default {
|
|||
dialog: false,
|
||||
formKey: 1,
|
||||
rules: {
|
||||
required: (v) => !!v || "Key Name Required",
|
||||
required: (v) => !!v || this.$i18n.t("recipe.key-name-required"),
|
||||
whiteSpace: (v) =>
|
||||
!v || v.split(" ").length <= 1 || "No White Space Allowed",
|
||||
!v || v.split(" ").length <= 1 || this.$i18n.t("recipe.no-white-space-allowed"),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-form ref="form">
|
||||
<v-card-text>
|
||||
<v-row dense>
|
||||
<v-col cols="3"></v-col>
|
||||
|
@ -12,35 +12,47 @@
|
|||
></v-file-input>
|
||||
</v-col>
|
||||
<v-col cols="3"></v-col>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
label="Total Time"
|
||||
v-model="value.totalTime"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Prep Time"
|
||||
v-model="value.prepTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
label="Cook Time / Perform Time"
|
||||
v-model="value.performTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
</v-row>
|
||||
</v-row>
|
||||
<v-text-field class="my-3" :label="$t('recipe.recipe-name')" v-model="value.name">
|
||||
<v-row dense>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('recipe.total-time')"
|
||||
v-model="value.totalTime"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
:label="$t('recipe.prep-time')"
|
||||
v-model="value.prepTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
<v-col
|
||||
><v-text-field
|
||||
:label="$t('recipe.perform-time')"
|
||||
v-model="value.performTime"
|
||||
></v-text-field
|
||||
></v-col>
|
||||
</v-row>
|
||||
<v-text-field
|
||||
class="my-3"
|
||||
:label="$t('recipe.recipe-name')"
|
||||
v-model="value.name"
|
||||
:rules="[rules.required]"
|
||||
>
|
||||
</v-text-field>
|
||||
<v-textarea height="100" :label="$t('recipe.description')" v-model="value.description">
|
||||
<v-textarea
|
||||
height="100"
|
||||
:label="$t('recipe.description')"
|
||||
v-model="value.description"
|
||||
>
|
||||
</v-textarea>
|
||||
<div class="my-2"></div>
|
||||
<v-row dense disabled>
|
||||
<v-col sm="5">
|
||||
<v-text-field :label="$t('recipe.servings')" v-model="value.recipeYield">
|
||||
<v-text-field
|
||||
:label="$t('recipe.servings')"
|
||||
v-model="value.recipeYield"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col></v-col>
|
||||
|
@ -54,34 +66,50 @@
|
|||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="12" md="4" lg="4">
|
||||
<h2 class="mb-4">{{$t('recipe.ingredients')}}</h2>
|
||||
<div
|
||||
v-for="(ingredient, index) in value.recipeIngredient"
|
||||
:key="generateKey('ingredient', index)"
|
||||
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
||||
<draggable
|
||||
v-model="value.recipeIngredient"
|
||||
@start="drag = true"
|
||||
@end="drag = false"
|
||||
>
|
||||
<v-row align="center">
|
||||
<v-btn
|
||||
fab
|
||||
x-small
|
||||
color="white"
|
||||
class="mr-2"
|
||||
elevation="0"
|
||||
@click="removeIngredient(index)"
|
||||
<transition-group
|
||||
type="transition"
|
||||
:name="!drag ? 'flip-list' : null"
|
||||
>
|
||||
<div
|
||||
v-for="(ingredient, index) in value.recipeIngredient"
|
||||
:key="generateKey('ingredient', index)"
|
||||
>
|
||||
<v-icon color="error">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
<v-text-field
|
||||
:label="$t('recipe.ingredient')"
|
||||
v-model="value.recipeIngredient[index]"
|
||||
></v-text-field>
|
||||
</v-row>
|
||||
</div>
|
||||
<v-row align="center">
|
||||
<v-text-field
|
||||
class="mr-2"
|
||||
:label="$t('recipe.ingredient')"
|
||||
v-model="value.recipeIngredient[index]"
|
||||
append-outer-icon="mdi-menu"
|
||||
mdi-move-resize
|
||||
solo
|
||||
dense
|
||||
>
|
||||
<v-icon
|
||||
class="mr-n1"
|
||||
slot="prepend"
|
||||
color="error"
|
||||
@click="removeIngredient(index)"
|
||||
>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</v-text-field>
|
||||
</v-row>
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
|
||||
<v-btn color="secondary" fab dark small @click="addIngredient">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<BulkAdd @bulk-data="appendIngredients" />
|
||||
|
||||
<h2 class="mt-6">{{$t('recipe.categories')}}</h2>
|
||||
<h2 class="mt-6">{{ $t("recipe.categories") }}</h2>
|
||||
<v-combobox
|
||||
dense
|
||||
multiple
|
||||
|
@ -89,6 +117,11 @@
|
|||
item-color="secondary"
|
||||
deletable-chips
|
||||
v-model="value.categories"
|
||||
hide-selected
|
||||
:items="categories"
|
||||
text="name"
|
||||
:search-input.sync="categoriesSearchInput"
|
||||
@change="categoriesSearchInput = ''"
|
||||
>
|
||||
<template v-slot:selection="data">
|
||||
<v-chip
|
||||
|
@ -103,8 +136,18 @@
|
|||
</template>
|
||||
</v-combobox>
|
||||
|
||||
<h2 class="mt-4">{{$t('recipe.tags')}}</h2>
|
||||
<v-combobox dense multiple chips deletable-chips v-model="value.tags">
|
||||
<h2 class="mt-4">{{ $t("recipe.tags") }}</h2>
|
||||
<v-combobox
|
||||
dense
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
v-model="value.tags"
|
||||
hide-selected
|
||||
:items="tags"
|
||||
:search-input.sync="tagsSearchInput"
|
||||
@change="tagssSearchInput = ''"
|
||||
>
|
||||
<template v-slot:selection="data">
|
||||
<v-chip
|
||||
:input-value="data.selected"
|
||||
|
@ -118,7 +161,7 @@
|
|||
</template>
|
||||
</v-combobox>
|
||||
|
||||
<h2 class="my-4">{{$t('recipe.notes')}}</h2>
|
||||
<h2 class="my-4">{{ $t("recipe.notes") }}</h2>
|
||||
<v-card
|
||||
class="mt-1"
|
||||
v-for="(note, index) in value.notes"
|
||||
|
@ -137,12 +180,15 @@
|
|||
<v-icon color="error">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
<v-text-field
|
||||
label="Title"
|
||||
:label="$t('recipe.title')"
|
||||
v-model="value.notes[index]['title']"
|
||||
></v-text-field>
|
||||
</v-row>
|
||||
|
||||
<v-textarea :label="$t('recipe.note')" v-model="value.notes[index]['text']">
|
||||
<v-textarea
|
||||
:label="$t('recipe.note')"
|
||||
v-model="value.notes[index]['text']"
|
||||
>
|
||||
</v-textarea>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
@ -155,7 +201,7 @@
|
|||
<v-divider class="my-divider" :vertical="true"></v-divider>
|
||||
|
||||
<v-col cols="12" sm="12" md="8" lg="8">
|
||||
<h2 class="mb-4">{{$t('recipe.instructions')}}</h2>
|
||||
<h2 class="mb-4">{{ $t("recipe.instructions") }}</h2>
|
||||
<div v-for="(step, index) in value.recipeInstructions" :key="index">
|
||||
<v-hover v-slot="{ hover }">
|
||||
<v-card
|
||||
|
@ -173,7 +219,9 @@
|
|||
@click="removeStep(index)"
|
||||
>
|
||||
<v-icon color="error">mdi-delete</v-icon> </v-btn
|
||||
>{{ $t('recipe.step-index', {step: index + 1}) }}</v-card-title
|
||||
>{{
|
||||
$t("recipe.step-index", { step: index + 1 })
|
||||
}}</v-card-title
|
||||
>
|
||||
<v-card-text>
|
||||
<v-textarea
|
||||
|
@ -189,13 +237,19 @@
|
|||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<BulkAdd @bulk-data="appendSteps" />
|
||||
<v-text-field
|
||||
v-model="value.orgURL"
|
||||
class="mt-10"
|
||||
:label="$t('recipe.original-url')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import api from "../../../api";
|
||||
import utils from "../../../utils";
|
||||
import BulkAdd from "./BulkAdd";
|
||||
|
@ -204,16 +258,36 @@ export default {
|
|||
components: {
|
||||
BulkAdd,
|
||||
ExtrasEditor,
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
value: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drag: false,
|
||||
fileObject: null,
|
||||
rules: {
|
||||
required: v => !!v || this.$i18n.t("recipe.key-name-required"),
|
||||
whiteSpace: v =>
|
||||
!v ||
|
||||
v.split(" ").length <= 1 ||
|
||||
this.$i18n.t("recipe.no-white-space-allowed"),
|
||||
},
|
||||
categoriesSearchInput: "",
|
||||
tagsSearchInput: "",
|
||||
categories: [],
|
||||
tags: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getCategories();
|
||||
},
|
||||
methods: {
|
||||
async getCategories() {
|
||||
let response = await api.categories.get_all();
|
||||
this.categories = response.map(cat => cat.name);
|
||||
},
|
||||
uploadImage() {
|
||||
this.$emit("upload", this.fileObject);
|
||||
},
|
||||
|
@ -259,7 +333,7 @@ export default {
|
|||
|
||||
appendSteps(steps) {
|
||||
let processSteps = [];
|
||||
steps.forEach((element) => {
|
||||
steps.forEach(element => {
|
||||
processSteps.push({ text: element });
|
||||
});
|
||||
|
||||
|
@ -289,6 +363,13 @@ export default {
|
|||
saveExtras(extras) {
|
||||
this.value.extras = extras;
|
||||
},
|
||||
validateRecipe() {
|
||||
if (this.$refs.form.validate()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
v-if="totalTime"
|
||||
></v-divider>
|
||||
<v-col v-if="totalTime">
|
||||
<div><strong> Total Time </strong></div>
|
||||
<div><strong> {{ $t("recipe.total-time") }} </strong></div>
|
||||
<div>{{ totalTime }}</div>
|
||||
</v-col>
|
||||
<v-divider
|
||||
|
@ -30,7 +30,7 @@
|
|||
v-if="prepTime"
|
||||
></v-divider>
|
||||
<v-col v-if="prepTime">
|
||||
<div><strong> Prep Time </strong></div>
|
||||
<div><strong> {{ $t("recipe.prep-time") }} </strong></div>
|
||||
<div>{{ prepTime }}</div>
|
||||
</v-col>
|
||||
<v-divider
|
||||
|
@ -40,7 +40,7 @@
|
|||
v-if="performTime"
|
||||
></v-divider>
|
||||
<v-col v-if="performTime">
|
||||
<div><strong> Cook Time </strong></div>
|
||||
<div><strong> {{ $t("recipe.perform-time") }} </strong></div>
|
||||
<div>{{ performTime }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
target="_blank"
|
||||
class="rounded-sm mr-4"
|
||||
>
|
||||
{{$t('recipe.original-recipe')}}
|
||||
{{$t('recipe.original-url')}}
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div>
|
||||
<ImportDialog
|
||||
:name="selectedName"
|
||||
:date="selectedDate"
|
||||
ref="import_dialog"
|
||||
@import="importBackup"
|
||||
@delete="deleteBackup"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col
|
||||
:sm="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="4"
|
||||
v-for="backup in backups"
|
||||
:key="backup.name"
|
||||
>
|
||||
<v-card hover outlined @click="openDialog(backup)">
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" sm="2">
|
||||
<v-icon large color="primary"> mdi-backup-restore </v-icon>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="10">
|
||||
<div>
|
||||
<strong>{{ backup.name }}</strong>
|
||||
</div>
|
||||
<div>{{ readableTime(backup.date) }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImportDialog from "./ImportDialog";
|
||||
import api from "../../../api";
|
||||
import utils from "../../../utils";
|
||||
export default {
|
||||
props: {
|
||||
backups: Array,
|
||||
},
|
||||
components: {
|
||||
ImportDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedName: "",
|
||||
selectedDate: "",
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openDialog(backup) {
|
||||
this.selectedDate = this.readableTime(backup.date);
|
||||
this.selectedName = backup.name;
|
||||
this.$refs.import_dialog.open();
|
||||
},
|
||||
readableTime(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
return utils.getDateAsText(date);
|
||||
},
|
||||
async importBackup(data) {
|
||||
this.$emit("loading");
|
||||
let response = await api.backups.import(data.name, data);
|
||||
|
||||
let failed = response.data.failed;
|
||||
let succesful = response.data.successful;
|
||||
|
||||
this.$emit("finished", succesful, failed);
|
||||
},
|
||||
deleteBackup(data) {
|
||||
this.$emit("loading");
|
||||
|
||||
api.backups.delete(data.name);
|
||||
this.selectedBackup = null;
|
||||
this.backupLoading = false;
|
||||
|
||||
this.$emit("finished");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -65,15 +65,15 @@
|
|||
<v-divider></v-divider>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn disabled color="success" text @click="raiseEvent('download')">
|
||||
{{$t('general.download')}}
|
||||
<v-btn color="accent" text :href="`/api/backups/${name}/download`">
|
||||
{{ $t("general.download") }}
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="error" text @click="raiseEvent('delete')">
|
||||
{{$t('general.delete')}}
|
||||
{{ $t("general.delete") }}
|
||||
</v-btn>
|
||||
<v-btn color="success" text @click="raiseEvent('import')">
|
||||
{{$t('general.import')}}
|
||||
<v-btn color="success" outlined @click="raiseEvent('import')">
|
||||
{{ $t("general.import") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
|
123
frontend/src/components/Settings/Backup/NewBackupCard.vue
Normal file
123
frontend/src/components/Settings/Backup/NewBackupCard.vue
Normal file
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title> {{ $t("settings.backup.create-heading") }} </v-card-title>
|
||||
<v-card-text class="mt-n3">
|
||||
<v-text-field
|
||||
dense
|
||||
:label="$t('settings.backup.backup-tag')"
|
||||
v-model="tag"
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions class="mt-n9">
|
||||
<v-switch v-model="fullBackup" :label="switchLabel"></v-switch>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" text @click="createBackup()">
|
||||
{{ $t("general.create") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<v-card-text v-if="!fullBackup" class="mt-n6">
|
||||
<v-row>
|
||||
<v-col sm="4">
|
||||
<p>{{ $t("general.options") }}:</p>
|
||||
<v-checkbox
|
||||
v-for="option in options"
|
||||
:key="option.text"
|
||||
class="mb-n4 mt-n3"
|
||||
dense
|
||||
:label="option.text"
|
||||
v-model="option.value"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<p>{{ $t("general.templates") }}:</p>
|
||||
<v-checkbox
|
||||
v-for="template in availableTemplates"
|
||||
:key="template"
|
||||
class="mb-n4 mt-n3"
|
||||
dense
|
||||
:label="template"
|
||||
@click="appendTemplate(template)"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tag: null,
|
||||
fullBackup: true,
|
||||
loading: false,
|
||||
options: {
|
||||
recipes: {
|
||||
value: true,
|
||||
text: this.$t("general.recipes"),
|
||||
},
|
||||
settings: {
|
||||
value: true,
|
||||
text: this.$t("general.settings"),
|
||||
},
|
||||
themes: {
|
||||
value: true,
|
||||
text: this.$t("general.themes"),
|
||||
},
|
||||
},
|
||||
availableTemplates: [],
|
||||
selectedTemplates: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAvailableBackups();
|
||||
},
|
||||
computed: {
|
||||
switchLabel() {
|
||||
if (this.fullBackup) {
|
||||
return this.$t("settings.backup.full-backup");
|
||||
} else return this.$t("settings.backup.partial-backup");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getAvailableBackups() {
|
||||
let response = await api.backups.requestAvailable();
|
||||
response.templates.forEach((element) => {
|
||||
this.availableTemplates.push(element);
|
||||
});
|
||||
},
|
||||
async createBackup() {
|
||||
this.loading = true;
|
||||
|
||||
let data = {
|
||||
tag: this.tag,
|
||||
options: {
|
||||
recipes: this.options.recipes.value,
|
||||
settings: this.options.settings.value,
|
||||
themes: this.options.themes.value,
|
||||
},
|
||||
templates: this.selectedTemplates,
|
||||
};
|
||||
|
||||
|
||||
await api.backups.create(data);
|
||||
this.loading = false;
|
||||
|
||||
this.$emit("created");
|
||||
},
|
||||
appendTemplate(templateName) {
|
||||
if (this.selectedTemplates.includes(templateName)) {
|
||||
let index = this.selectedTemplates.indexOf(templateName);
|
||||
if (index !== -1) {
|
||||
this.selectedTemplates.splice(index, 1);
|
||||
}
|
||||
} else this.selectedTemplates.push(templateName);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,42 +1,44 @@
|
|||
<template>
|
||||
<v-card :loading="backupLoading" class="mt-3">
|
||||
<v-card-title class="headline">
|
||||
{{$t('settings.backup-and-exports')}}
|
||||
{{ $t("settings.backup-and-exports") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
<p>
|
||||
{{$t('settings.backup-info')}}
|
||||
</p>
|
||||
|
||||
<v-row dense align="center">
|
||||
<v-col dense cols="12" sm="12" md="4">
|
||||
<v-text-field v-model="backupTag" :label="$t('settings.backup-tag')"></v-text-field>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" ss="12">
|
||||
<NewBackupCard @created="processFinished" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="12" md="3">
|
||||
<v-combobox
|
||||
auto-select-first
|
||||
:label="$t('settings.markdown-template')"
|
||||
:items="availableTemplates"
|
||||
v-model="selectedTemplate"
|
||||
></v-combobox>
|
||||
</v-col>
|
||||
<v-col dense cols="12" sm="12" md="2">
|
||||
<v-btn block text color="accent" @click="createBackup" width="165">
|
||||
{{$t('settings.backup-recipes')}}
|
||||
</v-btn>
|
||||
<v-col cols="12" md="6" sm="12">
|
||||
<p>
|
||||
{{ $t("settings.backup-info") }}
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<BackupCard
|
||||
<v-divider class="my-3"></v-divider>
|
||||
<v-card-title class="mt-n6">
|
||||
{{ $t("settings.available-backups") }}
|
||||
<span>
|
||||
<UploadBtn
|
||||
class="mt-1"
|
||||
url="/api/backups/upload"
|
||||
@uploaded="getAvailableBackups"
|
||||
/>
|
||||
</span>
|
||||
<v-spacer></v-spacer>
|
||||
</v-card-title>
|
||||
<AvailableBackupCard
|
||||
@loading="backupLoading = true"
|
||||
@finished="processFinished"
|
||||
:backups="availableBackups"
|
||||
/>
|
||||
<SuccessFailureAlert
|
||||
success-header="Successfully Imported"
|
||||
ref="report"
|
||||
:title="$t('settings.backup.backup-restore-report')"
|
||||
:success-header="$t('settings.backup.successfully-imported')"
|
||||
:success="successfulImports"
|
||||
failed-header="Failed Imports"
|
||||
:failed-header="$t('settings.backup.failed-imports')"
|
||||
:failed="failedImports"
|
||||
/>
|
||||
</v-card-text>
|
||||
|
@ -46,23 +48,23 @@
|
|||
<script>
|
||||
import api from "../../../api";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import BackupCard from "./BackupCard";
|
||||
import UploadBtn from "../../UI/UploadBtn";
|
||||
import AvailableBackupCard from "./AvailableBackupCard";
|
||||
import NewBackupCard from "./NewBackupCard";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SuccessFailureAlert,
|
||||
BackupCard,
|
||||
UploadBtn,
|
||||
AvailableBackupCard,
|
||||
NewBackupCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
failedImports: [],
|
||||
successfulImports: [],
|
||||
backupLoading: false,
|
||||
backupTag: null,
|
||||
selectedBackup: null,
|
||||
selectedTemplate: null,
|
||||
availableBackups: [],
|
||||
availableTemplates: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -85,22 +87,12 @@ export default {
|
|||
this.backupLoading = false;
|
||||
}
|
||||
},
|
||||
async createBackup() {
|
||||
this.backupLoading = true;
|
||||
|
||||
let response = await api.backups.create(this.backupTag, this.templates);
|
||||
|
||||
if (response.status == 201) {
|
||||
this.selectedBackup = null;
|
||||
this.getAvailableBackups();
|
||||
this.backupLoading = false;
|
||||
}
|
||||
},
|
||||
processFinished(successful = null, failed = null) {
|
||||
this.getAvailableBackups();
|
||||
this.backupLoading = false;
|
||||
this.successfulImports = successful;
|
||||
this.failedImports = failed;
|
||||
this.$refs.report.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
177
frontend/src/components/Settings/General/HomePageSettings.vue
Normal file
177
frontend/src/components/Settings/General/HomePageSettings.vue
Normal file
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<h2 class="mt-1 mb-1">Home Page</h2>
|
||||
<v-row align="center" justify="center" dense class="mb-n7 pb-n5">
|
||||
<v-col sm="2">
|
||||
<v-switch v-model="showRecent" label="Show Recent"></v-switch>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-slider
|
||||
class="pt-4"
|
||||
label="Card Per Section"
|
||||
v-model="showLimit"
|
||||
max="30"
|
||||
dense
|
||||
color="primary"
|
||||
min="3"
|
||||
thumb-label
|
||||
>
|
||||
</v-slider>
|
||||
</v-col>
|
||||
<v-spacer></v-spacer>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-card outlined min-height="250">
|
||||
<v-card-text class="pt-2 pb-1">
|
||||
<h3>Homepage Categories</h3>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-list
|
||||
min-height="200"
|
||||
dense
|
||||
max-height="200"
|
||||
style="overflow:auto"
|
||||
>
|
||||
<v-list-item-group>
|
||||
<draggable
|
||||
v-model="homeCategories"
|
||||
group="categories"
|
||||
:style="{
|
||||
minHeight: `150px`,
|
||||
}"
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(item, index) in homeCategories"
|
||||
:key="`${item.name}-${index}`"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-icon @click="deleteActiveCategory(index)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
</draggable>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-card outlined min-height="250px">
|
||||
<v-card-text class="pt-2 pb-1">
|
||||
<h3>
|
||||
All Categories
|
||||
<span>
|
||||
<v-btn absolute right x-small color="success" icon>
|
||||
<v-icon>mdi-plus</v-icon></v-btn
|
||||
>
|
||||
</span>
|
||||
</h3>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-list
|
||||
min-height="200"
|
||||
dense
|
||||
max-height="200"
|
||||
style="overflow:auto"
|
||||
>
|
||||
<v-list-item-group>
|
||||
<draggable
|
||||
v-model="categories"
|
||||
group="categories"
|
||||
:style="{
|
||||
minHeight: `150px`,
|
||||
}"
|
||||
>
|
||||
<v-list-item
|
||||
v-for="(item, index) in categories"
|
||||
:key="`${item.name}-${index}`"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-icon
|
||||
@click="deleteCategoryfromDatabase(item.slug)"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
</draggable>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" @click="saveSettings" class="mr-2">
|
||||
<v-icon left> mdi-content-save </v-icon>
|
||||
{{ $t("general.save") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../../api";
|
||||
import draggable from "vuedraggable";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
homeCategories: null,
|
||||
showLimit: null,
|
||||
showRecent: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getOptions();
|
||||
},
|
||||
computed: {
|
||||
categories() {
|
||||
return this.$store.getters.getCategories;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deleteCategoryfromDatabase(category) {
|
||||
api.categories.delete(category);
|
||||
this.$store.dispatch("requestHomePageSettings");
|
||||
},
|
||||
getOptions() {
|
||||
this.showLimit = this.$store.getters.getShowLimit;
|
||||
this.showRecent = this.$store.getters.getShowRecent;
|
||||
this.homeCategories = this.$store.getters.getHomeCategories;
|
||||
},
|
||||
deleteActiveCategory(index) {
|
||||
this.homeCategories.splice(index, 1);
|
||||
},
|
||||
saveSettings() {
|
||||
this.homeCategories.forEach((element, index) => {
|
||||
element.position = index + 1;
|
||||
});
|
||||
this.$store.commit("setShowRecent", this.showRecent);
|
||||
this.$store.commit("setShowLimit", this.showLimit);
|
||||
this.$store.commit("setHomeCategories", this.homeCategories);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
67
frontend/src/components/Settings/General/index.vue
Normal file
67
frontend/src/components/Settings/General/index.vue
Normal file
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ $t("settings.general-settings") }}
|
||||
<v-spacer></v-spacer>
|
||||
<span>
|
||||
<v-btn class="pt-1" text href="/docs">
|
||||
{{ $t("settings.local-api") }}
|
||||
<v-icon right>mdi-open-in-new</v-icon>
|
||||
</v-btn>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<HomePageSettings />
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<h2 class="mt-1 mb-4">{{ $t("settings.language") }}</h2>
|
||||
<v-row>
|
||||
<v-col cols="3">
|
||||
<v-select
|
||||
dense
|
||||
v-model="selectedLang"
|
||||
:items="langOptions"
|
||||
item-text="name"
|
||||
item-value="value"
|
||||
:label="$t('settings.language')"
|
||||
>
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HomePageSettings from "./HomePageSettings";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HomePageSettings,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
langOptions: [],
|
||||
selectedLang: "en",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getOptions();
|
||||
},
|
||||
watch: {
|
||||
selectedLang() {
|
||||
this.$store.commit("setLang", this.selectedLang);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getOptions() {
|
||||
this.langOptions = this.$store.getters.getAllLangs;
|
||||
this.selectedLang = this.$store.getters.getActiveLang;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<v-card-text>
|
||||
<p>
|
||||
{{$t('migration.currently-chowdown-via-public-repo-url-is-the-only-supported-type-of-migration')}}
|
||||
</p>
|
||||
<v-form ref="form">
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="5" sm="5">
|
||||
<v-text-field
|
||||
v-model="repo"
|
||||
:label="$t('migration.chowdown-repo-url')"
|
||||
:rules="[rules.required]"
|
||||
>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" sm="5">
|
||||
<v-btn text color="info" @click="importRepo"> {{$t('migration.migrate')}} </v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<v-alert v-if="failedRecipes[1]" outlined dense type="error">
|
||||
<h4>{{$t('migration.failed-recipes')}}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="fail in this.failedRecipes" :key="fail">
|
||||
{{ fail }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
<v-alert v-if="failedImages[1]" outlined dense type="error">
|
||||
<h4>{{$t('migration.failed-images')}}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="fail in this.failedImages" :key="fail">
|
||||
{{ fail }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../../api";
|
||||
// import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
// import TimePicker from "./Webhooks/TimePicker";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
processRan: false,
|
||||
failedImages: [],
|
||||
failedRecipes: [],
|
||||
repo: "",
|
||||
rules: {
|
||||
required: (v) => !!v || "Selection Required",
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async importRepo() {
|
||||
if (this.$refs.form.validate()) {
|
||||
this.$emit("loading");
|
||||
let response = await api.migrations.migrateChowdown(this.repo);
|
||||
this.failedImages = response.failedImages;
|
||||
this.failedRecipes = response.failedRecipes;
|
||||
this.$emit("finished");
|
||||
this.processRan = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
96
frontend/src/components/Settings/Migration/MigrationCard.vue
Normal file
96
frontend/src/components/Settings/Migration/MigrationCard.vue
Normal file
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<v-card class="my-2" :loading="loading">
|
||||
<v-card-title>
|
||||
{{ title }}
|
||||
<v-spacer></v-spacer>
|
||||
<span>
|
||||
<UploadBtn
|
||||
class="mt-1"
|
||||
:url="`/api/migrations/${folder}/upload`"
|
||||
@uploaded="$emit('refresh')"
|
||||
/>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text> {{ description }}</v-card-text>
|
||||
<div v-if="available[0]">
|
||||
<v-card
|
||||
outlined
|
||||
v-for="migration in available"
|
||||
:key="migration.name"
|
||||
class="ma-2"
|
||||
>
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" sm="2">
|
||||
<v-icon large color="primary"> mdi-import </v-icon>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="10">
|
||||
<div>
|
||||
<strong>{{ migration.name }}</strong>
|
||||
</div>
|
||||
<div>{{ readableTime(migration.date) }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="mt-n6">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="error" text @click="deleteMigration(migration.name)">
|
||||
{{ $t("general.delete") }}
|
||||
</v-btn>
|
||||
<v-btn color="accent" text @click="importMigration(migration.name)">
|
||||
{{ $t("general.import") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-card class="text-center ma-2">
|
||||
<v-card-text>
|
||||
{{ $t("migration.no-migration-data-available") }}
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<br />
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UploadBtn from "../../UI/UploadBtn";
|
||||
import utils from "../../../utils";
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
props: {
|
||||
folder: String,
|
||||
title: String,
|
||||
description: String,
|
||||
available: Array,
|
||||
},
|
||||
components: {
|
||||
UploadBtn,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteMigration(file_name) {
|
||||
api.migrations.delete(this.folder, file_name);
|
||||
this.$emit("refresh");
|
||||
},
|
||||
async importMigration(file_name) {
|
||||
this.loading == true;
|
||||
let response = await api.migrations.import(this.folder, file_name);
|
||||
this.$emit("imported", response.successful, response.failed);
|
||||
this.loading == false;
|
||||
},
|
||||
readableTime(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
return utils.getDateAsText(date);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,101 +0,0 @@
|
|||
<template>
|
||||
<v-card-text>
|
||||
<p>
|
||||
{{$t('migration.you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected')}}
|
||||
</p>
|
||||
<v-form ref="form">
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="5" sm="12">
|
||||
<v-select
|
||||
:items="availableImports"
|
||||
v-model="selectedImport"
|
||||
:label="$t('migration.nextcloud-data')"
|
||||
:rules="[rules.required]"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="2" sm="12">
|
||||
<v-btn text color="info" @click="importRecipes"> {{$t('migration.migrate')}} </v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" md="1" sm="12">
|
||||
<v-btn text color="error" @click="deleteImportValidation">
|
||||
{{$t('general.delete')}}
|
||||
</v-btn>
|
||||
<Confirmation
|
||||
:title="$t('general.delete-data')"
|
||||
:message="$t('migration.delete-confirmation')"
|
||||
color="error"
|
||||
icon="mdi-alert-circle"
|
||||
ref="deleteThemeConfirm"
|
||||
v-on:confirm="deleteImport()"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" md="5" sm="12">
|
||||
<UploadMigrationButton @uploaded="getAvaiableImports" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<SuccessFailureAlert
|
||||
:success-header="$t('migration.successfully-imported-from-nextcloud')"
|
||||
:success="successfulImports"
|
||||
failed-header="$t('migration.failed-imports')"
|
||||
:failed="failedImports"
|
||||
/>
|
||||
</v-card-text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../../api";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import UploadMigrationButton from "./UploadMigrationButton";
|
||||
import Confirmation from "../../UI/Confirmation";
|
||||
export default {
|
||||
components: {
|
||||
SuccessFailureAlert,
|
||||
UploadMigrationButton,
|
||||
Confirmation,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
successfulImports: [],
|
||||
failedImports: [],
|
||||
availableImports: [],
|
||||
selectedImport: null,
|
||||
rules: {
|
||||
required: (v) => !!v || "Selection Required",
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.getAvaiableImports();
|
||||
},
|
||||
methods: {
|
||||
async getAvaiableImports() {
|
||||
this.availableImports = await api.migrations.getNextcloudImports();
|
||||
},
|
||||
async importRecipes() {
|
||||
if (this.$refs.form.validate()) {
|
||||
this.$emit("loading");
|
||||
let data = await api.migrations.importNextcloud(this.selectedImport);
|
||||
|
||||
this.successfulImports = data.successful;
|
||||
this.failedImports = data.failed;
|
||||
this.$emit("finished");
|
||||
}
|
||||
},
|
||||
deleteImportValidation() {
|
||||
if (this.$refs.form.validate()) {
|
||||
this.$refs.deleteThemeConfirm.open();
|
||||
}
|
||||
},
|
||||
async deleteImport() {
|
||||
await api.migrations.delete(this.selectedImport);
|
||||
this.getAvaiableImports();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,49 +0,0 @@
|
|||
<template>
|
||||
<v-form ref="file">
|
||||
<v-file-input
|
||||
:loading="loading"
|
||||
:label="$t('migration.upload-an-archive')"
|
||||
v-model="file"
|
||||
accept=".zip"
|
||||
@change="upload"
|
||||
:prepend-icon="icon"
|
||||
class="file-icon"
|
||||
>
|
||||
</v-file-input>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
file: null,
|
||||
loading: false,
|
||||
icon: "mdi-paperclip",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async upload() {
|
||||
if (this.file != null) {
|
||||
this.loading = true;
|
||||
let formData = new FormData();
|
||||
formData.append("archive", this.file);
|
||||
|
||||
await api.migrations.uploadFile(formData);
|
||||
|
||||
this.loading = false;
|
||||
this.$emit("uploaded");
|
||||
this.file = null;
|
||||
this.icon = "mdi-check";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.file-icon {
|
||||
transition-duration: 5s;
|
||||
}
|
||||
</style>
|
|
@ -1,44 +1,96 @@
|
|||
<template>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title class="headline"> {{$t('migration.recipe-migration')}} </v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<div>
|
||||
<SuccessFailureAlert
|
||||
:title="$t('migration.migration-report')"
|
||||
ref="report"
|
||||
:failedHeader="$t('migration.failed-imports')"
|
||||
:failed="failed"
|
||||
:successHeader="$t('migration.successful-imports')"
|
||||
:success="success"
|
||||
/>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title class="headline">
|
||||
{{ $t("migration.recipe-migration") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
</v-card>
|
||||
|
||||
<v-tabs v-model="tab">
|
||||
<v-tab>Chowdown</v-tab>
|
||||
<v-tab>Nextcloud Recipes</v-tab>
|
||||
|
||||
<v-tab-item>
|
||||
<ChowdownCard @loading="loading = true" @finished="finished" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<NextcloudCard @loading="loading = true" @finished="finished" />
|
||||
</v-tab-item>
|
||||
</v-tabs>
|
||||
</v-card>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
:sm="12"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
v-for="migration in migrations"
|
||||
:key="migration.title"
|
||||
>
|
||||
<MigrationCard
|
||||
:title="migration.title"
|
||||
:folder="migration.urlVariable"
|
||||
:description="migration.description"
|
||||
:available="migration.availableImports"
|
||||
@refresh="getAvailableMigrations"
|
||||
@imported="showReport"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import ChowdownCard from "./ChowdownCard";
|
||||
import NextcloudCard from "./NextcloudCard";
|
||||
// import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
// import TimePicker from "./Webhooks/TimePicker";
|
||||
import MigrationCard from "./MigrationCard";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import api from "../../../api";
|
||||
export default {
|
||||
components: {
|
||||
ChowdownCard,
|
||||
NextcloudCard,
|
||||
MigrationCard,
|
||||
SuccessFailureAlert,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: null,
|
||||
loading: false,
|
||||
success: [],
|
||||
failed: [],
|
||||
migrations: {
|
||||
nextcloud: {
|
||||
title: this.$t("migration.nextcloud.title"),
|
||||
description: this.$t("migration.nextcloud.description"),
|
||||
urlVariable: "nextcloud",
|
||||
availableImports: [],
|
||||
},
|
||||
chowdown: {
|
||||
title: this.$t("migration.chowdown.title"),
|
||||
description: this.$t("migration.chowdown.description"),
|
||||
urlVariable: "chowdown",
|
||||
availableImports: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAvailableMigrations();
|
||||
},
|
||||
methods: {
|
||||
finished() {
|
||||
this.loading = false;
|
||||
this.$store.dispatch("requestRecentRecipes");
|
||||
},
|
||||
async getAvailableMigrations() {
|
||||
let response = await api.migrations.getMigrations();
|
||||
response.forEach((element) => {
|
||||
if (element.type === "nextcloud") {
|
||||
this.migrations.nextcloud.availableImports = element.files;
|
||||
} else if (element.type === "chowdown") {
|
||||
this.migrations.chowdown.availableImports = element.files;
|
||||
}
|
||||
});
|
||||
},
|
||||
showReport(successful, failed) {
|
||||
this.success = successful;
|
||||
this.failed = failed;
|
||||
this.$refs.report.open();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,36 +1,28 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-btn block :color="value" @click="dialog = true">
|
||||
{{ buttonText }}
|
||||
</v-btn>
|
||||
<v-dialog v-model="dialog" width="400">
|
||||
<v-card>
|
||||
<v-card-title> {{ buttonText }} {{$t('settings.color')}} </v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="color"> </v-text-field>
|
||||
<v-row>
|
||||
<v-col></v-col>
|
||||
<v-col>
|
||||
<v-color-picker
|
||||
dot-size="28"
|
||||
hide-inputs
|
||||
hide-mode-switch
|
||||
mode="hexa"
|
||||
:show-swatches="swatches"
|
||||
swatches-max-height="300"
|
||||
v-model="color"
|
||||
@change="updateColor"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<v-col></v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn text @click="toggleSwatches"> {{$t('settings.swatches')}} </v-btn>
|
||||
<v-btn text @click="dialog = false"> {{$t('general.select')}} </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<div class="text-center">
|
||||
<h3>{{ buttonText }}</h3>
|
||||
</div>
|
||||
<v-text-field v-model="color" hide-details class="ma-0 pa-0" solo>
|
||||
<template v-slot:append>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
top
|
||||
nudge-bottom="105"
|
||||
nudge-left="16"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<div :style="swatchStyle" v-on="on" swatches-max-height="300" />
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-text class="pa-0">
|
||||
<v-color-picker v-model="color" flat show-swatches />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -44,21 +36,30 @@ export default {
|
|||
return {
|
||||
dialog: false,
|
||||
swatches: false,
|
||||
color: "#FF00FF",
|
||||
color: "#1976D2",
|
||||
mask: "!#XXXXXXXX",
|
||||
menu: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
swatchStyle() {
|
||||
const { value, menu } = this;
|
||||
return {
|
||||
backgroundColor: value,
|
||||
cursor: "pointer",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
borderRadius: menu ? "50%" : "4px",
|
||||
transition: "border-radius 200ms ease-in-out",
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
color() {
|
||||
this.updateColor();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSwatches() {
|
||||
if (this.swatches) {
|
||||
this.swatches = false;
|
||||
} else this.swatches = true;
|
||||
},
|
||||
updateColor() {
|
||||
this.$emit("input", this.color);
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<v-card-title> {{$t('settings.add-a-new-theme')}} </v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
label="Theme Name"
|
||||
:label="$t('settings.theme.theme-name')"
|
||||
v-model="themeName"
|
||||
:rules="[rules.required]"
|
||||
></v-text-field>
|
||||
|
@ -34,7 +34,7 @@ export default {
|
|||
dialog: false,
|
||||
themeName: "",
|
||||
rules: {
|
||||
required: (val) => !!val || "Required.",
|
||||
required: (val) => !!val || this.$t("settings.theme.theme-name-is-required"),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
mandatory
|
||||
@change="setStoresDarkMode"
|
||||
>
|
||||
<v-btn value="system"> Default to system </v-btn>
|
||||
<v-btn value="system">
|
||||
{{ $t("settings.theme.default-to-system") }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn value="light"> {{ $t("settings.theme.light") }} </v-btn>
|
||||
|
||||
|
@ -43,7 +45,7 @@
|
|||
|
||||
<v-form ref="form" lazy-validation>
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="4" sm="3">
|
||||
<v-col md="4" sm="3">
|
||||
<v-select
|
||||
:label="$t('settings.theme.saved-color-theme')"
|
||||
:items="availableThemes"
|
||||
|
@ -56,13 +58,13 @@
|
|||
>
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<NewThemeDialog @new-theme="appendTheme" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="1">
|
||||
<v-btn text color="error" @click="deleteSelectedThemeValidation">
|
||||
Delete
|
||||
</v-btn>
|
||||
<v-col>
|
||||
<v-btn-toggle group class="mt-n5">
|
||||
<NewThemeDialog @new-theme="appendTheme" class="mt-1" />
|
||||
<v-btn text color="error" @click="deleteSelectedThemeValidation">
|
||||
{{ $t("general.delete") }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<Confirmation
|
||||
:title="$t('settings.theme.delete-theme')"
|
||||
:message="
|
||||
|
@ -74,6 +76,7 @@
|
|||
v-on:confirm="deleteSelectedTheme()"
|
||||
/>
|
||||
</v-col>
|
||||
<v-spacer></v-spacer>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<v-row dense align-content="center" v-if="selectedTheme.colors">
|
||||
|
@ -123,15 +126,11 @@
|
|||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-row>
|
||||
<v-col> </v-col>
|
||||
<v-col></v-col>
|
||||
<v-col align="end">
|
||||
<v-btn text color="success" @click="saveThemes">
|
||||
{{ $t("settings.theme.save-colors-and-apply-theme") }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" @click="saveThemes" class="mr-2">
|
||||
<v-icon left> mdi-content-save </v-icon>
|
||||
{{ $t("general.save") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
<template>
|
||||
<v-card>
|
||||
<v-card-title class="headline">
|
||||
{{$t('settings.webhooks.meal-planner-webhooks')}}
|
||||
{{ $t("settings.webhooks.meal-planner-webhooks") }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p v-html="$t('settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at', {time: time})"></p>
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
"settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at"
|
||||
)
|
||||
}}
|
||||
<strong>{{ time }}</strong>
|
||||
</p>
|
||||
|
||||
<v-row dense align="center">
|
||||
<v-col cols="12" md="2" sm="5">
|
||||
<v-switch
|
||||
v-model="enabled"
|
||||
inset
|
||||
:label="$t('general.enabled')"
|
||||
class="my-n3"
|
||||
></v-switch>
|
||||
<v-switch v-model="enabled" :label="$t('general.enabled')"></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3" sm="5">
|
||||
<TimePickerDialog @save-time="saveTime" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="4" sm="5">
|
||||
<v-btn text color="info" @click="testWebhooks"> {{$t('settings.webhooks.test-webhooks')}} </v-btn>
|
||||
<v-btn text color="info" @click="testWebhooks">
|
||||
<v-icon left> mdi-webhook </v-icon>
|
||||
{{ $t("settings.webhooks.test-webhooks") }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
@ -38,19 +43,14 @@
|
|||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn icon color="success" @click="addWebhook">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col> </v-col>
|
||||
<v-col align="end">
|
||||
<v-btn text color="success" @click="saveWebhooks">
|
||||
{{$t('settings.webhooks.save-webhooks')}}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-btn icon color="success" @click="addWebhook">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" @click="saveWebhooks" class="mr-2 mb-1">
|
||||
<v-icon left> mdi-content-save </v-icon>
|
||||
{{ $t("general.save") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
<Confirmation
|
||||
title="Delete Recpie"
|
||||
message="Are you sure you want to delete this recipie?"
|
||||
:title="$t('recipe.delete-recipe')"
|
||||
:message="$t('recipe.delete-confirmation')"
|
||||
color="error"
|
||||
icon="mdi-alert-circle"
|
||||
ref="deleteRecipieConfirm"
|
||||
|
@ -43,12 +43,12 @@ export default {
|
|||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
Confirmation
|
||||
Confirmation,
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -66,8 +66,8 @@ export default {
|
|||
},
|
||||
json() {
|
||||
this.$emit("json");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
81
frontend/src/components/UI/CardSection.vue
Normal file
81
frontend/src/components/UI/CardSection.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<div class="mt-n5">
|
||||
<v-card flat class="transparent" height="60px">
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn-toggle group>
|
||||
<v-btn text :to="`/recipes/${title.toLowerCase()}`">
|
||||
{{ title.toUpperCase() }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
<v-spacer></v-spacer>
|
||||
<v-col align="end">
|
||||
<v-menu offset-y v-if="sortable">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn-toggle group>
|
||||
<v-btn text v-bind="attrs" v-on="on"> Sort </v-btn>
|
||||
</v-btn-toggle>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item @click="$emit('sort-recent')">
|
||||
<v-list-item-title> Recent </v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="$emit('sort')">
|
||||
<v-list-item-title> A-Z </v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-row>
|
||||
<v-col
|
||||
:sm="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
v-for="recipe in recipes.slice(0, cardLimit)"
|
||||
:key="recipe.name"
|
||||
>
|
||||
<RecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecipeCard from "./RecipeCard";
|
||||
export default {
|
||||
components: {
|
||||
RecipeCard,
|
||||
},
|
||||
props: {
|
||||
sortable: {
|
||||
default: false,
|
||||
},
|
||||
title: String,
|
||||
recipes: Array,
|
||||
cardLimit: {
|
||||
default: 6,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.transparent {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
64
frontend/src/components/UI/CategorySidebar.vue
Normal file
64
frontend/src/components/UI/CategorySidebar.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<v-navigation-drawer width="175px" clipped app permanent expand-on-hover>
|
||||
<v-list nav dense>
|
||||
<v-list-item v-for="nav in links" :key="nav.title" link :to="nav.to">
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ nav.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ nav.title | titleCase }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
links: [],
|
||||
baseLinks: [
|
||||
{
|
||||
icon: "mdi-home",
|
||||
to: "/",
|
||||
title: "Home",
|
||||
},
|
||||
{
|
||||
icon: "mdi-view-module",
|
||||
to: "/recipes/all",
|
||||
title: "All Recipes",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allCategories() {
|
||||
return this.$store.getters.getCategories;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
allCategories() {
|
||||
this.buildSidebar();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.buildSidebar();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async buildSidebar() {
|
||||
this.links = [];
|
||||
this.links.push(...this.baseLinks);
|
||||
this.allCategories.forEach(async (element) => {
|
||||
this.links.push({
|
||||
title: element.name,
|
||||
to: `/recipes/${element.slug}`,
|
||||
icon: "mdi-tag",
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -21,8 +21,8 @@
|
|||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="cancel"> Cancel </v-btn>
|
||||
<v-btn :color="color" text @click="confirm"> Confirm </v-btn>
|
||||
<v-btn color="grey" text @click="cancel"> {{ $t("general.cancel") }} </v-btn>
|
||||
<v-btn :color="color" text @click="confirm"> {{ $t("general.confirm") }} </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
</template>
|
||||
|
||||
<v-list>
|
||||
<v-list-item v-for="(item, i) in items" :key="i" link>
|
||||
<v-list-item-icon @click="navRouter(item.nav)">
|
||||
<v-list-item v-for="(item, i) in items" :key="i" link :to="item.nav">
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content @click="navRouter(item.nav)">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ item.title }}
|
||||
</v-list-item-title>
|
||||
|
@ -32,7 +32,7 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<template>
|
||||
<v-row>
|
||||
<v-col
|
||||
:sm="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
v-for="recipe in recipes"
|
||||
:key="recipe.name"
|
||||
>
|
||||
<RecipeCard
|
||||
:name="recipe.name"
|
||||
:description="recipe.description"
|
||||
:slug="recipe.slug"
|
||||
:rating="recipe.rating"
|
||||
:image="recipe.image"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RecipeCard from "./RecipeCard";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RecipeCard,
|
||||
},
|
||||
data: () => ({}),
|
||||
mounted() {},
|
||||
computed: {
|
||||
recipes() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -3,7 +3,8 @@
|
|||
<v-card
|
||||
:class="{ 'on-hover': hover }"
|
||||
:elevation="hover ? 12 : 2"
|
||||
@click="moreInfo(slug)"
|
||||
:to="route ? `/recipe/${slug}` : ''"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<v-img height="200" :src="getImage(image)"></v-img>
|
||||
<v-card-title class="my-n3 mb-n6">{{ name | truncate(30) }}</v-card-title>
|
||||
|
@ -25,9 +26,9 @@
|
|||
<v-col align="end">
|
||||
<v-tooltip top color="secondary" max-width="400" open-delay="50">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn color="secondary" v-on="on" v-bind="attrs" text
|
||||
>{{$t('recipe.description')}}</v-btn
|
||||
>
|
||||
<v-btn color="secondary" v-on="on" v-bind="attrs" text>{{
|
||||
$t("recipe.description")
|
||||
}}</v-btn>
|
||||
</template>
|
||||
<span>{{ description }}</span>
|
||||
</v-tooltip>
|
||||
|
@ -47,11 +48,11 @@ export default {
|
|||
description: String,
|
||||
rating: Number,
|
||||
image: String,
|
||||
route: {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
moreInfo(recipeSlug) {
|
||||
this.$router.push(`/recipe/${recipeSlug}`);
|
||||
},
|
||||
getImage(image) {
|
||||
return utils.getImageURL(image);
|
||||
},
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
<template>
|
||||
<v-autocomplete
|
||||
:items="items"
|
||||
:loading="isLoading"
|
||||
v-model="selected"
|
||||
clearable
|
||||
return
|
||||
dense
|
||||
hide-details
|
||||
hide-selected
|
||||
item-text="slug"
|
||||
:label="$t('search.search-for-a-recipe')"
|
||||
single-line
|
||||
@keyup.enter.native="moreInfo(selected)"
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-slot:item="{ item }">
|
||||
<v-list-item-avatar
|
||||
color="primary"
|
||||
class="headline font-weight-light white--text"
|
||||
>
|
||||
<v-img :src="getImage(item.image)"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content @click="moreInfo(item.slug)">
|
||||
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from "../../utils";
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
selected: null,
|
||||
isLoading: false,
|
||||
}),
|
||||
|
||||
computed: {
|
||||
items() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
moreInfo(recipeSlug) {
|
||||
this.$router.push(`/recipe/${recipeSlug}`);
|
||||
},
|
||||
getImage(image) {
|
||||
return utils.getImageURL(image);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -2,11 +2,12 @@
|
|||
<div>
|
||||
<v-autocomplete
|
||||
:items="autoResults"
|
||||
v-model="searchSlug"
|
||||
item-value="item.slug"
|
||||
item-text="item.name"
|
||||
dense
|
||||
light
|
||||
label="Search Mealie"
|
||||
:label="$t('search.search-mealie')"
|
||||
:search-input.sync="search"
|
||||
hide-no-data
|
||||
cache-items
|
||||
|
@ -52,7 +53,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
search: "",
|
||||
searchSlug: "",
|
||||
search: " ",
|
||||
result: [],
|
||||
autoResults: [],
|
||||
isDark: false,
|
||||
|
@ -82,13 +84,15 @@ export default {
|
|||
search() {
|
||||
if (this.search.trim() === "") this.result = this.list;
|
||||
else this.result = this.fuse.search(this.search.trim());
|
||||
console.log("test");
|
||||
|
||||
this.$emit("results", this.result);
|
||||
if (this.showResults === true) {
|
||||
this.autoResults = this.result;
|
||||
}
|
||||
},
|
||||
searchSlug() {
|
||||
this.selected(this.searchSlug);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getImage(image) {
|
||||
|
|
75
frontend/src/components/UI/SearchDialog.vue
Normal file
75
frontend/src/components/UI/SearchDialog.vue
Normal file
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<v-dialog v-model="dialog" min-height="700" max-width="1000">
|
||||
<v-card min-height="725" height="100%">
|
||||
<v-card-text>
|
||||
<v-card-title></v-card-title>
|
||||
<v-row justify="center">
|
||||
<v-col cols="1"> </v-col>
|
||||
<v-col>
|
||||
<SearchBar @results="updateResults" :show-results="false" />
|
||||
</v-col>
|
||||
<v-col cols="2">
|
||||
<v-btn icon>
|
||||
<v-icon large> mdi-filter </v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-if="searchResults">
|
||||
<v-col
|
||||
:sm="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
v-for="item in searchResults.slice(0, 10)"
|
||||
:key="item.item.name"
|
||||
>
|
||||
<RecipeCard
|
||||
:route="false"
|
||||
:name="item.item.name"
|
||||
:description="item.item.description"
|
||||
:slug="item.item.slug"
|
||||
:rating="item.item.rating"
|
||||
:image="item.item.image"
|
||||
@click="emitSelect(item.item.name, item.item.slug)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SearchBar from "../UI/SearchBar";
|
||||
import RecipeCard from "../UI/RecipeCard";
|
||||
export default {
|
||||
components: {
|
||||
SearchBar,
|
||||
RecipeCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchResults: null,
|
||||
dialog: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateResults(results) {
|
||||
this.searchResults = results;
|
||||
},
|
||||
emitSelect(name, slug) {
|
||||
this.$emit("select", name, slug);
|
||||
this.dialog = false;
|
||||
},
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,32 +1,50 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-alert v-if="success[0]" outlined dense type="success">
|
||||
<h4>{{ successHeader }}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="success in this.success" :key="success">
|
||||
{{ success }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
<v-alert v-if="failed[0]" outlined dense type="error">
|
||||
<h4>{{ failedHeader }}</h4>
|
||||
<v-list dense>
|
||||
<v-list-item v-for="fail in this.failed" :key="fail">
|
||||
{{ fail }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-alert>
|
||||
</div>
|
||||
<v-dialog v-model="dialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title> {{ title }} </v-card-title>
|
||||
<v-card-text class="mt-3">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-alert outlined dense type="success">
|
||||
<h4>{{ successHeader }}</h4>
|
||||
<p v-for="success in this.success" :key="success" class="my-1">
|
||||
- {{ success }}
|
||||
</p>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-alert v-if="failed[0]" outlined dense type="error">
|
||||
<h4>{{ failedHeader }}</h4>
|
||||
<p v-for="fail in this.failed" :key="fail" class="my-1">
|
||||
- {{ fail }}
|
||||
</p>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
successHeader: String,
|
||||
success: Array,
|
||||
failedHeader: String,
|
||||
failed: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.dialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
56
frontend/src/components/UI/UploadBtn.vue
Normal file
56
frontend/src/components/UI/UploadBtn.vue
Normal file
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<v-form ref="file">
|
||||
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" text>
|
||||
<v-icon left> mdi-cloud-upload </v-icon>
|
||||
{{ $t("general.upload") }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../../api";
|
||||
export default {
|
||||
props: {
|
||||
url: String,
|
||||
},
|
||||
data: () => ({
|
||||
file: null,
|
||||
isSelecting: false,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async upload() {
|
||||
if (this.file != null) {
|
||||
this.isSelecting = true;
|
||||
let formData = new FormData();
|
||||
formData.append("archive", this.file);
|
||||
|
||||
await api.utils.uploadFile(this.url, formData);
|
||||
|
||||
this.isSelecting = false;
|
||||
this.$emit("uploaded");
|
||||
}
|
||||
},
|
||||
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>
|
Loading…
Add table
Add a link
Reference in a new issue