moved food and keyword to generic cards

This commit is contained in:
smilerz 2021-08-19 12:36:39 -05:00
parent aba2e66163
commit d606ea8db3
19 changed files with 377 additions and 138793 deletions

View File

@ -1 +1 @@
.shake[data-v-153e0375]{-webkit-animation:shake-data-v-153e0375 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-153e0375 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-153e0375{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-153e0375{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}} .shake[data-v-997172e4]{-webkit-animation:shake-data-v-997172e4 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-997172e4 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-997172e4{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-997172e4{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}

View File

@ -1 +1 @@
.shake[data-v-bb84128a]{-webkit-animation:shake-data-v-bb84128a .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-bb84128a .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-bb84128a{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-bb84128a{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}} .shake[data-v-997172e4]{-webkit-animation:shake-data-v-997172e4 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-997172e4 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-997172e4{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-997172e4{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,7 @@ from django.contrib.auth.models import User
from django.contrib.postgres.search import TrigramSimilarity from django.contrib.postgres.search import TrigramSimilarity
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.core.files import File from django.core.files import File
from django.db.models import Q from django.db.models import Q, Value
from django.db.models.fields.related import ForeignObjectRel from django.db.models.fields.related import ForeignObjectRel
from django.http import FileResponse, HttpResponse, JsonResponse from django.http import FileResponse, HttpResponse, JsonResponse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
@ -114,8 +114,9 @@ class FuzzyFilterMixin(ViewSetMixin):
) )
else: else:
# TODO have this check unaccent search settings or other search preferences? # TODO have this check unaccent search settings or other search preferences?
# TODO for some querysets exact matches are sorted beyond pagesize, need to find better solution # for some querysets exact matches are sorted beyond pagesize, this ensures exact matches appear first
self.queryset = self.queryset.filter(name__istartswith=query) | self.queryset.filter(name__icontains=query) self.queryset = self.queryset.filter(name__istartswith=query).annotate(order=Value(0)) | self.queryset.filter(name__icontains=query).annotate(order=Value(1))
self.queryset = self.queryset.order_by('order')
updated_at = self.request.query_params.get('updated_at', None) updated_at = self.request.query_params.get('updated_at', None)
if updated_at is not None: if updated_at is not None:

View File

@ -67,32 +67,46 @@
<!-- only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? --> <!-- only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? -->
<div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh"> <div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}"> <div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<food-card <generic-horizontal-card v-for="f in foods" v-bind:key="f.id"
v-for="f in foods" :model=f
v-bind:key="f.id" model_name="Food"
:food="f"
:draggable="true" :draggable="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'left')" @item-action="startAction($event, 'left')"
></food-card> >
<template v-slot:upper-right>
<b-button v-if="f.recipe" v-b-tooltip.hover :title="f.recipe.name"
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="f.recipe.url"/>
</template>
</generic-horizontal-card>
<infinite-loading <infinite-loading
:identifier='left' :identifier='left'
@infinite="infiniteHandler($event, 'left')" @infinite="infiniteHandler($event, 'left')"
spinner="waveDots"> spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading> </infinite-loading>
</div> </div>
<!-- right side food 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 <generic-horizontal-card v-for="f in foods" v-bind:key="f.id"
v-for="f in foods2" :model=f
v-bind:key="f.id" model_name="Food"
:food="f" :draggable="true"
draggable="true" :merge="true"
@item-action="startAction($event, 'right')" :move="true"
></food-card> @item-action="startAction($event, 'left')"
>
<template v-slot:upper-right>
<b-button v-if="f.recipe" v-b-tooltip.hover :title="f.recipe.name"
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="f.recipe.url"/>
</template>
</generic-horizontal-card>
<infinite-loading <infinite-loading
:identifier='right' :identifier='right'
@infinite="infiniteHandler($event, 'right')" @infinite="infiniteHandler($event, 'right')"
spinner="waveDots"> spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading> </infinite-loading>
</div> </div>
</div> </div>
@ -211,7 +225,8 @@ import _debounce from 'lodash/debounce'
import {ToastMixin} 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 GenericHorizontalCard from "@/components/GenericHorizontalCard";
import GenericMultiselect from "@/components/GenericMultiselect"; import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading'; import InfiniteLoading from 'vue-infinite-loading';
@ -220,7 +235,7 @@ Vue.use(BootstrapVue)
export default { export default {
name: 'FoodListView', name: 'FoodListView',
mixins: [ToastMixin], mixins: [ToastMixin],
components: {FoodCard, GenericMultiselect, InfiniteLoading}, components: {GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
data() { data() {
return { return {
foods: [], foods: [],
@ -282,9 +297,8 @@ export default {
}, },
// TODO should model actions be included with the context menu? the card? a seperate mixin avaible to all? // TODO should model actions be included with the context menu? the card? a seperate mixin avaible to all?
startAction: function(e, col) { startAction: function(e, col) {
let target = e.target || null let target = e?.target
let source = e.source || null let source = e?.source
if (e.action == 'delete') { if (e.action == 'delete') {
this.this_item = source this.this_item = source
this.$bvModal.show('id_modal_food_delete') this.$bvModal.show('id_modal_food_delete')
@ -309,13 +323,14 @@ export default {
this.mergeFood(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.show_children) {
Vue.set(source, 'expanded', false) Vue.set(source, 'show_children', false)
} else { } else {
this.this_item = source this.this_item = source
this.getChildren(col, source) this.getChildren(col, source)
} }
} else if (e.action === 'get-recipes') { } else if (e.action === 'get-recipes') {
if (source.show_recipes) { if (source.show_recipes) {
Vue.set(source, 'show_recipes', false) Vue.set(source, 'show_recipes', false)
} else { } else {
@ -421,7 +436,7 @@ export default {
} }
if (parent) { if (parent) {
Vue.set(parent, 'children', result.data.results) Vue.set(parent, 'children', result.data.results)
Vue.set(parent, 'expanded', true) Vue.set(parent, 'show_children', true)
Vue.set(parent, 'show_recipes', false) Vue.set(parent, 'show_recipes', false)
} }
}).catch((err) => { }).catch((err) => {
@ -446,7 +461,7 @@ export default {
if (parent) { if (parent) {
Vue.set(parent, 'recipes', result.data.results) Vue.set(parent, 'recipes', result.data.results)
Vue.set(parent, 'show_recipes', true) Vue.set(parent, 'show_recipes', true)
Vue.set(parent, 'expanded', false) Vue.set(parent, 'show_children', false)
} }
}).catch((err) => { }).catch((err) => {
@ -467,13 +482,13 @@ export default {
let parent2 = this.findFood(this.foods2, target.parent) let parent2 = this.findFood(this.foods2, target.parent)
if (parent) { if (parent) {
if (parent.expanded){ if (parent.show_children){
idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id)) idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id))
Vue.set(parent.children, idx, result.data) Vue.set(parent.children, idx, result.data)
} }
} }
if (parent2){ if (parent2){
if (parent2.expanded){ if (parent2.show_children){
idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id)) idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id))
// deep copy to force columns to be indepedent // deep copy to force columns to be indepedent
Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data))) Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data)))
@ -496,7 +511,7 @@ export default {
if (food.length == 1) { if (food.length == 1) {
return food[0] return food[0]
} else if (food.length == 0) { } else if (food.length == 0) {
for (const f of food_list.filter(fd => fd.expanded == true)) { for (const f of food_list.filter(fd => fd.show_children == true)) {
food = this.findFood(f.children, id) food = this.findFood(f.children, id)
if (food) { if (food) {
return food return food
@ -557,32 +572,32 @@ export default {
$state.complete(); $state.complete();
}) })
}, },
destroyCard: function(id) { destroyCard: function(id) {
let fd = this.findFood(this.foods, id) let fd = this.findFood(this.foods, id)
let fd2 = this.findFood(this.foods2, id) let fd2 = this.findFood(this.foods2, id)
let p_id = undefined let p_id = undefined
p_id = fd?.parent ?? fd2.parent p_id = fd?.parent ?? fd2.parent
if (p_id) { if (p_id) {
let parent = this.findFood(this.foods, p_id) let parent = this.findFood(this.foods, p_id)
let parent2 = this.findFood(this.foods2, p_id) let parent2 = this.findFood(this.foods2, p_id)
if (parent){ if (parent){
Vue.set(parent, 'numchild', parent.numchild - 1) Vue.set(parent, 'numchild', parent.numchild - 1)
if (parent.expanded) { if (parent.show_children) {
let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id)) let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id))
Vue.delete(parent.children, idx) Vue.delete(parent.children, idx)
}
}
if (parent2){
Vue.set(parent2, 'numchild', parent2.numchild - 1)
if (parent2.expanded) {
let idx = parent2.children.indexOf(parent2.children.find(kw => kw.id === id))
Vue.delete(parent2.children, idx)
}
} }
} }
this.foods = this.foods.filter(kw => kw.id != id) if (parent2){
this.foods2 = this.foods2.filter(kw => kw.id != id) Vue.set(parent2, 'numchild', parent2.numchild - 1)
if (parent2.show_children) {
let idx = parent2.children.indexOf(parent2.children.find(kw => kw.id === id))
Vue.delete(parent2.children, idx)
}
}
}
this.foods = this.foods.filter(kw => kw.id != id)
this.foods2 = this.foods2.filter(kw => kw.id != id)
}, },
} }
} }

View File

@ -68,32 +68,36 @@
<!-- only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? --> <!-- only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? -->
<div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh"> <div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}"> <div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<keyword-card <generic-horizontal-card v-for="kw in keywords" v-bind:key="kw.id"
v-for="k in keywords" :model=kw
v-bind:key="k.id" model_name="Keyword"
:keyword="k"
:draggable="true" :draggable="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'left')" @item-action="startAction($event, 'left')"
></keyword-card> />
<infinite-loading <infinite-loading
:identifier='left' :identifier='left'
@infinite="infiniteHandler($event, 'left')" @infinite="infiniteHandler($event, 'left')"
spinner="waveDots"> spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading> </infinite-loading>
</div> </div>
<!-- right side keyword cards --> <!-- right side keyword 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">
<keyword-card <generic-horizontal-card v-for="kw in keywords2" v-bind:key="kw.id"
v-for="k in keywords2" :model=kw
v-bind:key="k.id" model_name="Keyword"
:keyword="k" :draggable="true"
draggable="true" :merge="true"
@item-action="startAction($event, 'right')" :move="true"
></keyword-card> @item-action="startAction($event, 'left')"
/>
<infinite-loading <infinite-loading
:identifier='right' :identifier='right'
@infinite="infiniteHandler($event, 'right')" @infinite="infiniteHandler($event, 'right')"
spinner="waveDots"> spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading> </infinite-loading>
</div> </div>
</div> </div>
@ -197,7 +201,7 @@ import _debounce from 'lodash/debounce'
import {ToastMixin} 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 GenericHorizontalCard from "@/components/GenericHorizontalCard";
import GenericMultiselect from "@/components/GenericMultiselect"; import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading'; import InfiniteLoading from 'vue-infinite-loading';
@ -213,7 +217,7 @@ Vue.use(BootstrapVue)
export default { export default {
name: 'KeywordListView', name: 'KeywordListView',
mixins: [ToastMixin], mixins: [ToastMixin],
components: {TwemojiTextarea, KeywordCard, GenericMultiselect, InfiniteLoading}, components: {TwemojiTextarea, GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
computed: { computed: {
// move with generic modals // move with generic modals
emojiDataAll() { emojiDataAll() {
@ -309,8 +313,8 @@ export default {
this.mergeKeyword(e.source.id, e.target.id) this.mergeKeyword(e.source.id, e.target.id)
} }
} else if (e.action === 'get-children') { } else if (e.action === 'get-children') {
if (source.expanded) { if (source.show_children) {
Vue.set(source, 'expanded', false) Vue.set(source, 'show_children', false)
} else { } else {
this.this_item = source this.this_item = source
this.getChildren(col, source) this.getChildren(col, source)
@ -425,7 +429,7 @@ export default {
} }
if (parent) { if (parent) {
Vue.set(parent, 'children', result.data.results) Vue.set(parent, 'children', result.data.results)
Vue.set(parent, 'expanded', true) Vue.set(parent, 'show_children', true)
Vue.set(parent, 'show_recipes', false) Vue.set(parent, 'show_recipes', false)
} }
@ -453,7 +457,7 @@ export default {
if (parent) { if (parent) {
Vue.set(parent, 'recipes', result.data.results) Vue.set(parent, 'recipes', result.data.results)
Vue.set(parent, 'show_recipes', true) Vue.set(parent, 'show_recipes', true)
Vue.set(parent, 'expanded', false) Vue.set(parent, 'show_children', false)
} }
}).catch((err) => { }).catch((err) => {
@ -474,13 +478,13 @@ export default {
let parent2 = this.findKeyword(this.keywords2, target.parent) let parent2 = this.findKeyword(this.keywords2, target.parent)
if (parent) { if (parent) {
if (parent.expanded){ if (parent.show_children){
idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id)) idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id))
Vue.set(parent.children, idx, result.data) Vue.set(parent.children, idx, result.data)
} }
} }
if (parent2){ if (parent2){
if (parent2.expanded){ if (parent2.show_children){
idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id)) idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id))
// deep copy to force columns to be indepedent // deep copy to force columns to be indepedent
Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data))) Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data)))
@ -503,7 +507,7 @@ export default {
if (keyword.length == 1) { if (keyword.length == 1) {
return keyword[0] return keyword[0]
} else if (keyword.length == 0) { } else if (keyword.length == 0) {
for (const k of kw_list.filter(kw => kw.expanded == true)) { for (const k of kw_list.filter(kw => kw.show_children == true)) {
keyword = this.findKeyword(k.children, id) keyword = this.findKeyword(k.children, id)
if (keyword) { if (keyword) {
return keyword return keyword
@ -575,14 +579,14 @@ export default {
let parent2 = this.findKeyword(this.keywords2, p_id) let parent2 = this.findKeyword(this.keywords2, p_id)
if (parent){ if (parent){
Vue.set(parent, 'numchild', parent.numchild - 1) Vue.set(parent, 'numchild', parent.numchild - 1)
if (parent.expanded) { if (parent.show_children) {
let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id)) let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id))
Vue.delete(parent.children, idx) Vue.delete(parent.children, idx)
} }
} }
if (parent2){ if (parent2){
Vue.set(parent2, 'numchild', parent2.numchild - 1) Vue.set(parent2, 'numchild', parent2.numchild - 1)
if (parent2.expanded) { if (parent2.show_children) {
let idx = parent2.children.indexOf(parent2.children.find(kw => kw.id === id)) let idx = parent2.children.indexOf(parent2.children.find(kw => kw.id === id))
Vue.delete(parent2.children, idx) Vue.delete(parent2.children, idx)
} }

View File

@ -1,7 +1,6 @@
<template> <template>
<div row> <div row>
<b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}" <b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}"
refs="foodCard"
style="height: 10vh;" :style="{'cursor:grab' : draggable}" style="height: 10vh;" :style="{'cursor:grab' : draggable}"
@dragover.prevent @dragover.prevent
@dragenter.prevent @dragenter.prevent
@ -12,55 +11,63 @@
@drop="handleDragDrop($event)"> @drop="handleDragDrop($event)">
<b-row no-gutters style="height:inherit;"> <b-row no-gutters style="height:inherit;">
<b-col no-gutters md="3" style="height:inherit;"> <b-col no-gutters md="3" style="height:inherit;">
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="food_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy> <b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="model_image" v-bind:alt="text.image_alt"></b-card-img-lazy>
</b-col> </b-col>
<b-col no-gutters md="9" style="height:inherit;"> <b-col no-gutters md="9" style="height:inherit;">
<b-card-body class="m-0 py-0" style="height:inherit;"> <b-card-body class="m-0 py-0" style="height:inherit;">
<b-card-text class=" h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis"> <b-card-text class=" h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
<h5 class="m-0 mt-1 text-truncate">{{ food.name }}</h5> <h5 class="m-0 mt-1 text-truncate">{{ model[title] }}</h5>
<div class= "m-0 text-truncate">{{ food.description }}</div> <div class= "m-0 text-truncate">{{ model[subtitle] }}</div>
<div class="mt-auto mb-1 d-flex flex-row justify-content-end"> <div class="mt-auto mb-1 d-flex flex-row justify-content-end">
<div v-if="food.numchild !=0" class="mx-2 btn btn-link btn-sm" <div v-if="model[child_count] !=0" class="mx-2 btn btn-link btn-sm"
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':food})"> style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':model})">
<div v-if="!food.expanded">{{food.numchild}} {{$t('Foods')}}</div> <div v-if="!model.show_children">{{ model[child_count] }} {{ model_name }}</div>
<div v-else>{{$t('Hide Foods')}}</div> <div v-else>{{ text.hide_children }}</div>
</div> </div>
<div class="mx-2 btn btn-link btn-sm" style="z-index: 800;" <div v-if="model[recipe_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
v-on:click="$emit('item-action',{'action':'get-recipes','source':food})"> v-on:click="$emit('item-action',{'action':'get-recipes','source':model})">
<div v-if="!food.show_recipes">{{food.numrecipe}} {{$t('Recipes')}}</div> <div v-if="!model.show_recipes">{{ model[recipe_count] }} {{$t('Recipes')}}</div>
<div v-else>{{$t('Hide Recipes')}}</div> <div v-else>{{$t('Hide_Recipes')}}</div>
</div> </div>
</div> </div>
</b-card-text> </b-card-text>
</b-card-body> </b-card-body>
</b-col> </b-col>
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right"> <div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
<b-button v-if="food.recipe" v-b-tooltip.hover :title="food.recipe.name" <slot name="upper-right"></slot>
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="food.recipe.url"/>
<generic-context-menu class="p-0" <generic-context-menu class="p-0"
:show_merge="true" :show_merge="merge"
:show_move="true" :show_move="move"
@item-action="$emit('item-action', {'action': $event, 'source': food})"> @item-action="$emit('item-action', {'action': $event, 'source': model})">
</generic-context-menu> </generic-context-menu>
</div> </div>
</b-row> </b-row>
</b-card> </b-card>
<!-- recursively add child foods --> <!-- recursively add child cards -->
<div class="row" v-if="food.expanded"> <div class="row" v-if="model.show_children">
<div class="col-md-11 offset-md-1"> <div class="col-md-11 offset-md-1">
<food-card v-for="child in food.children" <generic-horizontal-card v-for="child in model[children]" v-bind:key="child.id"
:food="child" :draggable="draggable"
v-bind:key="child.id" :model="child"
draggable="true" :model_name="model_name"
:title="title"
:subtitle="subtitle"
:child_count="child_count"
:children="children"
:recipe_count="recipe_count"
:recipes="recipes"
:merge="merge"
:move="move"
@item-action="$emit('item-action', $event)"> @item-action="$emit('item-action', $event)">
</food-card> </generic-horizontal-card>
</div> </div>
</div> </div>
<!-- conditionally view recipes --> <!-- conditionally view recipes -->
<div class="row" v-if="food.show_recipes"> <div class="row" v-if="model.show_recipes">
<div class="col-md-11 offset-md-1"> <div class="col-md-11 offset-md-1">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
<recipe-card v-for="r in food.recipes" <recipe-card v-for="r in model[recipes]"
v-bind:key="r.id" v-bind:key="r.id"
:recipe="r"> :recipe="r">
</recipe-card> </recipe-card>
@ -69,11 +76,11 @@
</div> </div>
<!-- this should be made a generic component, would also require mixin for functions that generate the popup and put in parent container--> <!-- this should be made a generic component, would also require mixin for functions that generate the popup and put in parent container-->
<b-list-group ref="tooltip" variant="light" v-show="show_menu" v-on-clickaway="closeMenu" style="z-index:999; cursor:pointer"> <b-list-group ref="tooltip" variant="light" v-show="show_menu" v-on-clickaway="closeMenu" style="z-index:999; cursor:pointer">
<b-list-group-item action v-on:click="$emit('item-action',{'action': 'move', 'target': food, 'source': source}); closeMenu()"> <b-list-group-item v-if="move" action v-on:click="$emit('item-action',{'action': 'move', 'target': model, 'source': source}); closeMenu()">
{{$t('Move')}}: {{$t('move_confirmation', {'child': source.name,'parent':food.name})}} {{$t('Move')}}: {{$t('move_confirmation', {'child': source.name,'parent':model.name})}}
</b-list-group-item> </b-list-group-item>
<b-list-group-item action v-on:click="$emit('item-action',{'action': 'merge', 'target': food, 'source': source}); closeMenu()"> <b-list-group-item v-if="merge" action v-on:click="$emit('item-action',{'action': 'merge', 'target': model, 'source': source}); closeMenu()">
{{$t('Merge')}}: {{ $t('merge_confirmation', {'source': source.name,'target':food.name}) }} {{$t('Merge')}}: {{ $t('merge_confirmation', {'source': source.name,'target':model.name}) }}
</b-list-group-item> </b-list-group-item>
<b-list-group-item action v-on:click="closeMenu()"> <b-list-group-item action v-on:click="closeMenu()">
{{$t('Cancel')}} {{$t('Cancel')}}
@ -91,36 +98,50 @@ import { mixin as clickaway } from 'vue-clickaway';
import { createPopper } from '@popperjs/core'; import { createPopper } from '@popperjs/core';
export default { export default {
name: "FoodCard", name: "GenericHorizontalCard",
components: { GenericContextMenu, RecipeCard }, components: { GenericContextMenu, RecipeCard },
mixins: [clickaway], mixins: [clickaway],
props: { props: {
food: Object, model: Object,
draggable: {type: Boolean, default: false} model_name: {type: String, default: 'Blank Model'}, // TODO update translations to handle plural translations
draggable: {type: Boolean, default: false},
title: {type: String, default: 'name'},
subtitle: {type: String, default: 'description'},
child_count: {type: String, default: 'numchild'},
children: {type: String, default: 'children'},
recipe_count: {type: String, default: 'numrecipe'},
recipes: {type: String, default: 'recipes'},
merge: {type: Boolean, default: false},
move: {type: Boolean, default: false},
}, },
data() { data() {
return { return {
food_image: '', model_image: '',
over: false, over: false,
show_menu: false, show_menu: false,
dragMenu: undefined, dragMenu: undefined,
isError: false, isError: false,
source: {}, source: {'id': undefined, 'name': undefined},
target: {} target: {'id': undefined, 'name': undefined},
text: {
'image_alt': '',
'hide_children': '',
}
} }
}, },
mounted() { mounted() {
if (this.food == null || this.food.image == null) { this.model_image = this.model?.image ?? window.IMAGE_PLACEHOLDER
this.food_image = window.IMAGE_PLACEHOLDER
} else {
this.food_image = this.food.image
}
this.dragMenu = this.$refs.tooltip this.dragMenu = this.$refs.tooltip
this.text.image_alt = this.$t(this.model_name + '_Image'),
this.hide_children = this.$t('Hide_' + this.model_name)
}, },
methods: { methods: {
emitAction: function(m) {
},
handleDragStart: function(e) { handleDragStart: function(e) {
this.isError = false this.isError = false
e.dataTransfer.setData('source', JSON.stringify(this.food)) e.dataTransfer.setData('source', JSON.stringify(this.model))
}, },
handleDragEnter: function(e) { handleDragEnter: function(e) {
if (!e.currentTarget.contains(e.relatedTarget) && e.relatedTarget != null) { if (!e.currentTarget.contains(e.relatedTarget) && e.relatedTarget != null) {
@ -134,7 +155,7 @@ export default {
}, },
handleDragDrop: function(e) { handleDragDrop: function(e) {
let source = JSON.parse(e.dataTransfer.getData('source')) let source = JSON.parse(e.dataTransfer.getData('source'))
if (source.id != this.food.id){ if (source.id != this.model.id){
this.source = source this.source = source
let menuLocation = {getBoundingClientRect: this.generateLocation(e.clientX, e.clientY),} let menuLocation = {getBoundingClientRect: this.generateLocation(e.clientX, e.clientY),}
this.show_menu = true this.show_menu = true
@ -161,7 +182,7 @@ export default {
}) })
popper.update() popper.update()
this.over = false this.over = false
this.$emit({'action': 'drop', 'target': this.food, 'source': this.source}) this.$emit({'action': 'drop', 'target': this.model, 'source': this.source})
} else { } else {
this.isError = true this.isError = true
} }

View File

@ -66,7 +66,7 @@ export default {
let page = 1 let page = 1
let root = undefined let root = undefined
let tree = undefined let tree = undefined
let pageSize = 10 let pageSize = 25
if (query === '') { if (query === '') {
query = undefined query = undefined

View File

@ -107,5 +107,6 @@
"Shopping_Category": "Shopping Category", "Shopping_Category": "Shopping Category",
"Edit_Food": "Edit Food", "Edit_Food": "Edit Food",
"Move_Food": "Move Food", "Move_Food": "Move Food",
"New_Food": "New Food" "New_Food": "New Food",
"Hide_Foods": "Hide Food"
} }

View File

@ -86,7 +86,7 @@ module.exports = {
}, },
}, },
// TODO make this conditional on .env DEBUG = TRUE // TODO make this conditional on .env DEBUG = TRUE
config.optimization.minimize(false) // config.optimization.minimize(false)
); );
//TODO somehow remov them as they are also added to the manifest config of the service worker //TODO somehow remov them as they are also added to the manifest config of the service worker