card loading fixes
This commit is contained in:
@ -19,42 +19,16 @@
|
||||
<generic-horizontal-card
|
||||
v-for="i in items_left" v-bind:key="i.id"
|
||||
:item=i
|
||||
:item_type="this_model.name"
|
||||
:model="this_model"
|
||||
:draggable="true"
|
||||
:merge="this_model['merge'] !== false"
|
||||
:move="this_model['move'] !== false"
|
||||
@item-action="startAction($event, 'left')">
|
||||
<!-- foods can also be a recipe, show link to the recipe if it exists -->
|
||||
<template v-slot:upper-right>
|
||||
<b-button v-if="i.recipe" v-b-tooltip.hover :title="i.recipe.name"
|
||||
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="i.recipe.url"/>
|
||||
<!-- keywords can have icons - if it exists, display it -->
|
||||
<b-button v-if="i.icon"
|
||||
class=" btn p-0 border-0" variant="link">
|
||||
{{i.icon}}
|
||||
</b-button>
|
||||
</template>
|
||||
</generic-horizontal-card>
|
||||
@item-action="startAction($event, 'left')"/>
|
||||
</template>
|
||||
<template v-slot:cards-right>
|
||||
<generic-horizontal-card v-for="i in items_right" v-bind:key="i.id"
|
||||
:item=i
|
||||
:item_type="this_model.name"
|
||||
:model="this_model"
|
||||
:draggable="true"
|
||||
:merge="this_model['merge'] !== false"
|
||||
:move="this_model['move'] !== false"
|
||||
@item-action="startAction($event, 'right')">
|
||||
<!-- foods can also be a recipe, show link to the recipe if it exists -->
|
||||
<template v-slot:upper-right>
|
||||
<b-button v-if="i.recipe" v-b-tooltip.hover :title="i.recipe.name"
|
||||
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="i.recipe.url"/>
|
||||
<!-- keywords can have icons - if it exists, display it -->
|
||||
<b-button v-if="i.icon"
|
||||
class=" btn p-0 border-0" variant="link">
|
||||
{{i.icon}}
|
||||
</b-button>
|
||||
</template>
|
||||
</generic-horizontal-card>
|
||||
@item-action="startAction($event, 'right')"/>
|
||||
</template>
|
||||
</generic-split-lists>
|
||||
|
||||
@ -70,7 +44,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {CardMixin, ApiMixin} from "@/utils/utils";
|
||||
import {StandardToasts} from "@/utils/utils";
|
||||
import {StandardToasts, ToastMixin} from "@/utils/utils";
|
||||
|
||||
import GenericSplitLists from "@/components/GenericSplitLists";
|
||||
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
|
||||
@ -82,7 +56,7 @@ export default {
|
||||
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
|
||||
// or i'm capturing it incorrectly
|
||||
name: 'ModelListView',
|
||||
mixins: [CardMixin, ApiMixin],
|
||||
mixins: [CardMixin, ApiMixin, ToastMixin],
|
||||
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
||||
data() {
|
||||
return {
|
||||
@ -196,11 +170,11 @@ export default {
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, params).then((result) => {
|
||||
if (result.data.results.length) {
|
||||
this['items_' + column] = this['items_' + column].concat(result.data?.results)
|
||||
this[column + '_counts']['current'] = this['items_' + column].length
|
||||
this[column + '_counts']['max'] = result.data.count
|
||||
this[column + '_counts']['current'] = this['items_' + column].length
|
||||
} else {
|
||||
this[column + '_counts']['current'] = 0
|
||||
this[column + '_counts']['max'] = 0
|
||||
this[column + '_counts']['current'] = 0
|
||||
console.log('no data returned')
|
||||
}
|
||||
}).catch((err) => {
|
||||
@ -248,7 +222,6 @@ export default {
|
||||
}
|
||||
this.genericAPI(this.this_model, this.Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
if (target_id === 0) {
|
||||
|
||||
this.items_left = [item].concat(this.destroyCard(source_id, this.items_left)) // order matters, destroy old card before adding it back in at root
|
||||
this.items_right = [...[item]].concat(this.destroyCard(source_id, this.items_right)) // order matters, destroy old card before adding it back in at root
|
||||
item.parent = null
|
||||
|
40
vue/src/components/Badges.vue
Normal file
40
vue/src/components/Badges.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<span>
|
||||
<linked-recipe v-if="linkedRecipe"
|
||||
:item="item"/>
|
||||
<icon-badge v-if="Icon"
|
||||
:item="item"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LinkedRecipe from "@/components/Badges/LinkedRecipe";
|
||||
import IconBadge from "@/components/Badges/Icon";
|
||||
|
||||
export default {
|
||||
name: 'CardBadges',
|
||||
components: {LinkedRecipe, IconBadge},
|
||||
props: {
|
||||
item: {type: Object},
|
||||
model: {type: Object}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
computed: {
|
||||
linkedRecipe: function () {
|
||||
return this.model?.badges?.linked_recipe ?? false
|
||||
},
|
||||
Icon: function () {
|
||||
return this.model?.badges?.icon ?? false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
27
vue/src/components/Badges/Icon.vue
Normal file
27
vue/src/components/Badges/Icon.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-button v-if="item.icon" class=" btn p-0 border-0" variant="link">
|
||||
{{item.icon}}
|
||||
</b-button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'IconBadge',
|
||||
props: {
|
||||
item: {type: Object}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
26
vue/src/components/Badges/LinkedRecipe.vue
Normal file
26
vue/src/components/Badges/LinkedRecipe.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-button v-if="item.recipe" v-b-tooltip.hover :title="item.recipe.name"
|
||||
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="item.recipe.url"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'LinkedRecipeBadge',
|
||||
props: {
|
||||
item: {type: Object}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-dropdown variant="link" toggle-class="text-decoration-none" no-caret style="boundary:window">
|
||||
<template #button-content>
|
||||
<i class="fas fa-ellipsis-v" ></i>
|
||||
@ -20,6 +21,7 @@
|
||||
</b-dropdown-item>
|
||||
|
||||
</b-dropdown>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end">
|
||||
<div v-if="item[child_count]" class="mx-2 btn btn-link btn-sm"
|
||||
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':item})">
|
||||
<div v-if="!item.show_children">{{ item[child_count] }} {{ item_type }}</div>
|
||||
<div v-if="!item.show_children">{{ item[child_count] }} {{ itemName }}</div>
|
||||
<div v-else>{{ text.hide_children }}</div>
|
||||
</div>
|
||||
<div v-if="item[recipe_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
|
||||
@ -34,10 +34,10 @@
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
|
||||
<slot name="upper-right"></slot>
|
||||
<badges :item="item" :model="model"/>
|
||||
<generic-context-menu class="p-0"
|
||||
:show_merge="merge"
|
||||
:show_move="move"
|
||||
:show_merge="useMerge"
|
||||
:show_move="useMove"
|
||||
@item-action="$emit('item-action', {'action': $event, 'source': item})">
|
||||
</generic-context-menu>
|
||||
</div>
|
||||
@ -49,15 +49,13 @@
|
||||
<generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id"
|
||||
:draggable="draggable"
|
||||
:item="child"
|
||||
:item_type="item_type"
|
||||
:model="model"
|
||||
: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)">
|
||||
</generic-horizontal-card>
|
||||
</div>
|
||||
@ -75,10 +73,10 @@
|
||||
</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:9999; cursor:pointer">
|
||||
<b-list-group-item v-if="move" action v-on:click="$emit('item-action',{'action': 'move', 'target': item, 'source': source}); closeMenu()">
|
||||
<b-list-group-item v-if="useMove" action v-on:click="$emit('item-action',{'action': 'move', 'target': item, 'source': source}); closeMenu()">
|
||||
<i class="fas fa-expand-arrows-alt fa-fw"></i> {{$t('Move')}}: {{$t('move_confirmation', {'child': source.name,'parent':item.name})}}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item v-if="merge" action v-on:click="$emit('item-action',{'action': 'merge', 'target': item, 'source': source}); closeMenu()">
|
||||
<b-list-group-item v-if="useMerge" action v-on:click="$emit('item-action',{'action': 'merge', 'target': item, 'source': source}); closeMenu()">
|
||||
<i class="fas fa-compress-arrows-alt fa-fw"></i> {{$t('Merge')}}: {{ $t('merge_confirmation', {'source': source.name,'target':item.name}) }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item action v-on:click="closeMenu()">
|
||||
@ -92,27 +90,25 @@
|
||||
|
||||
<script>
|
||||
import GenericContextMenu from "@/components/GenericContextMenu";
|
||||
import Badges from "@/components/Badges";
|
||||
import RecipeCard from "@/components/RecipeCard";
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { createPopper } from '@popperjs/core';
|
||||
|
||||
export default {
|
||||
name: "GenericHorizontalCard",
|
||||
components: { GenericContextMenu, RecipeCard },
|
||||
components: { GenericContextMenu, RecipeCard, Badges},
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
item: Object,
|
||||
item_type: {type: String, default: 'Blank Item Type'}, // TODO update translations to handle plural translations
|
||||
item: {type: Object},
|
||||
model: {type: Object},
|
||||
draggable: {type: Boolean, default: false},
|
||||
title: {type: String, default: 'name'},
|
||||
title: {type: String, default: 'name'}, // this and the following props can probably be moved to model.js and made computed values
|
||||
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'},
|
||||
move: {type: Boolean, default: false},
|
||||
merge: {type: Boolean, default: false},
|
||||
tree: {type: Boolean, default: false},
|
||||
recipes: {type: String, default: 'recipes'}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -131,7 +127,18 @@ export default {
|
||||
mounted() {
|
||||
this.item_image = this.item?.image ?? window.IMAGE_PLACEHOLDER
|
||||
this.dragMenu = this.$refs.tooltip
|
||||
this.text.hide_children = this.$t('Hide_' + this.item_type)
|
||||
this.text.hide_children = this.$t('Hide_' + this.itemName)
|
||||
},
|
||||
computed: {
|
||||
itemName: function() {
|
||||
return this.model?.name ?? "You Forgot To Set Model Name in model.js"
|
||||
},
|
||||
useMove: function() {
|
||||
return this.model['move'] !== false
|
||||
},
|
||||
useMerge: function() {
|
||||
return this.model['merge'] !== false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleDragStart: function(e) {
|
||||
|
@ -75,12 +75,7 @@ export default {
|
||||
})
|
||||
},
|
||||
selectionChanged: function () {
|
||||
if (this.multiple) {
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||
} else {
|
||||
// if not multiple listener is expecting a single object, not an array
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects?.[0] ?? null})
|
||||
}
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||
|
||||
},
|
||||
addNew(e) {
|
||||
|
@ -43,7 +43,7 @@
|
||||
<!-- search box -->
|
||||
<div class="col col-md">
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" v-model="search_right"
|
||||
<b-input class="form-control" v-model="search_left"
|
||||
v-bind:placeholder="this.text.search"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
|
||||
@ -57,7 +57,7 @@
|
||||
<!-- 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"
|
||||
<b-input class="form-control" v-model="search_right"
|
||||
v-bind:placeholder="this.text.search"></b-input>
|
||||
</b-input-group>
|
||||
</div>
|
||||
@ -123,6 +123,8 @@ export default {
|
||||
left_page: 0,
|
||||
right_state: undefined,
|
||||
left_state: undefined,
|
||||
right_dirty: false,
|
||||
left_dirty: false,
|
||||
right: +new Date(),
|
||||
left: +new Date(),
|
||||
text: {
|
||||
@ -139,12 +141,22 @@ export default {
|
||||
this.text.new = this.$t('New_' + this.list_name)
|
||||
},
|
||||
watch: {
|
||||
search_right: _debounce(function() {
|
||||
search_left: _debounce(function() {
|
||||
if (this.left_dirty) {
|
||||
//prevents running twice if search is reset
|
||||
this.left_dirty = false
|
||||
return
|
||||
}
|
||||
this.left_page = 0
|
||||
this.$emit('reset', {'column':'left'})
|
||||
this.left += 1
|
||||
}, 700),
|
||||
search_left: _debounce(function() {
|
||||
search_right: _debounce(function(newVal, oldVal) {
|
||||
if (this.right_dirty) {
|
||||
//prevents running twice if search is reset
|
||||
this.right_dirty = false
|
||||
return
|
||||
}
|
||||
this.right_page = 0
|
||||
this.$emit('reset', {'column':'right'})
|
||||
this.right += 1
|
||||
@ -174,20 +186,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
resetSearch: function () {
|
||||
if (this.search_right == '') {
|
||||
this.right_dirty = true
|
||||
this.search_right = ''
|
||||
this.right_page = 0
|
||||
this.right += 1
|
||||
this.$emit('reset', {'column':'right'})
|
||||
} else {
|
||||
this.search_right = ''
|
||||
}
|
||||
if (this.search_left == '') {
|
||||
|
||||
this.left_dirty = true
|
||||
this.search_left = ''
|
||||
this.left_page = 0
|
||||
this.left += 1
|
||||
this.$emit('reset', {'column':'left'})
|
||||
} else {
|
||||
this.search_left = ''
|
||||
}
|
||||
},
|
||||
infiniteHandler: function($state, col) {
|
||||
let params = {
|
||||
|
@ -29,9 +29,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
Button: function(e) {
|
||||
this.$bvModal.show('modal')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -57,7 +57,7 @@ export default {
|
||||
return this.new_value
|
||||
} else if (!this.new_value) {
|
||||
return []
|
||||
} else if (this.new_value.id) {
|
||||
} else if (typeof(this.new_value) === 'object') {
|
||||
return [this.new_value]
|
||||
} else {
|
||||
return [{'id': -1, 'name': this.new_value}]
|
||||
@ -83,8 +83,6 @@ export default {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
this.show_modal = false
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,6 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
Button: function(e) {
|
||||
this.$bvModal.show('modal')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -62,6 +62,9 @@ export class Models {
|
||||
'name': i18n.t('Food'), // *OPTIONAL* : parameters will be built model -> model_type -> default
|
||||
'apiName': 'Food', // *REQUIRED* : the name that is used in api.ts for this model
|
||||
'model_type': this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create
|
||||
'badges': {
|
||||
'linked_recipe': true
|
||||
},
|
||||
// REQUIRED: unordered array of fields that can be set during create
|
||||
'create': {
|
||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||
@ -110,6 +113,9 @@ export class Models {
|
||||
'name': i18n.t('Keyword'), // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||
'apiName': 'Keyword',
|
||||
'model_type': this.TREE,
|
||||
'badges': {
|
||||
'icon': true
|
||||
},
|
||||
'create': {
|
||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||
'params': [['name', 'description', 'icon']],
|
||||
|
@ -384,6 +384,10 @@ export const CardMixin = {
|
||||
let idx = undefined
|
||||
target = this.findCard(obj.id, card_list)
|
||||
|
||||
if (target) {
|
||||
idx = card_list.indexOf(card_list.find(x => x.id === target.id))
|
||||
Vue.set(card_list, idx, obj)
|
||||
}
|
||||
if (target?.parent) {
|
||||
let parent = this.findCard(target.parent, card_list)
|
||||
if (parent) {
|
||||
@ -392,9 +396,6 @@ export const CardMixin = {
|
||||
Vue.set(parent.children, idx, obj)
|
||||
}
|
||||
}
|
||||
} else if (target) {
|
||||
idx = card_list.indexOf(card_list.find(x => x.id === target.id))
|
||||
Vue.set(card_list, idx, obj)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user