basic swiping working
This commit is contained in:
parent
37c7a62853
commit
5a0ca3f4e5
@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div id="shopping_line_item">
|
||||
<div id="shopping_line_item" class="swipe-container" @touchend="handleSwipe()"
|
||||
v-if="(useUserPreferenceStore().device_settings.shopping_show_checked_entries || !is_checked) && (useUserPreferenceStore().device_settings.shopping_show_delayed_entries || !is_delayed)"
|
||||
>
|
||||
<div class="swipe-action" :class="{'bg-success': !is_checked , 'bg-warning': is_checked }">
|
||||
<i class="swipe-icon fa-fw fas" :class="{'fa-check': !is_checked , 'fa-cart-plus': is_checked }"></i>
|
||||
</div>
|
||||
|
||||
<b-button-group class="w-100" v-if="(useUserPreferenceStore().device_settings.shopping_show_checked_entries || !is_checked) && (useUserPreferenceStore().device_settings.shopping_show_delayed_entries || !is_delayed)">
|
||||
<b-button-group class="swipe-element">
|
||||
<b-button variant="primary" v-if="is_delayed">
|
||||
<i class="fa-fw fas fa-hourglass-half"></i>
|
||||
</b-button>
|
||||
@ -17,11 +22,14 @@
|
||||
|
||||
<span v-if="info_row"><small class="text-muted">{{ info_row }}</small></span>
|
||||
</div>
|
||||
<b-button variant="success" @click="useShoppingListStore().setEntriesCheckedState(entries, !is_checked)" :class="{'btn-success': !is_checked, 'btn-warning': is_checked}">
|
||||
<b-button variant="success" @click="useShoppingListStore().setEntriesCheckedState(entries, !is_checked)"
|
||||
:class="{'btn-success': !is_checked, 'btn-warning': is_checked}">
|
||||
<i class="fa-fw fas" :class="{'fa-check': !is_checked , 'fa-cart-plus': is_checked }"></i>
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
|
||||
<div class="swipe-action bg-primary justify-content-end">
|
||||
<i class="fa-fw fas fa-hourglass-half swipe-icon"></i>
|
||||
</div>
|
||||
|
||||
<b-modal v-model="detail_modal_visible" @hidden="detail_modal_visible = false">
|
||||
<template #modal-title>
|
||||
@ -45,21 +53,29 @@
|
||||
<!-- TODO implement -->
|
||||
<!-- <b-button variant="success" block @click="detail_modal_visible = false;"> {{ $t("Edit_Food") }}</b-button> -->
|
||||
|
||||
<b-button variant="info" block @click="detail_modal_visible = false;useShoppingListStore().delayEntries(entries)">{{ $t('Postpone') }}</b-button>
|
||||
<b-button variant="info" block
|
||||
@click="detail_modal_visible = false;useShoppingListStore().delayEntries(entries,!this.is_delayed, true)">
|
||||
{{ $t('Postpone') }}
|
||||
</b-button>
|
||||
|
||||
|
||||
<h6 class="mt-2">{{ $t('Entries') }}</h6>
|
||||
|
||||
<b-button variant="danger" block @click="detail_modal_visible = false;useShoppingListStore().deleteEntries(entries)">{{ $t('Delete_All') }}</b-button>
|
||||
<b-button variant="danger" block
|
||||
@click="detail_modal_visible = false;useShoppingListStore().deleteEntries(entries)">
|
||||
{{ $t('Delete_All') }}
|
||||
</b-button>
|
||||
|
||||
<b-row v-for="e in entries" v-bind:key="e.id">
|
||||
<b-col cold="12">
|
||||
<b-button-group class="mt-1 w-100">
|
||||
<b-button variant="dark" block class="btn btn-block text-left">
|
||||
<span><span v-if="e.amount > 0">{{e.amount}}</span> {{e.unit?.name}} {{ food.name }}</span>
|
||||
<span><span v-if="e.amount > 0">{{ e.amount }}</span> {{ e.unit?.name }} {{ food.name }}</span>
|
||||
<span><br/><small class="text-muted">
|
||||
<span v-if="e.recipe_mealplan && e.recipe_mealplan.recipe_name !== ''">
|
||||
<a :href="resolveDjangoUrl('view_recipe', e.recipe_mealplan.recipe)"> {{ e.recipe_mealplan.recipe_name }} </a>({{
|
||||
<a :href="resolveDjangoUrl('view_recipe', e.recipe_mealplan.recipe)"> {{
|
||||
e.recipe_mealplan.recipe_name
|
||||
}} </a>({{
|
||||
e.recipe_mealplan.servings
|
||||
}} {{ $t('Servings') }})<br/>
|
||||
</span>
|
||||
@ -72,10 +88,14 @@
|
||||
</small></span>
|
||||
|
||||
</b-button>
|
||||
<b-button variant="warning" @click="detail_modal_visible = false; useShoppingListStore().deleteObject(e)"><i class="fas fa-trash"></i></b-button> <!-- TODO implement -->
|
||||
<b-button variant="warning"
|
||||
@click="detail_modal_visible = false; useShoppingListStore().deleteObject(e)"><i
|
||||
class="fas fa-trash"></i></b-button> <!-- TODO implement -->
|
||||
</b-button-group>
|
||||
|
||||
<number-scaler-component :number="e.amount" @change="e.amount = $event; useShoppingListStore().updateObject(e)" v-if="e.recipe_mealplan === null"></number-scaler-component>
|
||||
<number-scaler-component :number="e.amount"
|
||||
@change="e.amount = $event; useShoppingListStore().updateObject(e)"
|
||||
v-if="e.recipe_mealplan === null"></number-scaler-component>
|
||||
|
||||
</b-col>
|
||||
</b-row>
|
||||
@ -86,7 +106,8 @@
|
||||
</template>
|
||||
</b-modal>
|
||||
|
||||
<generic-modal-form :model="Models.FOOD" :show="editing_food !== null" @hidden="editing_food = null; useShoppingListStore().refreshFromAPI()"></generic-modal-form>
|
||||
<generic-modal-form :model="Models.FOOD" :show="editing_food !== null"
|
||||
@hidden="editing_food = null; useShoppingListStore().refreshFromAPI()"></generic-modal-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -186,7 +207,7 @@ export default {
|
||||
if (e.recipe_mealplan !== null) {
|
||||
let recipe_name = e.recipe_mealplan.recipe_name
|
||||
if (recipes.indexOf(recipe_name) === -1) {
|
||||
recipes.push(recipe_name)
|
||||
recipes.push(recipe_name.substring(0, 14) + (recipe_name.length > 14 ? '..' : ''))
|
||||
}
|
||||
|
||||
if ('mealplan_from_date' in e.recipe_mealplan) {
|
||||
@ -196,14 +217,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recipes.length > 1) {
|
||||
let short_recipes = []
|
||||
recipes.forEach(r => {
|
||||
short_recipes.push(r.substring(0, 14) + (r.length > 14 ? '..' : ''))
|
||||
})
|
||||
recipes = short_recipes
|
||||
}
|
||||
}
|
||||
|
||||
if (useUserPreferenceStore().device_settings.shopping_item_info_created_by && authors.length > 0) {
|
||||
@ -236,9 +249,12 @@ export default {
|
||||
dateStyle: "short",
|
||||
}).format(Date.parse(datetime))
|
||||
},
|
||||
|
||||
/**
|
||||
* update the food after the category was changed
|
||||
* handle changing category to category ID as a workaround
|
||||
* @param food
|
||||
*/
|
||||
updateFoodCategory: function (food) {
|
||||
|
||||
if (typeof food.supermarket_category === "number") { // not the best solution, but as long as generic multiselect does not support caching, I don't want to use a proper model
|
||||
food.supermarket_category = this.useShoppingListStore().supermarket_categories.filter(sc => sc.id === food.supermarket_category)[0]
|
||||
}
|
||||
@ -249,14 +265,66 @@ export default {
|
||||
}).catch((err) => {
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* function triggered by touchend event of swipe container
|
||||
* check if min distance is reached and execute desired action
|
||||
*/
|
||||
handleSwipe: function () {
|
||||
const minDistance = 80;
|
||||
const container = document.querySelector('.swipe-container');
|
||||
// get the distance the user swiped
|
||||
const swipeDistance = container.scrollLeft - container.clientWidth;
|
||||
if (swipeDistance < minDistance * -1) {
|
||||
useShoppingListStore().setEntriesCheckedState(this.entries, !this.is_checked)
|
||||
} else if (swipeDistance > minDistance) {
|
||||
useShoppingListStore().delayEntries(this.entries, !this.is_delayed, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
/* scroll snap takes care of restoring scroll position */
|
||||
.swipe-container {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
|
||||
/* scrollbar should be hidden */
|
||||
.swipe-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* main element should always snap into view */
|
||||
.swipe-element {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.swipe-icon {
|
||||
color: white;
|
||||
position: sticky;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
/* swipe-actions and element should be 100% wide */
|
||||
.swipe-action,
|
||||
.swipe-element {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.swipe-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -291,27 +291,34 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
||||
* function to handle user checking or unchecking a set of entries
|
||||
* @param {{}} entries set of entries
|
||||
* @param checked boolean to set checked state of entry to
|
||||
* @param undo if the user should be able to undo the change or not
|
||||
*/
|
||||
setEntriesCheckedState(entries, checked) {
|
||||
this.registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries)
|
||||
setEntriesCheckedState(entries, checked, undo) {
|
||||
if (undo) {
|
||||
this.registerChange((checked ? 'CHECKED' : 'UNCHECKED'), entries)
|
||||
}
|
||||
|
||||
for (let i in entries) {
|
||||
this.entries[i].checked = checked
|
||||
this.updateObject(this.entries[i])
|
||||
}
|
||||
},
|
||||
/**
|
||||
* function to handle user "delaying" shopping entries
|
||||
* function to handle user "delaying" and "undelaying" shopping entries
|
||||
* @param {{}} entries set of entries
|
||||
* @param delay if entries should be delayed or if delay should be removed
|
||||
* @param undo if the user should be able to undo the change or not
|
||||
*/
|
||||
delayEntries(entries) {
|
||||
let delay = 4 //TODO get delay from settings in an offline friendly way
|
||||
let delay_date = new Date(Date.now() + delay * (60 * 60 * 1000))
|
||||
delayEntries(entries, delay, undo) {
|
||||
let delay_hours = 4 //TODO get delay from settings in an offline friendly way
|
||||
let delay_date = new Date(Date.now() + delay_hours * (60 * 60 * 1000))
|
||||
|
||||
this.registerChange('DELAY', entries)
|
||||
if (undo) {
|
||||
this.registerChange((delay ? 'DELAY' : 'UNDELAY'), entries)
|
||||
}
|
||||
|
||||
for (let i in entries) {
|
||||
console.log('DELAYING ', i, ' until ', delay_date)
|
||||
this.entries[i].delay_until = delay_date
|
||||
this.entries[i].delay_until = (delay ? delay_date : null)
|
||||
this.updateObject(this.entries[i])
|
||||
}
|
||||
},
|
||||
@ -332,7 +339,7 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
||||
* @param {{}} entries set of entries
|
||||
*/
|
||||
registerChange(type, entries) {
|
||||
if (!['CREATED', 'CHECKED', 'UNCHECKED', 'DELAY'].includes(type)) {
|
||||
if (!['CREATED', 'CHECKED', 'UNCHECKED', 'DELAY', 'UNDELAY'].includes(type)) {
|
||||
throw Error('Tried to register unknown change type')
|
||||
}
|
||||
this.undo_stack.push({'type': type, 'entries': entries})
|
||||
@ -346,18 +353,15 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
||||
let type = last_item['type']
|
||||
let entries = last_item['entries']
|
||||
|
||||
for (let i in entries) {
|
||||
let e = entries[i]
|
||||
if (type === 'CREATED') {
|
||||
if (type === 'CHECKED' || type === 'UNCHECKED') {
|
||||
this.setEntriesCheckedState(entries, (type === 'UNCHECKED'), false)
|
||||
} else if (type === 'DELAY' || type === 'UNDELAY') {
|
||||
this.delayEntries(entries, (type === 'UNDELAY'), false)
|
||||
} else if (type === 'CREATED') {
|
||||
for (let i in entries) {
|
||||
let e = entries[i]
|
||||
this.deleteObject(e)
|
||||
} else if (type === 'CHECKED' || type === 'UNCHECKED') {
|
||||
e.checked = (type === 'UNCHECKED')
|
||||
this.updateObject(e)
|
||||
} else if (type === 'DELAY') {
|
||||
e.delay_until = null
|
||||
this.updateObject(e)
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// can use localization in store
|
||||
|
Loading…
Reference in New Issue
Block a user