WIP
This commit is contained in:
parent
0559143f0e
commit
b56c2429c7
@ -47,7 +47,7 @@ class SpaceFilterSerializer(serializers.ListSerializer):
|
|||||||
|
|
||||||
def to_representation(self, data):
|
def to_representation(self, data):
|
||||||
if (type(data) == QuerySet and data.query.is_sliced):
|
if (type(data) == QuerySet and data.query.is_sliced):
|
||||||
# if query is sliced or if is a MP_NodeQuerySet it came from api request not nested serializer
|
# if query is sliced it came from api request not nested serializer
|
||||||
return super().to_representation(data)
|
return super().to_representation(data)
|
||||||
if self.child.Meta.model == User:
|
if self.child.Meta.model == User:
|
||||||
data = data.filter(userpreference__space=self.context['request'].space)
|
data = data.filter(userpreference__space=self.context['request'].space)
|
||||||
@ -212,8 +212,8 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
|||||||
|
|
||||||
def get_image(self, obj):
|
def get_image(self, obj):
|
||||||
recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
||||||
if len(recipes) == 0:
|
if len(recipes) == 0 and obj.has_children():
|
||||||
recipes = Recipe.objects.filter(keywords__in=obj.get_tree(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
|
recipes = Recipe.objects.filter(keywords__in=obj.get_descendants(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
|
||||||
if len(recipes) != 0:
|
if len(recipes) != 0:
|
||||||
return random.choice(recipes).image.url
|
return random.choice(recipes).image.url
|
||||||
else:
|
else:
|
||||||
@ -297,10 +297,9 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
|||||||
return obj.recipe.image.url
|
return obj.recipe.image.url
|
||||||
# if food is not also a recipe, look for recipe images that use the food
|
# if food is not also a recipe, look for recipe images that use the food
|
||||||
recipes = Recipe.objects.filter(steps__ingredients__food=obj, space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
recipes = Recipe.objects.filter(steps__ingredients__food=obj, space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
||||||
|
|
||||||
# if no recipes found - check whole tree
|
# if no recipes found - check whole tree
|
||||||
if len(recipes) == 0:
|
if len(recipes) == 0 and obj.has_children():
|
||||||
recipes = Recipe.objects.filter(steps__ingredients__food__in=obj.get_tree(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
recipes = Recipe.objects.filter(steps__ingredients__food__in=obj.get_descendants(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
||||||
|
|
||||||
if len(recipes) != 0:
|
if len(recipes) != 0:
|
||||||
return random.choice(recipes).image.url
|
return random.choice(recipes).image.url
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -135,10 +135,6 @@ urlpatterns = [
|
|||||||
name='web_manifest'),
|
name='web_manifest'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# generic_models = (
|
|
||||||
# Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
|
||||||
# Comment, RecipeBookEntry, Keyword, Food, ShoppingList, InviteLink
|
|
||||||
# )
|
|
||||||
generic_models = (
|
generic_models = (
|
||||||
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
||||||
Comment, RecipeBookEntry, ShoppingList, InviteLink
|
Comment, RecipeBookEntry, ShoppingList, InviteLink
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
spinner="waveDots">
|
spinner="waveDots">
|
||||||
</infinite-loading>
|
</infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
<!-- right side keyword cards -->
|
<!-- right side food cards -->
|
||||||
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
|
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
|
||||||
<food-card
|
<food-card
|
||||||
v-for="f in foods2"
|
v-for="f in foods2"
|
||||||
@ -108,51 +108,56 @@
|
|||||||
<!-- TODO Modals can probably be made generic and moved to component -->
|
<!-- TODO Modals can probably be made generic and moved to component -->
|
||||||
<!-- edit modal -->
|
<!-- edit modal -->
|
||||||
<b-modal class="modal"
|
<b-modal class="modal"
|
||||||
:id="'id_modal_keyword_edit'"
|
:id="'id_modal_food_edit'"
|
||||||
@shown="prepareEmoji"
|
:title="this.$t('Edit_Food')"
|
||||||
:title="this.$t('Edit_Keyword')"
|
|
||||||
:ok-title="this.$t('Save')"
|
:ok-title="this.$t('Save')"
|
||||||
:cancel-title="this.$t('Cancel')"
|
:cancel-title="this.$t('Cancel')"
|
||||||
@ok="saveKeyword">
|
@ok="saveFood">
|
||||||
<form>
|
<form>
|
||||||
<label for="id_keyword_name_edit">{{ this.$t('Name') }}</label>
|
<label for="id_food_name_edit">{{ this.$t('Name') }}</label>
|
||||||
<input class="form-control" type="text" id="id_keyword_name_edit" v-model="this_item.name">
|
<input class="form-control" type="text" id="id_food_name_edit" v-model="this_item.name">
|
||||||
<label for="id_keyword_description_edit">{{ this.$t('Description') }}</label>
|
<label for="id_food_description_edit">{{ this.$t('Description') }}</label>
|
||||||
<input class="form-control" type="text" id="id_keyword_description_edit" v-model="this_item.description">
|
<input class="form-control" type="text" id="id_food_description_edit" v-model="this_item.description">
|
||||||
<label for="id_keyword_icon_edit">{{ this.$t('Icon') }}</label>
|
<label for="id_food_recipe_edit">{{ this.$t('Recipe') }}</label>
|
||||||
<twemoji-textarea
|
<generic-multiselect
|
||||||
id="id_keyword_icon_edit"
|
@change="this_item.recipe=$event.val"
|
||||||
ref="_edit"
|
label="name"
|
||||||
:emojiData="emojiDataAll"
|
:initial_selection="this_item.recipe"
|
||||||
:emojiGroups="emojiGroups"
|
search_function="listRecipes"
|
||||||
triggerType="hover"
|
:multiple="false"
|
||||||
recentEmojisFeat="true"
|
:sticky_options="[{'id': null,'name': $t('None')}]"
|
||||||
recentEmojisStorage="local"
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
@contentChanged="setIcon"
|
:placeholder="this.$t('Search')">
|
||||||
/>
|
</generic-multiselect>
|
||||||
|
<div class="form-group form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="id_food_ignore_edit" v-model="this_item.ignore_shopping">
|
||||||
|
<label class="form-check-label" for="id_food_ignore_edit">{{ this.$t('Ignore_Shopping') }}</label>
|
||||||
|
</div>
|
||||||
|
<label for="id_food_category_edit">{{ this.$t('Shopping_Category') }}</label>
|
||||||
|
<input class="form-control" type="text" id="id_food_category_edit" >
|
||||||
</form>
|
</form>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<!-- delete modal -->
|
<!-- delete modal -->
|
||||||
<b-modal class="modal"
|
<b-modal class="modal"
|
||||||
:id="'id_modal_keyword_delete'"
|
:id="'id_modal_food_delete'"
|
||||||
:title="this.$t('Delete_Keyword')"
|
:title="this.$t('Delete_Food')"
|
||||||
:ok-title="this.$t('Delete')"
|
:ok-title="this.$t('Delete')"
|
||||||
:cancel-title="this.$t('Cancel')"
|
:cancel-title="this.$t('Cancel')"
|
||||||
@ok="delKeyword(this_item.id)">
|
@ok="delFood(this_item.id)">
|
||||||
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
|
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
|
||||||
</b-modal>
|
</b-modal>
|
||||||
<!-- move modal -->
|
<!-- move modal -->
|
||||||
<b-modal class="modal"
|
<b-modal class="modal"
|
||||||
:id="'id_modal_keyword_move'"
|
:id="'id_modal_food_move'"
|
||||||
:title="this.$t('Move_Keyword')"
|
:title="this.$t('Move_Food')"
|
||||||
:ok-title="this.$t('Move')"
|
:ok-title="this.$t('Move')"
|
||||||
:cancel-title="this.$t('Cancel')"
|
:cancel-title="this.$t('Cancel')"
|
||||||
@ok="moveKeyword(this_item.id, this_item.target.id)">
|
@ok="moveFood(this_item.id, this_item.target.id)">
|
||||||
{{ this.$t("move_selection", {'child': this_item.name}) }}
|
{{ this.$t("move_selection", {'child': this_item.name}) }}
|
||||||
<generic-multiselect
|
<generic-multiselect
|
||||||
@change="this_item.target=$event.val"
|
@change="this_item.target=$event.val"
|
||||||
label="name"
|
label="name"
|
||||||
search_function="listKeywords"
|
search_function="listFood"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:sticky_options="[{'id': 0,'name': $t('Root')}]"
|
:sticky_options="[{'id': 0,'name': $t('Root')}]"
|
||||||
:tree_api="true"
|
:tree_api="true"
|
||||||
@ -162,16 +167,16 @@
|
|||||||
</b-modal>
|
</b-modal>
|
||||||
<!-- merge modal -->
|
<!-- merge modal -->
|
||||||
<b-modal class="modal"
|
<b-modal class="modal"
|
||||||
:id="'id_modal_keyword_merge'"
|
:id="'id_modal_food_merge'"
|
||||||
:title="this.$t('Merge_Keyword')"
|
:title="this.$t('Merge_Food')"
|
||||||
:ok-title="this.$t('Merge')"
|
:ok-title="this.$t('Merge')"
|
||||||
:cancel-title="this.$t('Cancel')"
|
:cancel-title="this.$t('Cancel')"
|
||||||
@ok="mergeKeyword(this_item.id, this_item.target.id)">
|
@ok="mergeFood(this_item.id, this_item.target.id)">
|
||||||
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('keyword')}) }}
|
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('food')}) }}
|
||||||
<generic-multiselect
|
<generic-multiselect
|
||||||
@change="this_item.target=$event.val"
|
@change="this_item.target=$event.val"
|
||||||
label="name"
|
label="name"
|
||||||
search_function="listKeywords"
|
search_function="listFoods"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:tree_api="true"
|
:tree_api="true"
|
||||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
@ -193,36 +198,19 @@ import {BootstrapVue} from 'bootstrap-vue'
|
|||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
import _debounce from 'lodash/debounce'
|
import _debounce from 'lodash/debounce'
|
||||||
|
|
||||||
import {ResolveUrlMixin} from "@/utils/utils";
|
import {ToastMixin} from "@/utils/utils";
|
||||||
|
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
||||||
import FoodCard from "@/components/FoodCard";
|
import FoodCard from "@/components/FoodCard";
|
||||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||||
import InfiniteLoading from 'vue-infinite-loading';
|
import InfiniteLoading from 'vue-infinite-loading';
|
||||||
|
|
||||||
// would move with modals if made generic
|
|
||||||
import {TwemojiTextarea} from '@kevinfaguiar/vue-twemoji-picker';
|
|
||||||
// TODO add localization
|
|
||||||
import EmojiAllData from '@kevinfaguiar/vue-twemoji-picker/emoji-data/en/emoji-all-groups.json';
|
|
||||||
import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-groups.json';
|
|
||||||
// end move with generic modals
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FoodListView',
|
name: 'FoodListView',
|
||||||
mixins: [ResolveUrlMixin],
|
mixins: [ToastMixin],
|
||||||
components: {TwemojiTextarea, FoodCard, GenericMultiselect, InfiniteLoading},
|
components: {FoodCard, GenericMultiselect, InfiniteLoading},
|
||||||
computed: {
|
|
||||||
// move with generic modals
|
|
||||||
emojiDataAll() {
|
|
||||||
return EmojiAllData;
|
|
||||||
},
|
|
||||||
emojiGroups() {
|
|
||||||
return EmojiGroups;
|
|
||||||
}
|
|
||||||
// end move with generic modals
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
foods: [],
|
foods: [],
|
||||||
@ -241,7 +229,9 @@ export default {
|
|||||||
'id': -1,
|
'id': -1,
|
||||||
'name': '',
|
'name': '',
|
||||||
'description': '',
|
'description': '',
|
||||||
'icon': '',
|
'recipe': null,
|
||||||
|
'ignore_shopping': '',
|
||||||
|
'supermarket_category': null,
|
||||||
'target': {
|
'target': {
|
||||||
'id': -1,
|
'id': -1,
|
||||||
'name': ''
|
'name': ''
|
||||||
@ -262,15 +252,6 @@ export default {
|
|||||||
}, 700)
|
}, 700)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
makeToast: function (title, message, variant = null) {
|
|
||||||
//TODO remove duplicate function in favor of central one
|
|
||||||
this.$bvToast.toast(message, {
|
|
||||||
title: title,
|
|
||||||
variant: variant,
|
|
||||||
toaster: 'b-toaster-top-center',
|
|
||||||
solid: true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
resetSearch: function () {
|
resetSearch: function () {
|
||||||
if (this.search_input !== '') {
|
if (this.search_input !== '') {
|
||||||
this.search_input = ''
|
this.search_input = ''
|
||||||
@ -295,26 +276,26 @@ export default {
|
|||||||
|
|
||||||
if (e.action == 'delete') {
|
if (e.action == 'delete') {
|
||||||
this.this_item = source
|
this.this_item = source
|
||||||
this.$bvModal.show('id_modal_keyword_delete')
|
this.$bvModal.show('id_modal_food_delete')
|
||||||
} else if (e.action == 'new') {
|
} else if (e.action == 'new') {
|
||||||
this.this_item = {}
|
this.this_item = {}
|
||||||
this.$bvModal.show('id_modal_keyword_edit')
|
this.$bvModal.show('id_modal_food_edit')
|
||||||
} else if (e.action == 'edit') {
|
} else if (e.action == 'edit') {
|
||||||
this.this_item = source
|
this.this_item = source
|
||||||
this.$bvModal.show('id_modal_keyword_edit')
|
this.$bvModal.show('id_modal_food_edit')
|
||||||
} else if (e.action === 'move') {
|
} else if (e.action === 'move') {
|
||||||
this.this_item = source
|
this.this_item = source
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
this.$bvModal.show('id_modal_keyword_move')
|
this.$bvModal.show('id_modal_food_move')
|
||||||
} else {
|
} else {
|
||||||
this.moveKeyword(source.id, target.id)
|
this.moveFood(source.id, target.id)
|
||||||
}
|
}
|
||||||
} else if (e.action === 'merge') {
|
} else if (e.action === 'merge') {
|
||||||
this.this_item = source
|
this.this_item = source
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
this.$bvModal.show('id_modal_keyword_merge')
|
this.$bvModal.show('id_modal_food_merge')
|
||||||
} else {
|
} else {
|
||||||
this.mergeKeyword(e.source.id, e.target.id)
|
this.mergeFood(e.source.id, e.target.id)
|
||||||
}
|
}
|
||||||
} else if (e.action === 'get-children') {
|
} else if (e.action === 'get-children') {
|
||||||
if (source.expanded) {
|
if (source.expanded) {
|
||||||
@ -335,13 +316,14 @@ export default {
|
|||||||
},
|
},
|
||||||
saveFood: function () {
|
saveFood: function () {
|
||||||
let apiClient = new ApiApiFactory()
|
let apiClient = new ApiApiFactory()
|
||||||
let food = {
|
console.log(this.this_item, !this.this_item.id)
|
||||||
name: this.this_item.name,
|
// let food = {
|
||||||
description: this.this_item.description,
|
// name: this.this_item.name,
|
||||||
icon: this.this_item.icon,
|
// description: this.this_item.description,
|
||||||
}
|
// icon: this.this_item.icon,
|
||||||
|
// }
|
||||||
if (!this.this_item.id) { // if there is no item id assume its a new item
|
if (!this.this_item.id) { // if there is no item id assume its a new item
|
||||||
apiClient.createFood(food).then(result => {
|
apiClient.createFood(this.this_item).then(result => {
|
||||||
// place all new foods at the top of the list - could sort instead
|
// place all new foods at the top of the list - could sort instead
|
||||||
this.foods = [result.data].concat(this.foods)
|
this.foods = [result.data].concat(this.foods)
|
||||||
// this creates a deep copy to make sure that columns stay independent
|
// this creates a deep copy to make sure that columns stay independent
|
||||||
@ -356,7 +338,7 @@ export default {
|
|||||||
this.this_item = {}
|
this.this_item = {}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
apiClient.partialUpdateFood(this.this_item.id, food).then(result => {
|
apiClient.partialUpdateFood(this.this_item.id, this.this_item).then(result => {
|
||||||
this.refreshCard(this.this_item.id)
|
this.refreshCard(this.this_item.id)
|
||||||
this.this_item={}
|
this.this_item={}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -427,9 +409,9 @@ export default {
|
|||||||
|
|
||||||
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
|
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
|
||||||
if (col == 'left') {
|
if (col == 'left') {
|
||||||
parent = this.findFood(this.keywords, food.id)
|
parent = this.findFood(this.foods, food.id)
|
||||||
} else if (col == 'right'){
|
} else if (col == 'right'){
|
||||||
parent = this.findFood(this.keywords2, food.id)
|
parent = this.findFood(this.foods2, food.id)
|
||||||
}
|
}
|
||||||
if (parent) {
|
if (parent) {
|
||||||
Vue.set(parent, 'children', result.data.results)
|
Vue.set(parent, 'children', result.data.results)
|
||||||
|
@ -194,7 +194,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
|||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
import _debounce from 'lodash/debounce'
|
import _debounce from 'lodash/debounce'
|
||||||
|
|
||||||
import {ResolveUrlMixin} from "@/utils/utils";
|
import {ToastMixin} from "@/utils/utils";
|
||||||
|
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
||||||
import KeywordCard from "@/components/KeywordCard";
|
import KeywordCard from "@/components/KeywordCard";
|
||||||
@ -212,7 +212,7 @@ Vue.use(BootstrapVue)
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'KeywordListView',
|
name: 'KeywordListView',
|
||||||
mixins: [ResolveUrlMixin],
|
mixins: [ToastMixin],
|
||||||
components: {TwemojiTextarea, KeywordCard, GenericMultiselect, InfiniteLoading},
|
components: {TwemojiTextarea, KeywordCard, GenericMultiselect, InfiniteLoading},
|
||||||
computed: {
|
computed: {
|
||||||
// move with generic modals
|
// move with generic modals
|
||||||
@ -263,15 +263,6 @@ export default {
|
|||||||
}, 700)
|
}, 700)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
makeToast: function (title, message, variant = null) {
|
|
||||||
//TODO remove duplicate function in favor of central one
|
|
||||||
this.$bvToast.toast(message, {
|
|
||||||
title: title,
|
|
||||||
variant: variant,
|
|
||||||
toaster: 'b-toaster-top-center',
|
|
||||||
solid: true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
resetSearch: function () {
|
resetSearch: function () {
|
||||||
if (this.search_input !== '') {
|
if (this.search_input !== '') {
|
||||||
this.search_input = ''
|
this.search_input = ''
|
||||||
|
@ -65,6 +65,11 @@ export default {
|
|||||||
apiClient[this.search_function](query, root, tree, page, pageSize).then(result => {
|
apiClient[this.search_function](query, root, tree, page, pageSize).then(result => {
|
||||||
this.objects = this.sticky_options.concat(result.data.results)
|
this.objects = this.sticky_options.concat(result.data.results)
|
||||||
})
|
})
|
||||||
|
} else if (this.search_function === 'listRecipes') {
|
||||||
|
apiClient[this.search_function](query, undefined, undefined, undefined, undefined, undefined,
|
||||||
|
undefined, undefined, undefined, undefined, undefined, 25, undefined).then(result => {
|
||||||
|
this.objects = this.sticky_options.concat(result.data.results)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
apiClient[this.search_function]({query: {query: query, limit: this.limit}}).then(result => {
|
apiClient[this.search_function]({query: {query: query, limit: this.limit}}).then(result => {
|
||||||
this.objects = this.sticky_options.concat(result.data)
|
this.objects = this.sticky_options.concat(result.data)
|
||||||
|
@ -102,5 +102,8 @@
|
|||||||
"merge_selection": "Replace all occurences of {source} with the selected {type}.",
|
"merge_selection": "Replace all occurences of {source} with the selected {type}.",
|
||||||
"Advanced Search Settings": "Advanced Search Settings",
|
"Advanced Search Settings": "Advanced Search Settings",
|
||||||
"Download": "Download",
|
"Download": "Download",
|
||||||
"Root": "Root"
|
"Root": "Root",
|
||||||
|
"Ignore_Shopping": "Ignore Shopping",
|
||||||
|
"Shopping_Category": "Shopping Category",
|
||||||
|
"Edit_Food": "Edit Food"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user