Pagingation for tasks in kanban buckets (#419)

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/419
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
konrad 2021-03-10 10:59:29 +00:00
parent 1f33477f57
commit f7d8095b5a
3 changed files with 110 additions and 8 deletions

View file

@ -22,8 +22,6 @@ export default {
text: err,
actions: actions,
})
context.loading = false
},
success(e, context, actions = []) {
// Build the notification text from error response
@ -41,7 +39,5 @@ export default {
actions: actions,
},
})
context.loading = false
},
}

View file

@ -1,8 +1,12 @@
import Vue from 'vue'
import {cloneDeep} from 'lodash'
import BucketService from '../../services/bucket'
import {filterObject} from '@/helpers/filterObject'
import {setLoading} from '../helper'
import TaskCollectionService from '@/services/taskCollection'
const tasksPerBucket = 25
/**
* This store is intended to hold the currently active kanban view.
@ -13,6 +17,9 @@ export default {
state: () => ({
buckets: [],
listId: 0,
bucketLoading: {},
taskPagesPerBucket: {},
allTasksLoadedForBucket: {},
}),
mutations: {
setListId(state, listId) {
@ -20,6 +27,10 @@ export default {
},
setBuckets(state, buckets) {
state.buckets = buckets
buckets.forEach(b => {
Vue.set(state.taskPagesPerBucket, b.id, 1)
Vue.set(state.allTasksLoadedForBucket, b.id, false)
})
},
addBucket(state, bucket) {
state.buckets.push(bucket)
@ -71,6 +82,13 @@ export default {
const bi = filterObject(state.buckets, b => b.id === task.bucketId)
state.buckets[bi].tasks.push(task)
},
addTasksToBucket(state, {tasks, bucketId}) {
const bi = filterObject(state.buckets, b => b.id === bucketId)
tasks.forEach(t => {
state.buckets[bi].tasks.push(t)
})
},
removeTaskInBucket(state, task) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (state.buckets.length === 0) {
@ -91,6 +109,15 @@ export default {
}
}
},
setBucketLoading(state, {bucketId, loading}) {
Vue.set(state.bucketLoading, bucketId, loading)
},
setTasksLoadedForBucketPage(state, {bucketId, page}) {
Vue.set(state.taskPagesPerBucket, bucketId, page)
},
setAllTasksLoadedForBucket(state, bucketId) {
Vue.set(state.allTasksLoadedForBucket, bucketId, true)
},
},
getters: {
getTaskById: state => id => {
@ -119,6 +146,8 @@ export default {
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
ctx.commit('setBuckets', [])
params.per_page = tasksPerBucket
const bucketService = new BucketService()
return bucketService.getAll({listId: listId}, params)
.then(r => {
@ -133,6 +162,64 @@ export default {
cancel()
})
},
loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
const isLoading = ctx.state.bucketLoading[bucketId] ?? false
if (isLoading) {
return Promise.resolve()
}
const page = (ctx.state.taskPagesPerBucket[bucketId] ?? 1) + 1
const alreadyLoaded = ctx.state.allTasksLoadedForBucket[bucketId] ?? false
if (alreadyLoaded) {
return Promise.resolve()
}
const cancel = setLoading(ctx, 'kanban')
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: true})
const params = cloneDeep(ps)
params.sort_by = 'position'
params.order_by = 'asc'
let hasBucketFilter = false
for (const f in params.filter_by) {
if (params.filter_by[f] === 'bucket_id') {
hasBucketFilter = true
if (params.filter_value[f] !== bucketId) {
params.filter_value[f] = bucketId
}
break
}
}
if (!hasBucketFilter) {
params.filter_by = [...(params.filter_by ?? []), 'bucket_id']
params.filter_value = [...(params.filter_value ?? []), bucketId]
params.filter_comparator = [...(params.filter_comparator ?? []), 'equals']
}
params.per_page = tasksPerBucket
const taskService = new TaskCollectionService()
return taskService.getAll({listId: listId}, params, page)
.then(r => {
ctx.commit('addTasksToBucket', {tasks: r, bucketId: bucketId})
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
if (taskService.totalPages <= page) {
ctx.commit('setAllTasksLoadedForBucket', bucketId)
}
return Promise.resolve(r)
})
.catch(e => {
return Promise.reject(e)
})
.finally(() => {
cancel()
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: false})
})
},
createBucket(ctx, bucket) {
const cancel = setLoading(ctx, 'kanban')
@ -149,7 +236,7 @@ export default {
cancel()
})
},
deleteBucket(ctx, bucket) {
deleteBucket(ctx, {bucket, params}) {
const cancel = setLoading(ctx, 'kanban')
const bucketService = new BucketService()
@ -157,7 +244,7 @@ export default {
.then(r => {
ctx.commit('removeBucket', bucket)
// We reload all buckets because tasks are being moved from the deleted bucket
ctx.dispatch('loadBucketsForList', {listId: bucket.listId})
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params: params})
return Promise.resolve(r)
})
.catch(e => {

View file

@ -352,7 +352,26 @@ export default {
console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $route.params =`, this.$route.params)
this.filtersChanged = false
const minScrollHeightPercent = 0.25
this.$store.dispatch('kanban/loadBucketsForList', {listId: this.$route.params.listId, params: this.params})
.then(bs => {
bs.forEach(b => {
const e = this.$refs[`tasks-container${b.id}`][0]
e.onscroll = () => {
if (e.scrollTopMax <= e.scrollTop + e.scrollTop * minScrollHeightPercent) {
this.$store.dispatch('kanban/loadNextTasksForBucket', {
listId: this.$route.params.listId,
params: this.params,
bucketId: b.id,
})
.catch(e => {
this.error(e, this)
})
}
}
})
})
.catch(e => {
this.error(e, this)
})
@ -518,7 +537,7 @@ export default {
listId: this.$route.params.listId,
})
this.$store.dispatch('kanban/deleteBucket', bucket)
this.$store.dispatch('kanban/deleteBucket', {bucket: bucket, params: this.params})
.then(() => {
this.success({message: 'The bucket has been deleted successfully.'}, this)
})