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

@@ -67,32 +67,46 @@
<!-- 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="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<food-card
v-for="f in foods"
v-bind:key="f.id"
:food="f"
<generic-horizontal-card v-for="f in foods" v-bind:key="f.id"
:model=f
model_name="Food"
:draggable="true"
:merge="true"
:move="true"
@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
:identifier='left'
@infinite="infiniteHandler($event, 'left')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading>
</div>
<!-- right side food cards -->
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
<food-card
v-for="f in foods2"
v-bind:key="f.id"
:food="f"
draggable="true"
@item-action="startAction($event, 'right')"
></food-card>
<generic-horizontal-card v-for="f in foods" v-bind:key="f.id"
:model=f
model_name="Food"
:draggable="true"
:merge="true"
:move="true"
@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
:identifier='right'
@infinite="infiniteHandler($event, 'right')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading>
</div>
</div>
@@ -211,7 +225,8 @@ import _debounce from 'lodash/debounce'
import {ToastMixin} from "@/utils/utils";
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 InfiniteLoading from 'vue-infinite-loading';
@@ -220,7 +235,7 @@ Vue.use(BootstrapVue)
export default {
name: 'FoodListView',
mixins: [ToastMixin],
components: {FoodCard, GenericMultiselect, InfiniteLoading},
components: {GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
data() {
return {
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?
startAction: function(e, col) {
let target = e.target || null
let source = e.source || null
let target = e?.target
let source = e?.source
if (e.action == 'delete') {
this.this_item = source
this.$bvModal.show('id_modal_food_delete')
@@ -309,13 +323,14 @@ export default {
this.mergeFood(e.source.id, e.target.id)
}
} else if (e.action === 'get-children') {
if (source.expanded) {
Vue.set(source, 'expanded', false)
if (source.show_children) {
Vue.set(source, 'show_children', false)
} else {
this.this_item = source
this.getChildren(col, source)
}
} else if (e.action === 'get-recipes') {
if (source.show_recipes) {
Vue.set(source, 'show_recipes', false)
} else {
@@ -421,7 +436,7 @@ export default {
}
if (parent) {
Vue.set(parent, 'children', result.data.results)
Vue.set(parent, 'expanded', true)
Vue.set(parent, 'show_children', true)
Vue.set(parent, 'show_recipes', false)
}
}).catch((err) => {
@@ -446,7 +461,7 @@ export default {
if (parent) {
Vue.set(parent, 'recipes', result.data.results)
Vue.set(parent, 'show_recipes', true)
Vue.set(parent, 'expanded', false)
Vue.set(parent, 'show_children', false)
}
}).catch((err) => {
@@ -467,13 +482,13 @@ export default {
let parent2 = this.findFood(this.foods2, target.parent)
if (parent) {
if (parent.expanded){
if (parent.show_children){
idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id))
Vue.set(parent.children, idx, result.data)
}
}
if (parent2){
if (parent2.expanded){
if (parent2.show_children){
idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id))
// deep copy to force columns to be indepedent
Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data)))
@@ -496,7 +511,7 @@ export default {
if (food.length == 1) {
return food[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)
if (food) {
return food
@@ -557,32 +572,32 @@ export default {
$state.complete();
})
},
destroyCard: function(id) {
let fd = this.findFood(this.foods, id)
let fd2 = this.findFood(this.foods2, id)
let p_id = undefined
p_id = fd?.parent ?? fd2.parent
destroyCard: function(id) {
let fd = this.findFood(this.foods, id)
let fd2 = this.findFood(this.foods2, id)
let p_id = undefined
p_id = fd?.parent ?? fd2.parent
if (p_id) {
let parent = this.findFood(this.foods, p_id)
let parent2 = this.findFood(this.foods2, p_id)
if (parent){
Vue.set(parent, 'numchild', parent.numchild - 1)
if (parent.expanded) {
let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id))
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)
}
if (p_id) {
let parent = this.findFood(this.foods, p_id)
let parent2 = this.findFood(this.foods2, p_id)
if (parent){
Vue.set(parent, 'numchild', parent.numchild - 1)
if (parent.show_children) {
let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id))
Vue.delete(parent.children, idx)
}
}
this.foods = this.foods.filter(kw => kw.id != id)
this.foods2 = this.foods2.filter(kw => kw.id != id)
if (parent2){
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? -->
<div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<keyword-card
v-for="k in keywords"
v-bind:key="k.id"
:keyword="k"
<generic-horizontal-card v-for="kw in keywords" v-bind:key="kw.id"
:model=kw
model_name="Keyword"
:draggable="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'left')"
></keyword-card>
/>
<infinite-loading
:identifier='left'
@infinite="infiniteHandler($event, 'left')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading>
</div>
<!-- right side keyword cards -->
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
<keyword-card
v-for="k in keywords2"
v-bind:key="k.id"
:keyword="k"
draggable="true"
@item-action="startAction($event, 'right')"
></keyword-card>
<generic-horizontal-card v-for="kw in keywords2" v-bind:key="kw.id"
:model=kw
model_name="Keyword"
:draggable="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'left')"
/>
<infinite-loading
:identifier='right'
@infinite="infiniteHandler($event, 'right')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading>
</div>
</div>
@@ -197,7 +201,7 @@ import _debounce from 'lodash/debounce'
import {ToastMixin} from "@/utils/utils";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
import KeywordCard from "@/components/KeywordCard";
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading';
@@ -213,7 +217,7 @@ Vue.use(BootstrapVue)
export default {
name: 'KeywordListView',
mixins: [ToastMixin],
components: {TwemojiTextarea, KeywordCard, GenericMultiselect, InfiniteLoading},
components: {TwemojiTextarea, GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
computed: {
// move with generic modals
emojiDataAll() {
@@ -309,8 +313,8 @@ export default {
this.mergeKeyword(e.source.id, e.target.id)
}
} else if (e.action === 'get-children') {
if (source.expanded) {
Vue.set(source, 'expanded', false)
if (source.show_children) {
Vue.set(source, 'show_children', false)
} else {
this.this_item = source
this.getChildren(col, source)
@@ -425,7 +429,7 @@ export default {
}
if (parent) {
Vue.set(parent, 'children', result.data.results)
Vue.set(parent, 'expanded', true)
Vue.set(parent, 'show_children', true)
Vue.set(parent, 'show_recipes', false)
}
@@ -453,7 +457,7 @@ export default {
if (parent) {
Vue.set(parent, 'recipes', result.data.results)
Vue.set(parent, 'show_recipes', true)
Vue.set(parent, 'expanded', false)
Vue.set(parent, 'show_children', false)
}
}).catch((err) => {
@@ -474,13 +478,13 @@ export default {
let parent2 = this.findKeyword(this.keywords2, target.parent)
if (parent) {
if (parent.expanded){
if (parent.show_children){
idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id))
Vue.set(parent.children, idx, result.data)
}
}
if (parent2){
if (parent2.expanded){
if (parent2.show_children){
idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id))
// deep copy to force columns to be indepedent
Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data)))
@@ -503,7 +507,7 @@ export default {
if (keyword.length == 1) {
return keyword[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)
if (keyword) {
return keyword
@@ -575,14 +579,14 @@ export default {
let parent2 = this.findKeyword(this.keywords2, p_id)
if (parent){
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))
Vue.delete(parent.children, idx)
}
}
if (parent2){
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))
Vue.delete(parent2.children, idx)
}

View File

@@ -1,7 +1,6 @@
<template>
<div row>
<b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}"
refs="foodCard"
style="height: 10vh;" :style="{'cursor:grab' : draggable}"
@dragover.prevent
@dragenter.prevent
@@ -12,55 +11,63 @@
@drop="handleDragDrop($event)">
<b-row no-gutters 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 no-gutters md="9" 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">
<h5 class="m-0 mt-1 text-truncate">{{ food.name }}</h5>
<div class= "m-0 text-truncate">{{ food.description }}</div>
<h5 class="m-0 mt-1 text-truncate">{{ model[title] }}</h5>
<div class= "m-0 text-truncate">{{ model[subtitle] }}</div>
<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"
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':food})">
<div v-if="!food.expanded">{{food.numchild}} {{$t('Foods')}}</div>
<div v-else>{{$t('Hide Foods')}}</div>
<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':model})">
<div v-if="!model.show_children">{{ model[child_count] }} {{ model_name }}</div>
<div v-else>{{ text.hide_children }}</div>
</div>
<div class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
v-on:click="$emit('item-action',{'action':'get-recipes','source':food})">
<div v-if="!food.show_recipes">{{food.numrecipe}} {{$t('Recipes')}}</div>
<div v-else>{{$t('Hide Recipes')}}</div>
<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':model})">
<div v-if="!model.show_recipes">{{ model[recipe_count] }} {{$t('Recipes')}}</div>
<div v-else>{{$t('Hide_Recipes')}}</div>
</div>
</div>
</b-card-text>
</b-card-body>
</b-col>
<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"
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="food.recipe.url"/>
<slot name="upper-right"></slot>
<generic-context-menu class="p-0"
:show_merge="true"
:show_move="true"
@item-action="$emit('item-action', {'action': $event, 'source': food})">
:show_merge="merge"
:show_move="move"
@item-action="$emit('item-action', {'action': $event, 'source': model})">
</generic-context-menu>
</div>
</b-row>
</b-card>
<!-- recursively add child foods -->
<div class="row" v-if="food.expanded">
<!-- recursively add child cards -->
<div class="row" v-if="model.show_children">
<div class="col-md-11 offset-md-1">
<food-card v-for="child in food.children"
:food="child"
v-bind:key="child.id"
draggable="true"
<generic-horizontal-card v-for="child in model[children]" v-bind:key="child.id"
:draggable="draggable"
:model="child"
: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)">
</food-card>
</generic-horizontal-card>
</div>
</div>
<!-- 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 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"
:recipe="r">
</recipe-card>
@@ -69,11 +76,11 @@
</div>
<!-- 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-item action v-on:click="$emit('item-action',{'action': 'move', 'target': food, 'source': source}); closeMenu()">
{{$t('Move')}}: {{$t('move_confirmation', {'child': source.name,'parent':food.name})}}
<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':model.name})}}
</b-list-group-item>
<b-list-group-item action v-on:click="$emit('item-action',{'action': 'merge', 'target': food, 'source': source}); closeMenu()">
{{$t('Merge')}}: {{ $t('merge_confirmation', {'source': source.name,'target':food.name}) }}
<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':model.name}) }}
</b-list-group-item>
<b-list-group-item action v-on:click="closeMenu()">
{{$t('Cancel')}}
@@ -91,36 +98,50 @@ import { mixin as clickaway } from 'vue-clickaway';
import { createPopper } from '@popperjs/core';
export default {
name: "FoodCard",
name: "GenericHorizontalCard",
components: { GenericContextMenu, RecipeCard },
mixins: [clickaway],
props: {
food: Object,
draggable: {type: Boolean, default: false}
model: Object,
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() {
return {
food_image: '',
model_image: '',
over: false,
show_menu: false,
dragMenu: undefined,
isError: false,
source: {},
target: {}
source: {'id': undefined, 'name': undefined},
target: {'id': undefined, 'name': undefined},
text: {
'image_alt': '',
'hide_children': '',
}
}
},
mounted() {
if (this.food == null || this.food.image == null) {
this.food_image = window.IMAGE_PLACEHOLDER
} else {
this.food_image = this.food.image
}
this.model_image = this.model?.image ?? window.IMAGE_PLACEHOLDER
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: {
emitAction: function(m) {
},
handleDragStart: function(e) {
this.isError = false
e.dataTransfer.setData('source', JSON.stringify(this.food))
e.dataTransfer.setData('source', JSON.stringify(this.model))
},
handleDragEnter: function(e) {
if (!e.currentTarget.contains(e.relatedTarget) && e.relatedTarget != null) {
@@ -134,7 +155,7 @@ export default {
},
handleDragDrop: function(e) {
let source = JSON.parse(e.dataTransfer.getData('source'))
if (source.id != this.food.id){
if (source.id != this.model.id){
this.source = source
let menuLocation = {getBoundingClientRect: this.generateLocation(e.clientX, e.clientY),}
this.show_menu = true
@@ -161,7 +182,7 @@ export default {
})
popper.update()
this.over = false
this.$emit({'action': 'drop', 'target': this.food, 'source': this.source})
this.$emit({'action': 'drop', 'target': this.model, 'source': this.source})
} else {
this.isError = true
}

View File

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

View File

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