basic swiping working

This commit is contained in:
vabene1111 2024-01-14 11:54:10 +08:00
parent 37c7a62853
commit 5a0ca3f4e5
2 changed files with 116 additions and 44 deletions

View File

@ -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>

View File

@ -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