1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-03 20:45:23 +02:00
* 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&amp;auml;tt ugnen p&amp;aring; 200&amp;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:
Hayden 2021-02-08 09:47:40 -09:00 committed by GitHub
parent 3ec0f2ec21
commit b3573dc078
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
233 changed files with 11756 additions and 2491 deletions

View file

@ -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();
},
},
};

View file

@ -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>

View file

@ -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) {

View file

@ -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>

View file

@ -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"),
},
};
},

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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();
},
},
};

View 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>

View 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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);
},

View file

@ -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"),
},
};
},

View file

@ -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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View file

@ -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>

View file

@ -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: [
{

View file

@ -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>

View file

@ -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);
},

View file

@ -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>

View file

@ -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) {

View 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>

View file

@ -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>

View 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>