Merge branch 'develop' into feature/fulltext-search

# Conflicts:
#	cookbook/static/vue/js/chunk-vendors.js
#	cookbook/static/vue/js/import_response_view.js
#	cookbook/static/vue/js/offline_view.js
#	cookbook/static/vue/js/recipe_search_view.js
#	cookbook/static/vue/js/recipe_view.js
#	cookbook/static/vue/js/supermarket_view.js
#	cookbook/static/vue/js/user_file_view.js
#	requirements.txt
#	vue/src/apps/RecipeSearchView/RecipeSearchView.vue
#	vue/src/components/GenericMultiselect.vue
#	vue/src/locales/en.json
This commit is contained in:
vabene1111
2021-08-22 13:28:57 +02:00
86 changed files with 7258 additions and 3337 deletions

View File

@@ -174,7 +174,7 @@
:initial_selection="settings.search_foods"
search_function="listFoods" label="name"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
v-bind:placeholder="$t('Ingredients')"></generic-multiselect>
v-bind:placeholder="$t('Ingredients')" :limit="20"></generic-multiselect>
<b-input-group-append>
<b-input-group-text>
<b-form-checkbox v-model="settings.search_foods_or" name="check-button"
@@ -196,7 +196,7 @@
:initial_selection="settings.search_books"
search_function="listRecipeBooks" label="name"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
v-bind:placeholder="$t('Books')"></generic-multiselect>
v-bind:placeholder="$t('Books')" :limit="50"></generic-multiselect>
<b-input-group-append>
<b-input-group-text>
<b-form-checkbox v-model="settings.search_books_or" name="check-button"

View File

@@ -1,21 +1,32 @@
<template>
<div>
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
:cancel-title="$t('Close')" @ok="addToBook()">
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Manage_Books')" :ok-title="$t('Add')"
:cancel-title="$t('Close')" @ok="addToBook()" @shown="loadBookEntries">
<table>
<tr v-for="be in this.recipe_book_list" v-bind:key="be.id">
<td>
<button class="btn btn-sm btn-danger" @click="removeFromBook(be)"><i class="fa fa-trash-alt"></i></button>
</td>
<td> {{ be.book_content.name }}</td>
</tr>
</table>
<multiselect
style="margin-top: 1vh"
v-model="selected_book"
:options="books"
:preserve-search="true"
:options="books_filtered"
:taggable="true"
@tag="createBook"
v-bind:tag-placeholder="$t('Create')"
:placeholder="$t('Select_Book')"
label="name"
track-by="id"
id="id_books"
:multiple="false"
@search-change="loadBook">
:loading="books_loading"
@search-change="loadBooks">
</multiselect>
</b-modal>
</div>
@@ -31,7 +42,8 @@ Vue.prototype.moment = moment
import Vue from "vue";
import {BootstrapVue} from "bootstrap-vue";
import {apiAddRecipeBookEntry, apiLoadCookBooks, apiLogCooking} from "@/utils/api";
import {ApiApiFactory} from "@/utils/openapi/api";
import {makeStandardToast, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue)
@@ -47,21 +59,66 @@ export default {
data() {
return {
books: [],
books_loading: false,
recipe_book_list: [],
selected_book: null,
}
},
computed: {
books_filtered: function () {
let books_filtered = []
this.books.forEach(b => {
if (this.recipe_book_list.filter(e => e.book === b.id).length === 0) {
books_filtered.push(b)
}
})
return books_filtered
}
},
mounted() {
this.loadBook('')
},
methods: {
loadBook: function (query) {
apiLoadCookBooks(query).then(results => {
this.books = results
loadBooks: function (query) {
this.books_loading = true
let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBooks({query: {query: query}}).then(results => {
this.books = results.data.filter(e => this.recipe_book_list.indexOf(e) === -1)
this.books_loading = false
})
},
addToBook() {
apiAddRecipeBookEntry({'recipe': this.recipe.id, 'book': this.selected_book.id})
createBook: function (name) {
let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBook({name: name}).then(r => {
this.books.push(r.data)
this.selected_book = r.data
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
},
addToBook: function () {
let apiFactory = new ApiApiFactory()
apiFactory.createRecipeBookEntry({book: this.selected_book.id, recipe: this.recipe.id}).then(r => {
this.recipe_book_list.push(r.data)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
})
},
removeFromBook: function (book_entry) {
let apiFactory = new ApiApiFactory()
apiFactory.destroyRecipeBookEntry(book_entry.id).then(r => {
this.recipe_book_list = this.recipe_book_list.filter(e => e.id !== book_entry.id)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
})
},
loadBookEntries: function () {
let apiFactory = new ApiApiFactory()
apiFactory.listRecipeBookEntrys({query: {recipe: this.recipe.id}}).then(r => {
this.recipe_book_list = r.data
this.loadBooks('')
})
}
}
}
</script>

View File

@@ -36,6 +36,10 @@ export default {
search_function: String,
label: String,
parent_variable: {type: String, default: undefined},
limit: {
type: Number,
default: 10,
}
sticky_options: {type:Array, default(){return []}},
initial_selection: {type:Array, default(){return []}},
multiple: {type: Boolean, default: true},
@@ -79,4 +83,4 @@ export default {
<style scoped>
</style>
</style>

View File

@@ -17,7 +17,7 @@
<a href="#">
<button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)">
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }}
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Manage_Books') }}
</button>
</a>

View File

@@ -16,13 +16,13 @@
"Log_Recipe_Cooking": "Log Recipe Cooking",
"External_Recipe_Image": "External Recipe Image",
"Add_to_Book": "Add to Book",
"Add_to_Shopping": "Add to Shopping",
"Add_to_Plan": "Add to Plan",
"Step_start_time": "Step start time",
"Sort_by_new": "Sort by new",
"Recipes_per_page": "Recipes per Page",
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan",
"Select_Book": "Select Book",
"Recipe_Image": "Recipe Image",
@@ -60,6 +60,7 @@
"Add": "Add",
"New": "New",
"Success": "Success",
"Failure": "Failure",
"Ingredients": "Ingredients",
"Supermarket": "Supermarket",
"Categories": "Categories",
@@ -86,6 +87,8 @@
"or": "or",
"and": "and",
"Information": "Information",
"Download": "Download",
"Create": "Create",
"Advanced Search Settings": "Advanced Search Settings",
"View": "View",
"Recipes": "Recipes",

80
vue/src/locales/fr.json Normal file
View File

@@ -0,0 +1,80 @@
{
"err_fetching_resource": "Il y a eu une erreur pour récupérer une ressource !",
"err_creating_resource": "Il y a eu une erreur pour créer une ressource !",
"err_updating_resource": "Il y a eu une erreur pour mettre à jour une ressource !",
"err_deleting_resource": "Il y a eu une erreur pour supprimer une ressource !",
"success_fetching_resource": "Ressource correctement récupérée !",
"success_creating_resource": "Ressource correctement créée !",
"success_updating_resource": "Ressource correctement mise à jour !",
"success_deleting_resource": "Ressource correctement supprimée !",
"import_running": "Importation en cours, veuillez patienter !",
"all_fields_optional": "Tous les champs sont optionnels et peuvent être laissés vides.",
"convert_internal": "Convertir en recette interne",
"show_only_internal": "Montrer uniquement les recettes internes",
"Log_Recipe_Cooking": "Marquer la recette comme cuisinée",
"External_Recipe_Image": "Image externe de recette",
"Add_to_Shopping": "Ajouter à la liste de courses",
"Add_to_Plan": "Ajouter au menu",
"Step_start_time": "Heure de départ de l'étape",
"Sort_by_new": "Trier par nouveautés",
"Recipes_per_page": "Nombre de recettes par page",
"Manage_Books": "Gérer les favoris",
"Meal_Plan": "Menu de la semaine",
"Select_Book": "Sélectionnez livre",
"Recipe_Image": "Image de la recette",
"Import_finished": "Importation finie",
"View_Recipes": "Voir les recettes",
"Log_Cooking": "Marquer comme cuisiné",
"New_Recipe": "Nouvelle recette",
"Url_Import": "Importation de l'url",
"Reset_Search": "Réinitialiser la recherche",
"Recently_Viewed": "Vu récemment",
"Load_More": "Charger plus",
"Keywords": "Mots-clés",
"Books": "Livres",
"Proteins": "Protéines",
"Fats": "Matières grasses",
"Carbohydrates": "Glucides",
"Calories": "Calories",
"Nutrition": "Informations nutritionnelles",
"Date": "Date",
"Share": "Partager",
"Export": "Exporter",
"Copy": "Copier",
"Rating": "Note",
"Close": "Fermer",
"Link": "Lien",
"Add": "Ajouter",
"New": "Nouveau",
"Success": "Réussite",
"Failure": "Échec",
"Ingredients": "Ingrédients",
"Supermarket": "Supermarché",
"Categories": "Catégories",
"Category": "Catégorie",
"Selected": "Sélectionné",
"min": "min",
"Servings": "Portions",
"Waiting": "Attente",
"Preparation": "Préparation",
"External": "Externe",
"Size": "Taille",
"Files": "Fichiers",
"File": "Fichier",
"Edit": "Modifier",
"Cancel": "Annuler",
"Delete": "Supprimer",
"Open": "Ouvrir",
"Ok": "Ouvrir",
"Save": "Sauvegarder",
"Step": "Étape",
"Search": "Rechercher",
"Import": "Importer",
"Print": "Imprimer",
"Settings": "Paramètres",
"or": "ou",
"and": "et",
"Information": "Information",
"Download": "Télécharger",
"Create": "Créer"
}

View File

@@ -0,0 +1,80 @@
{
"err_fetching_resource": "",
"err_creating_resource": "",
"err_updating_resource": "",
"err_deleting_resource": "",
"success_fetching_resource": "",
"success_creating_resource": "",
"success_updating_resource": "",
"success_deleting_resource": "",
"import_running": "",
"all_fields_optional": "",
"convert_internal": "",
"show_only_internal": "",
"Log_Recipe_Cooking": "",
"External_Recipe_Image": "外部菜谱图像",
"Add_to_Shopping": "添加到购物",
"Add_to_Plan": "添加到计划",
"Step_start_time": "",
"Sort_by_new": "",
"Recipes_per_page": "",
"Manage_Books": "管理书籍",
"Meal_Plan": "",
"Select_Book": "",
"Recipe_Image": "菜谱图像",
"Import_finished": "导入完成",
"View_Recipes": "",
"Log_Cooking": "",
"New_Recipe": "新菜谱",
"Url_Import": "导入网址",
"Reset_Search": "重置搜索",
"Recently_Viewed": "最近浏览",
"Load_More": "加载更多",
"Keywords": "关键字",
"Books": "书籍",
"Proteins": "蛋白质",
"Fats": "脂肪",
"Carbohydrates": "碳水化合物",
"Calories": "卡路里",
"Nutrition": "营养",
"Date": "日期",
"Share": "分享",
"Export": "导出",
"Copy": "拷贝",
"Rating": "评分",
"Close": "关闭",
"Link": "链接",
"Add": "添加",
"New": "新",
"Success": "成功",
"Failure": "失败",
"Ingredients": "材料",
"Supermarket": "超级市场",
"Categories": "分类",
"Category": "分类",
"Selected": "选定",
"min": "",
"Servings": "份量",
"Waiting": "等待",
"Preparation": "准备",
"External": "外部",
"Size": "大小",
"Files": "文件",
"File": "文件",
"Edit": "编辑",
"Cancel": "取消",
"Delete": "删除",
"Open": "打开",
"Ok": "打开",
"Save": "储存",
"Step": "步骤",
"Search": "搜索",
"Import": "导入",
"Print": "打印",
"Settings": "设置",
"or": "或",
"and": "与",
"Information": "更多资讯",
"Download": "下载",
"Create": "创立"
}

View File

@@ -0,0 +1,80 @@
{
"err_fetching_resource": "",
"err_creating_resource": "",
"err_updating_resource": "",
"err_deleting_resource": "",
"success_fetching_resource": "",
"success_creating_resource": "",
"success_updating_resource": "",
"success_deleting_resource": "",
"import_running": "",
"all_fields_optional": "",
"convert_internal": "",
"show_only_internal": "",
"Log_Recipe_Cooking": "",
"External_Recipe_Image": "",
"Add_to_Shopping": "",
"Add_to_Plan": "",
"Step_start_time": "",
"Sort_by_new": "",
"Recipes_per_page": "",
"Manage_Books": "",
"Meal_Plan": "",
"Select_Book": "",
"Recipe_Image": "",
"Import_finished": "",
"View_Recipes": "",
"Log_Cooking": "",
"New_Recipe": "",
"Url_Import": "",
"Reset_Search": "",
"Recently_Viewed": "",
"Load_More": "",
"Keywords": "",
"Books": "",
"Proteins": "",
"Fats": "",
"Carbohydrates": "",
"Calories": "",
"Nutrition": "",
"Date": "",
"Share": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Link": "",
"Add": "",
"New": "",
"Success": "",
"Failure": "",
"Ingredients": "",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Cancel": "",
"Delete": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": ""
}

View File

@@ -37,23 +37,6 @@ export function apiLogCooking(cook_log) {
})
}
export function apiLoadCookBooks(query) {
return axios.get(resolveDjangoUrl('api:recipebook-list') + '?query=' + query).then((response) => {
return response.data
}).catch((err) => {
//handleError(err, 'There was an error loading a resource!', 'danger')
})
}
export function apiAddRecipeBookEntry(entry) {
return axios.post(resolveDjangoUrl('api:recipebookentry-list',), entry).then((response) => {
makeToast('Saved', 'Recipe Book entry saved!', 'success')
}).catch((err) => {
handleError(err, 'There was an error creating a resource!', 'danger')
})
}
function handleError(error, message) {
if ('response' in error) {
console.log(error.response)

View File

@@ -1,6 +1,6 @@
/* frac.js (C) 2012-present SheetJS -- http://sheetjs.com */
/*https://developer.aliyun.com/mirror/npm/package/frac/v/0.3.0 Apache license*/
/* https://www.npmjs.com/package/frac Apache license*/
export function frac(x, D, mixed) {
var n1 = Math.floor(x), d1 = 1;
var n2 = n1 + 1, d2 = 1;

View File

@@ -2,6 +2,7 @@
* Utility functions to call bootstrap toasts
* */
import {BToast} from 'bootstrap-vue'
import i18n from "@/i18n";
export const ToastMixin = {
methods: {
@@ -21,6 +22,49 @@ export function makeToast(title, message, variant = null) {
})
}
export class StandardToasts {
static SUCCESS_CREATE = 'SUCCESS_CREATE'
static SUCCESS_FETCH = 'SUCCESS_FETCH'
static SUCCESS_UPDATE = 'SUCCESS_UPDATE'
static SUCCESS_DELETE = 'SUCCESS_DELETE'
static FAIL_CREATE = 'FAIL_CREATE'
static FAIL_FETCH = 'FAIL_FETCH'
static FAIL_UPDATE = 'FAIL_UPDATE'
static FAIL_DELETE = 'FAIL_DELETE'
static makeStandardToast(toast) {
switch (toast) {
case StandardToasts.SUCCESS_CREATE:
makeToast(i18n.tc('Success'), i18n.tc('success_creating_resource'), 'success')
break;
case StandardToasts.SUCCESS_FETCH:
makeToast(i18n.tc('Success'), i18n.tc('success_fetching_resource'), 'success')
break;
case StandardToasts.SUCCESS_UPDATE:
makeToast(i18n.tc('Success'), i18n.tc('success_updating_resource'), 'success')
break;
case StandardToasts.SUCCESS_DELETE:
makeToast(i18n.tc('Success'), i18n.tc('success_deleting_resource'), 'success')
break;
case StandardToasts.FAIL_CREATE:
makeToast(i18n.tc('Failure'), i18n.tc('success_creating_resource'), 'danger')
break;
case StandardToasts.FAIL_FETCH:
makeToast(i18n.tc('Failure'), i18n.tc('err_fetching_resource'), 'danger')
break;
case StandardToasts.FAIL_UPDATE:
makeToast(i18n.tc('Failure'), i18n.tc('err_updating_resource'), 'danger')
break;
case StandardToasts.FAIL_DELETE:
makeToast(i18n.tc('Failure'), i18n.tc('err_deleting_resource'), 'danger')
break;
}
}
}
/*
* Utility functions to use djangos gettext
* */
@@ -88,7 +132,7 @@ import {frac} from "@/utils/fractions";
export function calculateAmount(amount, factor) {
if (getUserPreference('use_fractions')) {
let return_string = ''
let fraction = frac((amount * factor), 9, true)
let fraction = frac((amount * factor), 10, true)
if (fraction[0] > 0) {
return_string += fraction[0]

View File

@@ -71,17 +71,17 @@ module.exports = {
chainWebpack: config => {
config.optimization.splitChunks({
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-vendors",
chunks: "all",
priority: 1
},
},
},
},
// TODO make this conditional on .env DEBUG = TRUE
// config.optimization.minimize(false)
// TODO make this conditional on .env DEBUG = TRUE
config.optimization.minimize(true)
);
//TODO somehow remov them as they are also added to the manifest config of the service worker

View File

@@ -9296,9 +9296,9 @@ url-loader@^2.2.0:
schema-utils "^2.5.0"
url-parse@^1.4.3, url-parse@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b"
integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==
version "1.5.3"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"