From f7d8095b5a9eab55d76f14ab395b1b3dd959ef44 Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 10 Mar 2021 10:59:29 +0000 Subject: [PATCH] Pagingation for tasks in kanban buckets (#419) Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/419 Co-authored-by: konrad Co-committed-by: konrad --- src/message/index.js | 4 -- src/store/modules/kanban.js | 91 ++++++++++++++++++++++++++++++++- src/views/list/views/Kanban.vue | 23 ++++++++- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/message/index.js b/src/message/index.js index 2c8d8bc9..6f4569ad 100644 --- a/src/message/index.js +++ b/src/message/index.js @@ -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 }, } \ No newline at end of file diff --git a/src/store/modules/kanban.js b/src/store/modules/kanban.js index 97a67951..132a7b49 100644 --- a/src/store/modules/kanban.js +++ b/src/store/modules/kanban.js @@ -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 => { diff --git a/src/views/list/views/Kanban.vue b/src/views/list/views/Kanban.vue index a03f3e5c..248705bd 100644 --- a/src/views/list/views/Kanban.vue +++ b/src/views/list/views/Kanban.vue @@ -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) }) @@ -423,7 +442,7 @@ export default { task.done = !task.done this.$store.dispatch('tasks/update', task) .then(() => { - if(task.done) { + if (task.done) { playPop() } }) @@ -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) })