working WIP

This commit is contained in:
smilerz 2021-08-23 20:50:54 -05:00
parent d606ea8db3
commit f7b2af7f97
20 changed files with 2201 additions and 17460 deletions

View File

@ -1 +1 @@
.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)}}
.shake[data-v-05ce8dfc]{-webkit-animation:shake-data-v-05ce8dfc .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-05ce8dfc .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-05ce8dfc{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-05ce8dfc{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-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)}}
.shake[data-v-05ce8dfc]{-webkit-animation:shake-data-v-05ce8dfc .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-05ce8dfc .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-05ce8dfc{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-05ce8dfc{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

7963
vue/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
"vue-cookies": "^1.7.4",
"vue-i18n": "^8.24.4",
"vue-infinite-loading": "^2.4.5",
"vue-infinite-scroll": "^2.0.2",
"vue-multiselect": "^2.1.6",
"vue-property-decorator": "^9.1.2",
"vue-template-compiler": "^2.6.14",

View File

@ -1,123 +1,47 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<div class="row">
<div class="col-md-2 d-none d-md-block">
</div>
<div class="col-xl-8 col-12">
<div class="container-fluid d-flex flex-column flex-grow-1" :class="{'vh-100' : show_split}">
<!-- expanded options box -->
<div class="row flex-shrink-0">
<div class="col col-md-12">
<b-collapse id="collapse_advanced" class="mt-2" v-model="advanced_visible">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-3" style="margin-top: 1vh">
<div class="btn btn-primary btn-block text-uppercase" @click="startAction({'action':'new'})">
{{ this.$t('New_Food') }}
</div>
</div>
<div class="col-md-3" style="margin-top: 1vh">
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
{{ this.$t('Reset_Search') }}
</button>
</div>
<div class="col-md-3" style="position: relative; margin-top: 1vh">
<b-form-checkbox v-model="show_split" name="check-button"
class="shadow-none"
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
{{ this.$t('show_split_screen') }}
</b-form-checkbox>
</div>
</div>
</div>
</div>
</b-collapse>
</div>
</div>
<div class="row flex-shrink-0">
<!-- search box -->
<div class="col col-md">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_input"
v-bind:placeholder="this.$t('Search')"></b-input>
<b-input-group-append>
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
<i class="fas fa-caret-down" v-if="!advanced_visible"></i>
<i class="fas fa-caret-up" v-if="advanced_visible"></i>
</b-button>
</b-input-group-append>
</b-input-group>
</div>
<!-- split side search -->
<div class="col col-md" v-if="show_split">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_input2"
v-bind:placeholder="this.$t('Search')"></b-input>
</b-input-group>
</div>
</div>
<!-- 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}">
<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='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">
<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>
</div>
</div>
<div class="col-md-2 d-none d-md-block">
</div>
</div>
<generic-split-lists
:list_name="this_model"
:load_more_left="load_more_left"
:load_more_right="load_more_right"
@reset="resetList"
@get-list="getFoods"
@item-action="startAction"
>
<template v-slot:cards-left>
<generic-horizontal-card
v-for="f in foods" v-bind:key="f.id"
:model=f
:model_name="this_model"
:draggable="true"
:tree="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>
</template>
<template v-slot:cards-right>
<generic-horizontal-card v-for="f in foods2" v-bind:key="f.id"
:model=f
:model_name="this_model"
:draggable="true"
:tree="true"
:merge="true"
:move="true"
@item-action="startAction($event, 'right')"
>
<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>
</template>
</generic-split-lists>
<!-- TODO Modals can probably be made generic and moved to component -->
<!-- edit modal -->
@ -167,8 +91,8 @@
:title="this.$t('Delete_Food')"
:ok-title="this.$t('Delete')"
:cancel-title="this.$t('Cancel')"
@ok="delFood(this_item.id)">
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
@ok="deleteThis(this_item.id, this_model)">
{{this.$t("delete_confimation", {'kw': this_item.name})}}
</b-modal>
<!-- move modal -->
<b-modal class="modal"
@ -220,39 +144,29 @@ import Vue from 'vue'
import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import _debounce from 'lodash/debounce'
import {ToastMixin} from "@/utils/utils";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
// import FoodCard from "@/components/FoodCard";
import GenericSplitLists from "@/components/GenericSplitLists";
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading';
Vue.use(BootstrapVue)
export default {
name: 'FoodListView',
mixins: [ToastMixin],
components: {GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
mixins: [ToastMixin, GenericSplitLists, GenericHorizontalCard],
components: {GenericHorizontalCard, GenericMultiselect, GenericSplitLists},
data() {
return {
this_model: 'Food',
foods: [],
foods2: [],
show_split: false,
search_input: '',
search_input2: '',
advanced_visible: false,
right_page: 0,
right: +new Date(),
isDirtyRight: false,
left_page: 0,
update_recipe: [],
left: +new Date(),
isDirtyLeft: false,
this_item: {
'id': -1,
load_more_left: true,
load_more_right: true,
blank_item: {
'id': undefined,
'name': '',
'description': '',
'recipe': null,
@ -260,85 +174,135 @@ export default {
'ignore_shopping': false,
'supermarket_category': undefined,
'target': {
'id': -1,
'id': undefined,
'name': ''
},
},
this_item: {
'id': undefined,
'name': '',
'description': '',
'recipe': null,
'recipe_full': undefined,
'ignore_shopping': false,
'supermarket_category': undefined,
'target': {
'id': undefined,
'name': ''
},
},
}
},
watch: {
search_input: _debounce(function() {
this.left_page = 0
this.foods = []
this.left += 1
}, 700),
search_input2: _debounce(function() {
this.right_page = 0
this.foods2 = []
this.right += 1
}, 700)
},
methods: {
resetSearch: function () {
if (this.search_input !== '') {
this.search_input = ''
} else {
this.left_page = 0
this.foods = []
this.left += 1
}
if (this.search_input2 !== '') {
this.search_input2 = ''
} else {
this.right_page = 0
// TODO should model actions be included with the context menu? the card? a seperate mixin avaible to all?
resetList: function(e) {
if (e.column === 'left') {
this.foods = []
} else if (e.column === 'right') {
this.foods2 = []
this.right += 1
}
},
// 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
let source = e?.source
if (e.action == 'delete') {
this.this_item = source
this.$bvModal.show('id_modal_food_delete')
} else if (e.action == 'new') {
this.this_item = {}
this.$bvModal.show('id_modal_food_edit')
} else if (e.action == 'edit') {
this.this_item = source
this.$bvModal.show('id_modal_food_edit')
} else if (e.action === 'move') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_food_move')
} else {
this.moveFood(source.id, target.id)
}
} else if (e.action === 'merge') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_food_merge')
} else {
this.mergeFood(e.source.id, e.target.id)
}
} else if (e.action === 'get-children') {
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') {
startAction: function(e, param) {
let source = e?.source ?? this.blank_item
let target = e?.target ?? undefined
this.this_item = source
this.this_item.target = target || undefined
if (source.show_recipes) {
Vue.set(source, 'show_recipes', false)
} else {
this.this_item = source
this.getRecipes(col, source)
}
switch (e.action) {
case 'delete':
this.$bvModal.show('id_modal_food_delete')
break;
case 'new':
this.this_item = {...this.blank_item}
this.$bvModal.show('id_modal_food_edit')
break;
case 'edit':
this.$bvModal.show('id_modal_food_edit')
break;
case 'move':
if (target == null) {
this.$bvModal.show('id_modal_food_move')
} else {
this.moveFood(source.id, target.id)
}
break;
case 'merge':
if (target == null) {
this.$bvModal.show('id_modal_food_merge')
} else {
this.mergeFood(e.source.id, e.target.id)
}
break;
case 'get-children':
if (source.show_children) {
Vue.set(source, 'show_children', false)
} else {
this.getChildren(param, source)
}
break;
case 'get-recipes':
if (source.show_recipes) {
Vue.set(source, 'show_recipes', false)
} else {
this.getRecipes(param, source)
}
break;
}
},
getFoods: function(params, callback) {
let apiClient = new ApiApiFactory()
let query = options?.query ?? ''
let page = options?.page ?? 1
let root = options?.root ?? undefined
let tree = options?.tree ?? undefined
let pageSize = options?.pageSize ?? 25
if (query === '') {
query = undefined
root = 0
}
// delete above
let options = {
'query': params?.query ?? '',
'page': params?.page ?? 1,
'root' : params?.id ?? undefined
}
let column = params?.column ?? 'left'
// let promise = this.listObjects(this.this_model, options).then(result => {
let promise = apiClient.listFoods(query, root, tree, page, pageSize).then((result) => {
if (result.data.results.length){
if (column ==='left') {
this.foods = this.foods.concat(result.data.results)
if (this.foods?.length < result.data.count) {
this.load_more_left = true
} else {
this.load_more_left = false
}
} else if (column ==='right') {
this.foods2 = this.foods2.concat(result.data.results)
if (this.foods2?.length < result.data.count) {
this.load_more_right = true
} else {
this.load_more_right = false
}
}
} else {
if (column ==='left') {
this.load_more_left = false
} else if (column ==='right') {
this.load_more_right = false
}
console.log('no data returned')
}
callback(promise)
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
},
saveFood: function () {
let apiClient = new ApiApiFactory()
let food = {
@ -358,47 +322,30 @@ export default {
} else {
this.foods2 = []
}
this.this_item={}
}).catch((err) => {
console.log(err)
this.this_item = {}
})
} else {
apiClient.partialUpdateFood(this.this_item.id, food).then(result => {
this.refreshCard(this.this_item.id)
this.this_item={}
}).catch((err) => {
console.log(err)
this.this_item = {}
})
}
},
delFood: function (id) {
let apiClient = new ApiApiFactory()
apiClient.destroyFood(id).then(response => {
this.destroyCard(id)
}).catch((err) => {
console.log(err)
this.this_item = {}
})
this.this_item = {...this.blank_item}
},
moveFood: function (source_id, target_id) {
let apiClient = new ApiApiFactory()
apiClient.moveFood(String(source_id), String(target_id)).then(result => {
if (target_id === 0) {
let food = this.findFood(this.foods, source_id) || this.findFood(this.foods2, source_id)
let food = this.findCard(this.foods, source_id) || this.findCard(this.foods2, source_id)
food.parent = null
if (this.show_split){
this.destroyCard(source_id) // order matters, destroy old card before adding it back in at root
this.foods = [food].concat(this.foods)
this.foods2 = [JSON.parse(JSON.stringify(food))].concat(this.foods2)
} else {
this.destroyCard(source_id)
this.foods = [food].concat(this.foods)
this.foods2 = []
}
this.foods = [food].concat(this.destroyCard(source_id, this.foods))
this.foods2 = [...food].concat(this.destroyCard(source_id, this.foods2)) // order matters, destroy old card before adding it back in at root
} else {
this.destroyCard(source_id)
this.foods = this.destroyCard(source_id, this.foods)
this.foods2 = this.destroyCard(source_id, this.foods2)
this.refreshCard(target_id)
}
}).catch((err) => {
@ -411,7 +358,7 @@ export default {
mergeFood: function (source_id, target_id) {
let apiClient = new ApiApiFactory()
apiClient.mergeFood(String(source_id), String(target_id)).then(result => {
this.destroyCard(source_id)
// this.destroyCard(source_id)
this.refreshCard(target_id)
}).catch((err) => {
console.log('Error', err)
@ -430,9 +377,9 @@ export default {
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
if (col == 'left') {
parent = this.findFood(this.foods, food.id)
parent = this.findCard(this.foods, food.id)
} else if (col == 'right'){
parent = this.findFood(this.foods2, food.id)
parent = this.findCard(this.foods2, food.id)
}
if (parent) {
Vue.set(parent, 'children', result.data.results)
@ -454,9 +401,9 @@ export default {
undefined, undefined, undefined, undefined, undefined, pageSize, undefined
).then(result => {
if (col == 'left') {
parent = this.findFood(this.foods, food.id)
parent = this.findCard(this.foods, food.id)
} else if (col == 'right'){
parent = this.findFood(this.foods2, food.id)
parent = this.findCard(this.foods2, food.id)
}
if (parent) {
Vue.set(parent, 'recipes', result.data.results)
@ -475,11 +422,11 @@ export default {
let idx = undefined
let idx2 = undefined
apiClient.retrieveFood(id).then(result => {
target = this.findFood(this.foods, id) || this.findFood(this.foods2, id)
target = this.findCard(this.foods, id) || this.findCard(this.foods2, id)
if (target.parent) {
let parent = this.findFood(this.foods, target.parent)
let parent2 = this.findFood(this.foods2, target.parent)
let parent = this.findCard(this.foods, target.parent)
let parent2 = this.findCard(this.foods2, target.parent)
if (parent) {
if (parent.show_children){
@ -503,25 +450,6 @@ export default {
})
},
findFood: function(food_list, id){
if (food_list.length == 0) {
return false
}
let food = food_list.filter(fd => fd.id == id)
if (food.length == 1) {
return food[0]
} else if (food.length == 0) {
for (const f of food_list.filter(fd => fd.show_children == true)) {
food = this.findFood(f.children, id)
if (food) {
return food
}
}
} else {
console.log('something terrible happened')
}
},
// this would move with modals with mixin?
prepareEmoji: function() {
this.$refs._edit.addText(this.this_item.icon || '');
@ -532,73 +460,15 @@ export default {
setIcon: function(icon) {
this.this_item.icon = icon
},
infiniteHandler: function($state, col) {
let apiClient = new ApiApiFactory()
let query = (col==='left') ? this.search_input : this.search_input2
let page = (col==='left') ? this.left_page + 1 : this.right_page + 1
let root = undefined
let tree = undefined
let pageSize = undefined
if (query === '') {
query = undefined
root = 0
}
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
if (result.data.results.length){
if (col ==='left') {
this.left_page+=1
this.foods = this.foods.concat(result.data.results)
$state.loaded();
if (this.foods.length >= result.data.count) {
$state.complete();
}
} else if (col ==='right') {
this.right_page+=1
this.foods2 = this.foods2.concat(result.data.results)
$state.loaded();
if (this.foods2.length >= result.data.count) {
$state.complete();
}
}
} else {
console.log('no data returned')
$state.complete();
}
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
$state.complete();
deleteThis: function(id, model) {
const result = new Promise((callback) => this.deleteObject(id, model, callback))
result.then(() => {
this.foods = this.destroyCard(id, this.foods)
this.foods2 = this.destroyCard(id, this.foods2)
})
},
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.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.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

@ -0,0 +1,611 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<div class="row">
<div class="col-md-2 d-none d-md-block">
</div>
<div class="col-xl-8 col-12">
<div class="container-fluid d-flex flex-column flex-grow-1" :class="{'vh-100' : show_split}">
<!-- expanded options box -->
<div class="row flex-shrink-0">
<div class="col col-md-12">
<b-collapse id="collapse_advanced" class="mt-2" v-model="advanced_visible">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-3" style="margin-top: 1vh">
<div class="btn btn-primary btn-block text-uppercase" @click="startAction({'action':'new'})">
{{ this.$t('New_Food') }}
</div>
</div>
<div class="col-md-3" style="margin-top: 1vh">
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
{{ this.$t('Reset_Search') }}
</button>
</div>
<div class="col-md-3" style="position: relative; margin-top: 1vh">
<b-form-checkbox v-model="show_split" name="check-button"
class="shadow-none"
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
{{ this.$t('show_split_screen') }}
</b-form-checkbox>
</div>
</div>
</div>
</div>
</b-collapse>
</div>
</div>
<div class="row flex-shrink-0">
<!-- search box -->
<div class="col col-md">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_input"
v-bind:placeholder="this.$t('Search')"></b-input>
<b-input-group-append>
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
<i class="fas fa-caret-down" v-if="!advanced_visible"></i>
<i class="fas fa-caret-up" v-if="advanced_visible"></i>
</b-button>
</b-input-group-append>
</b-input-group>
</div>
<!-- split side search -->
<div class="col col-md" v-if="show_split">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_input2"
v-bind:placeholder="this.$t('Search')"></b-input>
</b-input-group>
</div>
</div>
<!-- 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}">
<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='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">
<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>
</div>
</div>
<div class="col-md-2 d-none d-md-block">
</div>
</div>
<!-- TODO Modals can probably be made generic and moved to component -->
<!-- edit modal -->
<b-modal class="modal"
:id="'id_modal_food_edit'"
:title="this.$t('Edit_Food')"
:ok-title="this.$t('Save')"
:cancel-title="this.$t('Cancel')"
@ok="saveFood">
<form>
<label for="id_food_name_edit">{{ this.$t('Name') }}</label>
<input class="form-control" type="text" id="id_food_name_edit" v-model="this_item.name">
<label for="id_food_description_edit">{{ this.$t('Description') }}</label>
<input class="form-control" type="text" id="id_food_description_edit" v-model="this_item.description">
<label for="id_food_recipe_edit">{{ this.$t('Recipe') }}</label>
<!-- TODO initial selection isn't working and I don't know why -->
<generic-multiselect
@change="this_item.recipe=$event.val"
label="name"
:initial_selection="[this_item.recipe]"
search_function="listRecipes"
:multiple="false"
:sticky_options="[{'id': null,'name': $t('None')}]"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
: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>
<generic-multiselect
@change="this_item.supermarket_category=$event.val"
label="name"
:initial_selection="[this_item.supermarket_category]"
search_function="listSupermarketCategorys"
:multiple="false"
:sticky_options="[{'id': null,'name': $t('None')}]"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Shopping_Category')">
</generic-multiselect>
</form>
</b-modal>
<!-- delete modal -->
<b-modal class="modal"
:id="'id_modal_food_delete'"
:title="this.$t('Delete_Food')"
:ok-title="this.$t('Delete')"
:cancel-title="this.$t('Cancel')"
@ok="delFood(this_item.id)">
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
</b-modal>
<!-- move modal -->
<b-modal class="modal"
:id="'id_modal_food_move'"
:title="this.$t('Move_Food')"
:ok-title="this.$t('Move')"
:cancel-title="this.$t('Cancel')"
@ok="moveFood(this_item.id, this_item.target.id)">
{{ this.$t("move_selection", {'child': this_item.name}) }}
<generic-multiselect
@change="this_item.target=$event.val"
label="name"
search_function="listFoods"
:multiple="false"
:sticky_options="[{'id': 0,'name': $t('Root')}]"
:tree_api="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Search')">
</generic-multiselect>
</b-modal>
<!-- merge modal -->
<b-modal class="modal"
:id="'id_modal_food_merge'"
:title="this.$t('Merge_Food')"
:ok-title="this.$t('Merge')"
:cancel-title="this.$t('Cancel')"
@ok="mergeFood(this_item.id, this_item.target.id)">
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('food')}) }}
<generic-multiselect
@change="this_item.target=$event.val"
label="name"
search_function="listFoods"
:multiple="false"
:tree_api="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="this.$t('Search')">
</generic-multiselect>
</b-modal>
</div>
</template>
<script>
import axios from "axios";
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
import Vue from 'vue'
import {BootstrapVue} from 'bootstrap-vue'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import _debounce from 'lodash/debounce'
import {ToastMixin} from "@/utils/utils";
import {ApiApiFactory} from "@/utils/openapi/api.ts";
// import FoodCard from "@/components/FoodCard";
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
import GenericMultiselect from "@/components/GenericMultiselect";
import InfiniteLoading from 'vue-infinite-loading';
Vue.use(BootstrapVue)
export default {
name: 'FoodListView',
mixins: [ToastMixin],
components: {GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
data() {
return {
foods: [],
foods2: [],
show_split: false,
search_input: '',
search_input2: '',
advanced_visible: false,
right_page: 0,
right: +new Date(),
isDirtyRight: false,
left_page: 0,
update_recipe: [],
left: +new Date(),
isDirtyLeft: false,
this_item: {
'id': -1,
'name': '',
'description': '',
'recipe': null,
'recipe_full': undefined,
'ignore_shopping': false,
'supermarket_category': undefined,
'target': {
'id': -1,
'name': ''
},
},
}
},
watch: {
search_input: _debounce(function() {
this.left_page = 0
this.foods = []
this.left += 1
}, 700),
search_input2: _debounce(function() {
this.right_page = 0
this.foods2 = []
this.right += 1
}, 700)
},
methods: {
resetSearch: function () {
if (this.search_input !== '') {
this.search_input = ''
} else {
this.left_page = 0
this.foods = []
this.left += 1
}
if (this.search_input2 !== '') {
this.search_input2 = ''
} else {
this.right_page = 0
this.foods2 = []
this.right += 1
}
},
// 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
let source = e?.source
if (e.action == 'delete') {
this.this_item = source
this.$bvModal.show('id_modal_food_delete')
} else if (e.action == 'new') {
this.this_item = {}
this.$bvModal.show('id_modal_food_edit')
} else if (e.action == 'edit') {
this.this_item = source
this.$bvModal.show('id_modal_food_edit')
} else if (e.action === 'move') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_food_move')
} else {
this.moveFood(source.id, target.id)
}
} else if (e.action === 'merge') {
this.this_item = source
if (target == null) {
this.$bvModal.show('id_modal_food_merge')
} else {
this.mergeFood(e.source.id, e.target.id)
}
} else if (e.action === 'get-children') {
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 {
this.this_item = source
this.getRecipes(col, source)
}
}
},
saveFood: function () {
let apiClient = new ApiApiFactory()
let food = {
name: this.this_item.name,
description: this.this_item.description,
recipe: this.this_item.recipe?.id ?? null,
ignore_shopping: this.this_item.ignore_shopping,
supermarket_category: this.this_item.supermarket_category?.id ?? null,
}
if (!this.this_item.id) { // if there is no item id assume its a new item
apiClient.createFood(food).then(result => {
// place all new foods at the top of the list - could sort instead
this.foods = [result.data].concat(this.foods)
// this creates a deep copy to make sure that columns stay independent
if (this.show_split){
this.foods2 = [JSON.parse(JSON.stringify(result.data))].concat(this.foods2)
} else {
this.foods2 = []
}
this.this_item={}
}).catch((err) => {
console.log(err)
this.this_item = {}
})
} else {
apiClient.partialUpdateFood(this.this_item.id, food).then(result => {
this.refreshCard(this.this_item.id)
this.this_item={}
}).catch((err) => {
console.log(err)
this.this_item = {}
})
}
},
delFood: function (id) {
let apiClient = new ApiApiFactory()
apiClient.destroyFood(id).then(response => {
this.destroyCard(id)
}).catch((err) => {
console.log(err)
this.this_item = {}
})
},
moveFood: function (source_id, target_id) {
let apiClient = new ApiApiFactory()
apiClient.moveFood(String(source_id), String(target_id)).then(result => {
if (target_id === 0) {
let food = this.findFood(this.foods, source_id) || this.findFood(this.foods2, source_id)
food.parent = null
if (this.show_split){
this.destroyCard(source_id) // order matters, destroy old card before adding it back in at root
this.foods = [food].concat(this.foods)
this.foods2 = [JSON.parse(JSON.stringify(food))].concat(this.foods2)
} else {
this.destroyCard(source_id)
this.foods = [food].concat(this.foods)
this.foods2 = []
}
} else {
this.destroyCard(source_id)
this.refreshCard(target_id)
}
}).catch((err) => {
// TODO none of the error checking works because the openapi generated functions don't throw an error?
// or i'm capturing it incorrectly
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
},
mergeFood: function (source_id, target_id) {
let apiClient = new ApiApiFactory()
apiClient.mergeFood(String(source_id), String(target_id)).then(result => {
this.destroyCard(source_id)
this.refreshCard(target_id)
}).catch((err) => {
console.log('Error', err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
},
// TODO: DRY the listFood functions (refresh, get children, infinityHandler ) can probably all be consolidated into a single function
getChildren: function(col, food){
let apiClient = new ApiApiFactory()
let parent = {}
let query = undefined
let page = undefined
let root = food.id
let tree = undefined
let pageSize = 200
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
if (col == 'left') {
parent = this.findFood(this.foods, food.id)
} else if (col == 'right'){
parent = this.findFood(this.foods2, food.id)
}
if (parent) {
Vue.set(parent, 'children', result.data.results)
Vue.set(parent, 'show_children', true)
Vue.set(parent, 'show_recipes', false)
}
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
},
getRecipes: function(col, food){
let apiClient = new ApiApiFactory()
let parent = {}
let pageSize = 200
apiClient.listRecipes(
undefined, undefined, String(food.id), undefined, undefined, undefined,
undefined, undefined, undefined, undefined, undefined, pageSize, undefined
).then(result => {
if (col == 'left') {
parent = this.findFood(this.foods, food.id)
} else if (col == 'right'){
parent = this.findFood(this.foods2, food.id)
}
if (parent) {
Vue.set(parent, 'recipes', result.data.results)
Vue.set(parent, 'show_recipes', true)
Vue.set(parent, 'show_children', false)
}
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
},
refreshCard: function(id){
let target = {}
let apiClient = new ApiApiFactory()
let idx = undefined
let idx2 = undefined
apiClient.retrieveFood(id).then(result => {
target = this.findFood(this.foods, id) || this.findFood(this.foods2, id)
if (target.parent) {
let parent = this.findFood(this.foods, target.parent)
let parent2 = this.findFood(this.foods2, target.parent)
if (parent) {
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.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)))
}
}
} else {
idx = this.foods.indexOf(this.foods.find(food => food.id === target.id))
idx2 = this.foods2.indexOf(this.foods2.find(food => food.id === target.id))
Vue.set(this.foods, idx, result.data)
Vue.set(this.foods2, idx2, JSON.parse(JSON.stringify(result.data)))
}
})
},
findFood: function(food_list, id){
if (food_list.length == 0) {
return false
}
let food = food_list.filter(fd => fd.id == id)
if (food.length == 1) {
return food[0]
} else if (food.length == 0) {
for (const f of food_list.filter(fd => fd.show_children == true)) {
food = this.findFood(f.children, id)
if (food) {
return food
}
}
} else {
console.log('something terrible happened')
}
},
// this would move with modals with mixin?
prepareEmoji: function() {
this.$refs._edit.addText(this.this_item.icon || '');
this.$refs._edit.blur()
document.getElementById('btn-emoji-default').disabled = true;
},
// this would move with modals with mixin?
setIcon: function(icon) {
this.this_item.icon = icon
},
infiniteHandler: function($state, col) {
let apiClient = new ApiApiFactory()
let query = (col==='left') ? this.search_input : this.search_input2
let page = (col==='left') ? this.left_page + 1 : this.right_page + 1
let root = undefined
let tree = undefined
let pageSize = undefined
if (query === '') {
query = undefined
root = 0
}
apiClient.listFoods(query, root, tree, page, pageSize).then(result => {
if (result.data.results.length){
if (col ==='left') {
this.left_page+=1
this.foods = this.foods.concat(result.data.results)
$state.loaded();
if (this.foods.length >= result.data.count) {
$state.complete();
}
} else if (col ==='right') {
this.right_page+=1
this.foods2 = this.foods2.concat(result.data.results)
$state.loaded();
if (this.foods2.length >= result.data.count) {
$state.complete();
}
}
} else {
console.log('no data returned')
$state.complete();
}
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
$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
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)
}
}
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)
},
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style>
</style>

View File

@ -11,7 +11,7 @@
@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="model_image" v-bind:alt="text.image_alt"></b-card-img-lazy>
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="model_image" v-bind:alt="$t('Recipe_Image')"></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;">
@ -35,7 +35,6 @@
</b-col>
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
<slot name="upper-right"></slot>
<generic-context-menu class="p-0"
:show_merge="merge"
:show_move="move"
@ -74,7 +73,7 @@
</div>
</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-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})}}
@ -92,6 +91,7 @@
</template>
<script>
import {ApiApiFactory} from "@/utils/openapi/api.ts";
import GenericContextMenu from "@/components/GenericContextMenu";
import RecipeCard from "@/components/RecipeCard";
import { mixin as clickaway } from 'vue-clickaway';
@ -113,6 +113,7 @@ export default {
recipes: {type: String, default: 'recipes'},
merge: {type: Boolean, default: false},
move: {type: Boolean, default: false},
tree: {type: Boolean, default: false},
},
data() {
return {
@ -124,7 +125,6 @@ export default {
source: {'id': undefined, 'name': undefined},
target: {'id': undefined, 'name': undefined},
text: {
'image_alt': '',
'hide_children': '',
}
}
@ -132,8 +132,7 @@ export default {
mounted() {
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)
this.text.hide_children = this.$t('Hide_' + this.model_name)
},
methods: {
emitAction: function(m) {
@ -199,7 +198,39 @@ export default {
},
closeMenu: function(){
this.show_menu = false
},
deleteObject: function(id, model, callback) {
let apiClient = new ApiApiFactory()
let promise = apiClient['destroy' + model](id).then(() => {
}).catch((err) => {
console.log(err)
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
})
callback(promise)
},
async listObjects(model, options) {
let apiClient = new ApiApiFactory()
let query = options?.query ?? ''
let page = options?.page ?? 1
let root = options?.root ?? undefined
let tree = options?.tree ?? undefined
let pageSize = options?.pageSize ?? 25
if (this.tree) {
if (query === '') {
query = undefined
root = 0
}
await apiClient.listFoods(query, root, tree, page, pageSize).then((result) => {
return result
})
} else {
await apiClient.listFoods(query, page, pageSize).then((result) => {
return result
})
}
}
}
}
</script>

View File

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

View File

@ -0,0 +1,235 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<div class="row">
<div class="col-md-2 d-none d-md-block">
</div>
<div class="col-xl-8 col-12">
<div class="container-fluid d-flex flex-column flex-grow-1" :class="{'vh-100' : show_split}">
<!-- expanded options box -->
<div class="row flex-shrink-0">
<div class="col col-md-12">
<b-collapse id="collapse_advanced" class="mt-2" v-model="advanced_visible">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-3" style="margin-top: 1vh">
<div class="btn btn-primary btn-block text-uppercase" @click="$emit('item-action', {'action':'new'})">
{{ this.text.new }}
</div>
</div>
<div class="col-md-3" style="margin-top: 1vh">
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
{{ this.text.reset }}
</button>
</div>
<div class="col-md-3" style="position: relative; margin-top: 1vh">
<b-form-checkbox v-model="show_split" name="check-button"
class="shadow-none"
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
{{ this.text.split }}
</b-form-checkbox>
</div>
</div>
</div>
</div>
</b-collapse>
</div>
</div>
<div class="row flex-shrink-0">
<!-- search box -->
<div class="col col-md">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_right"
v-bind:placeholder="this.text.search"></b-input>
<b-input-group-append>
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
<i class="fas fa-caret-down" v-if="!advanced_visible"></i>
<i class="fas fa-caret-up" v-if="advanced_visible"></i>
</b-button>
</b-input-group-append>
</b-input-group>
</div>
<!-- split side search -->
<div class="col col-md" v-if="show_split">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_left"
v-bind:placeholder="this.text.search"></b-input>
</b-input-group>
</div>
</div>
<!-- only show scollbars in split mode -->
<!-- weird behavior when switching to split mode, infinite scoll doesn't trigger if
bottom of page is in viewport can trigger by scrolling page (not column) up -->
<div class="row" :class="{'overflow-hidden' : show_split}">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<slot name="cards-left"></slot>
<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">
<slot name="cards-right"></slot>
<infinite-loading
:identifier='right'
@infinite="infiniteHandler($event, 'right')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
</infinite-loading>
</div>
</div>
</div>
</div>
<div class="col-md-2 d-none d-md-block">
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue' // maybe not needed?
import 'bootstrap-vue/dist/bootstrap-vue.css'
import _debounce from 'lodash/debounce'
import InfiniteLoading from 'vue-infinite-loading';
export default {
name: 'GenericSplitLists',
components: {InfiniteLoading},
props: {
list_name: {type: String, default: 'Blank List'}, // TODO update translations to handle plural translations
left_list: {type:Array, default(){return []}},
right_list: {type:Array, default(){return []}},
load_more_left: {type: Boolean, default: false},
load_more_right: {type: Boolean, default: false},
//merge: {type: Boolean, default: false},
//move: {type: Boolean, default: false},
//action: Object
},
data() {
return {
advanced_visible: false,
show_split: false,
search_right: '',
search_left: '',
right_page: 1,
left_page: 1,
right: +new Date(),
left: +new Date(),
isDirtyright: false,
isDirtyleft: false,
text: {
'new': '',
'name': '',
'reset': this.$t('Reset_Search'),
'split': this.$t('show_split_screen'),
'search': this.$t('Search')
},
}
},
mounted() {
this.dragMenu = this.$refs.tooltip
this.text.new = this.$t('New_' + this.list_name)
this.text.name = this.$t(this.list_name)
},
watch: {
search_right: _debounce(function() {
this.left_page = 0
this.$emit('reset', {'column':'left'})
this.left += 1
}, 700),
search_left: _debounce(function() {
this.right_page = 0
this.$emit('reset', {'column':'right'})
this.right += 1
}, 700),
},
methods: {
resetSearch: function () {
if (this.search_right !== '') {
this.search_right = ''
} else {
this.left_page = 1
this.$emit('reset', {'column':'left'})
this.left += 1
}
if (this.search_left !== '') {
this.search_left = ''
} else {
this.right_page = 1
this.$emit('reset', {'column':'right'})
this.right += 1
}
},
infiniteHandler: function($state, col) {
let params = {
'query': (col==='left') ? this.search_right : this.search_left,
'page': (col==='left') ? this.left_page + 1 : this.right_page + 1,
'column': col
}
const result = new Promise((callback) => this.$emit('get-list', params, callback))
result.then((result) => {
console.log(result)
this[col+'_page']+=1
$state.loaded();
if (!this['load_more_' + col]) {
$state.complete();
}
}).catch(() => {
$state.complete();
})
},
findCard: function(card_list, id){
let card_length = card_list?.length ?? 0
if (card_length == 0) {
return false
}
let cards = card_list.filter(obj => obj.id == id)
if (cards.length == 1) {
return cards[0]
} else if (cards.length == 0) {
for (const o of card_list.filter(o => o.show_children == true)) {
cards = this.findCard(o.children, id)
if (cards) {
return cards
}
}
} else {
console.log('something terrible happened')
}
},
destroyCard: function(id, cardList) {
let card = this.findCard(cardList, id)
let card_id = undefined
let p_id = card?.parent ?? undefined
if (p_id) {
let parent = this.findCard(cardList, p_id)
if (parent){
Vue.set(parent, 'numchild', parent.numchild - 1)
if (parent.show_children) {
let idx = parent.children.indexOf(parent.children.find(x => x.id === id))
Vue.delete(parent.children, idx)
}
}
}
return cardList.filter(kw => kw.id != id)
},
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style>
</style>

View File

@ -108,5 +108,7 @@
"Edit_Food": "Edit Food",
"Move_Food": "Move Food",
"New_Food": "New Food",
"Hide_Foods": "Hide Food"
"Hide_Food": "Hide Food",
"Delete_Food": "Delete Food",
"No_ID": "ID not found, cannot delete."
}

File diff suppressed because it is too large Load Diff