feat: migrate kanban store to pina (#2411)

Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2411
This commit is contained in:
konrad 2022-10-01 12:15:22 +00:00
commit d1d7cd535e
7 changed files with 277 additions and 261 deletions

View file

@ -13,7 +13,6 @@ import {
MENU_ACTIVE, MENU_ACTIVE,
QUICK_ACTIONS_ACTIVE, QUICK_ACTIONS_ACTIVE,
} from './mutation-types' } from './mutation-types'
import kanban from './modules/kanban'
import ListModel from '@/models/list' import ListModel from '@/models/list'
@ -33,9 +32,6 @@ export function useStore () {
export const store = createStore<RootStoreState>({ export const store = createStore<RootStoreState>({
strict: import.meta.env.DEV, strict: import.meta.env.DEV,
modules: {
kanban,
},
state: () => ({ state: () => ({
loading: false, loading: false,
loadingModule: null, loadingModule: null,

View file

@ -68,13 +68,16 @@ export interface ConfigState {
export interface KanbanState { export interface KanbanState {
buckets: IBucket[], buckets: IBucket[],
listId: IList['id'], listId: IList['id'],
bucketLoading: {}, bucketLoading: {
[id: IBucket['id']]: boolean
},
taskPagesPerBucket: { taskPagesPerBucket: {
[id: IBucket['id']]: number [id: IBucket['id']]: number
}, },
allTasksLoadedForBucket: { allTasksLoadedForBucket: {
[id: IBucket['id']]: boolean [id: IBucket['id']]: boolean
}, },
isLoading: boolean,
} }
export interface LabelState { export interface LabelState {

View file

@ -1,14 +1,14 @@
import type { Module } from 'vuex' import {defineStore, acceptHMRUpdate} from 'pinia'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import {findById, findIndexById} from '@/helpers/utils' import {findById, findIndexById} from '@/helpers/utils'
import {i18n} from '@/i18n' import {i18n} from '@/i18n'
import {success} from '@/message' import {success} from '@/message'
import BucketService from '../../services/bucket' import BucketService from '../services/bucket'
import {setLoading} from '../helper' import {setLoadingPinia} from '@/store/helper'
import TaskCollectionService from '@/services/taskCollection' import TaskCollectionService from '@/services/taskCollection'
import type { RootStoreState, KanbanState } from '@/store/types' import type { KanbanState } from '@/store/types'
import type { ITask } from '@/modelTypes/ITask' import type { ITask } from '@/modelTypes/ITask'
import type { IList } from '@/modelTypes/IList' import type { IList } from '@/modelTypes/IList'
import type { IBucket } from '@/modelTypes/IBucket' import type { IBucket } from '@/modelTypes/IBucket'
@ -41,188 +41,16 @@ const addTaskToBucketAndSort = (state: KanbanState, task: ITask) => {
* This store is intended to hold the currently active kanban view. * This store is intended to hold the currently active kanban view.
* It should hold only the current buckets. * It should hold only the current buckets.
*/ */
const kanbanStore : Module<KanbanState, RootStoreState> = { export const useKanbanStore = defineStore('kanban', {
namespaced: true, state: () : KanbanState => ({
state: () => ({
buckets: [], buckets: [],
listId: 0, listId: 0,
bucketLoading: {}, bucketLoading: {},
taskPagesPerBucket: {}, taskPagesPerBucket: {},
allTasksLoadedForBucket: {}, allTasksLoadedForBucket: {},
isLoading: false,
}), }),
mutations: {
setListId(state, listId: IList['id']) {
state.listId = parseInt(listId)
},
setBuckets(state, buckets: IBucket[]) {
state.buckets = buckets
buckets.forEach(b => {
state.taskPagesPerBucket[b.id] = 1
state.allTasksLoadedForBucket[b.id] = false
})
},
addBucket(state, bucket: IBucket) {
state.buckets.push(bucket)
},
removeBucket(state, bucket: IBucket) {
const bucketIndex = findIndexById(state.buckets, bucket.id)
state.buckets.splice(bucketIndex, 1)
},
setBucketById(state, bucket: IBucket) {
const bucketIndex = findIndexById(state.buckets, bucket.id)
state.buckets[bucketIndex] = bucket
},
setBucketByIndex(state, {
bucketIndex,
bucket,
} : {
bucketIndex: number,
bucket: IBucket
}) {
state.buckets[bucketIndex] = bucket
},
setTaskInBucketByIndex(state, {
bucketIndex,
taskIndex,
task,
} : {
bucketIndex: number,
taskIndex: number,
task: ITask
}) {
const bucket = state.buckets[bucketIndex]
bucket.tasks[taskIndex] = task
state.buckets[bucketIndex] = bucket
},
setTasksInBucketByBucketId(state, {
bucketId,
tasks,
} : {
bucketId: IBucket['id'],
tasks: ITask[],
}) {
const bucketIndex = findIndexById(state.buckets, bucketId)
state.buckets[bucketIndex] = {
...state.buckets[bucketIndex],
tasks,
}
},
setTaskInBucket(state, task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (state.buckets.length === 0) {
return
}
let found = false
const findAndUpdate = b => {
for (const t in state.buckets[b].tasks) {
if (state.buckets[b].tasks[t].id === task.id) {
const bucket = state.buckets[b]
bucket.tasks[t] = task
if (bucket.id !== task.bucketId) {
bucket.tasks.splice(t, 1)
addTaskToBucketAndSort(state, task)
}
state.buckets[b] = bucket
found = true
return
}
}
}
for (const b in state.buckets) {
if (state.buckets[b].id === task.bucketId) {
findAndUpdate(b)
if (found) {
return
}
}
}
for (const b in state.buckets) {
findAndUpdate(b)
if (found) {
return
}
}
},
addTaskToBucket(state, task: ITask) {
const bucketIndex = findIndexById(state.buckets, task.bucketId)
const oldBucket = state.buckets[bucketIndex]
const newBucket = {
...oldBucket,
tasks: [
...oldBucket.tasks,
task,
],
}
state.buckets[bucketIndex] = newBucket
},
addTasksToBucket(state, {tasks, bucketId}: {
tasks: ITask[];
bucketId: IBucket['id'];
}) {
const bucketIndex = findIndexById(state.buckets, bucketId)
const oldBucket = state.buckets[bucketIndex]
const newBucket = {
...oldBucket,
tasks: [
...oldBucket.tasks,
...tasks,
],
}
state.buckets[bucketIndex] = newBucket
},
removeTaskInBucket(state, task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (state.buckets.length === 0) {
return
}
const { bucketIndex, taskIndex } = getTaskIndicesById(state, task.id)
if (
!bucketIndex ||
state.buckets[bucketIndex]?.id !== task.bucketId ||
!taskIndex ||
(state.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id)
) {
return
}
state.buckets[bucketIndex].tasks.splice(taskIndex, 1)
},
setBucketLoading(state, {bucketId, loading}) {
state.bucketLoading[bucketId] = loading
},
setTasksLoadedForBucketPage(state: KanbanState, {bucketId, page}) {
state.taskPagesPerBucket[bucketId] = page
},
setAllTasksLoadedForBucket(state: KanbanState, bucketId) {
state.allTasksLoadedForBucket[bucketId] = true
},
},
getters: { getters: {
getBucketById(state) { getBucketById(state) {
return (bucketId: IBucket['id']) => findById(state.buckets, bucketId) return (bucketId: IBucket['id']) => findById(state.buckets, bucketId)
@ -243,40 +71,216 @@ const kanbanStore : Module<KanbanState, RootStoreState> = {
}, },
actions: { actions: {
async loadBucketsForList(ctx, {listId, params}) { setIsLoading(isLoading: boolean) {
const cancel = setLoading(ctx, 'kanban') this.isLoading = isLoading
},
setListId(listId: IList['id']) {
this.listId = Number(listId)
},
setBuckets(buckets: IBucket[]) {
this.buckets = buckets
buckets.forEach(b => {
this.taskPagesPerBucket[b.id] = 1
this.allTasksLoadedForBucket[b.id] = false
})
},
addBucket(bucket: IBucket) {
this.buckets.push(bucket)
},
removeBucket(bucket: IBucket) {
const bucketIndex = findIndexById(this.buckets, bucket.id)
this.buckets.splice(bucketIndex, 1)
},
setBucketById(bucket: IBucket) {
const bucketIndex = findIndexById(this.buckets, bucket.id)
this.buckets[bucketIndex] = bucket
},
setBucketByIndex({
bucketIndex,
bucket,
} : {
bucketIndex: number,
bucket: IBucket
}) {
this.buckets[bucketIndex] = bucket
},
setTaskInBucketByIndex({
bucketIndex,
taskIndex,
task,
} : {
bucketIndex: number,
taskIndex: number,
task: ITask
}) {
const bucket = this.buckets[bucketIndex]
bucket.tasks[taskIndex] = task
this.buckets[bucketIndex] = bucket
},
setTasksInBucketByBucketId({
bucketId,
tasks,
} : {
bucketId: IBucket['id'],
tasks: ITask[],
}) {
const bucketIndex = findIndexById(this.buckets, bucketId)
this.buckets[bucketIndex] = {
...this.buckets[bucketIndex],
tasks,
}
},
setTaskInBucket(task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (this.buckets.length === 0) {
return
}
let found = false
const findAndUpdate = b => {
for (const t in this.buckets[b].tasks) {
if (this.buckets[b].tasks[t].id === task.id) {
const bucket = this.buckets[b]
bucket.tasks[t] = task
if (bucket.id !== task.bucketId) {
bucket.tasks.splice(t, 1)
addTaskToBucketAndSort(this, task)
}
this.buckets[b] = bucket
found = true
return
}
}
}
for (const b in this.buckets) {
if (this.buckets[b].id === task.bucketId) {
findAndUpdate(b)
if (found) {
return
}
}
}
for (const b in this.buckets) {
findAndUpdate(b)
if (found) {
return
}
}
},
addTaskToBucket(task: ITask) {
const bucketIndex = findIndexById(this.buckets, task.bucketId)
const oldBucket = this.buckets[bucketIndex]
const newBucket = {
...oldBucket,
tasks: [
...oldBucket.tasks,
task,
],
}
this.buckets[bucketIndex] = newBucket
},
addTasksToBucket({tasks, bucketId}: {
tasks: ITask[];
bucketId: IBucket['id'];
}) {
const bucketIndex = findIndexById(this.buckets, bucketId)
const oldBucket = this.buckets[bucketIndex]
const newBucket = {
...oldBucket,
tasks: [
...oldBucket.tasks,
...tasks,
],
}
this.buckets[bucketIndex] = newBucket
},
removeTaskInBucket(task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (this.buckets.length === 0) {
return
}
const { bucketIndex, taskIndex } = getTaskIndicesById(this, task.id)
if (
!bucketIndex ||
this.buckets[bucketIndex]?.id !== task.bucketId ||
!taskIndex ||
(this.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id)
) {
return
}
this.buckets[bucketIndex].tasks.splice(taskIndex, 1)
},
setBucketLoading({bucketId, loading}: {bucketId: IBucket['id'], loading: boolean}) {
this.bucketLoading[bucketId] = loading
},
setTasksLoadedForBucketPage({bucketId, page}: {bucketId: IBucket['id'], page: number}) {
this.taskPagesPerBucket[bucketId] = page
},
setAllTasksLoadedForBucket(bucketId: IBucket['id']) {
this.allTasksLoadedForBucket[bucketId] = true
},
async loadBucketsForList({listId, params}: {listId: IList['id'], params}) {
const cancel = setLoadingPinia(this)
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments // Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
ctx.commit('setBuckets', []) this.setBuckets([])
params.per_page = TASKS_PER_BUCKET params.per_page = TASKS_PER_BUCKET
const bucketService = new BucketService() const bucketService = new BucketService()
try { try {
const response = await bucketService.getAll({listId}, params) const buckets = await bucketService.getAll({listId}, params)
ctx.commit('setBuckets', response) this.setBuckets(buckets)
ctx.commit('setListId', listId) this.setListId(listId)
return response return buckets
} finally { } finally {
cancel() cancel()
} }
}, },
async loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) { async loadNextTasksForBucket(
const isLoading = ctx.state.bucketLoading[bucketId] ?? false {listId, ps = {}, bucketId} :
{listId: IList['id'], ps, bucketId: IBucket['id']},
) {
const isLoading = this.bucketLoading[bucketId] ?? false
if (isLoading) { if (isLoading) {
return return
} }
const page = (ctx.state.taskPagesPerBucket[bucketId] ?? 1) + 1 const page = (this.taskPagesPerBucket[bucketId] ?? 1) + 1
const alreadyLoaded = ctx.state.allTasksLoadedForBucket[bucketId] ?? false const alreadyLoaded = this.allTasksLoadedForBucket[bucketId] ?? false
if (alreadyLoaded) { if (alreadyLoaded) {
return return
} }
const cancel = setLoading(ctx, 'kanban') const cancel = setLoadingPinia(this)
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: true}) this.setBucketLoading({bucketId: bucketId, loading: true})
const params = JSON.parse(JSON.stringify(ps)) const params = JSON.parse(JSON.stringify(ps))
@ -305,67 +309,67 @@ const kanbanStore : Module<KanbanState, RootStoreState> = {
const taskService = new TaskCollectionService() const taskService = new TaskCollectionService()
try { try {
const tasks = await taskService.getAll({listId}, params, page) const tasks = await taskService.getAll({listId}, params, page)
ctx.commit('addTasksToBucket', {tasks, bucketId: bucketId}) this.addTasksToBucket({tasks, bucketId: bucketId})
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page}) this.setTasksLoadedForBucketPage({bucketId, page})
if (taskService.totalPages <= page) { if (taskService.totalPages <= page) {
ctx.commit('setAllTasksLoadedForBucket', bucketId) this.setAllTasksLoadedForBucket(bucketId)
} }
return tasks return tasks
} finally { } finally {
cancel() cancel()
ctx.commit('setBucketLoading', {bucketId, loading: false}) this.setBucketLoading({bucketId, loading: false})
} }
}, },
async createBucket(ctx, bucket: IBucket) { async createBucket(bucket: IBucket) {
const cancel = setLoading(ctx, 'kanban') const cancel = setLoadingPinia(this)
const bucketService = new BucketService() const bucketService = new BucketService()
try { try {
const createdBucket = await bucketService.create(bucket) const createdBucket = await bucketService.create(bucket)
ctx.commit('addBucket', createdBucket) this.addBucket(createdBucket)
return createdBucket return createdBucket
} finally { } finally {
cancel() cancel()
} }
}, },
async deleteBucket(ctx, {bucket, params}) { async deleteBucket({bucket, params}: {bucket: IBucket, params}) {
const cancel = setLoading(ctx, 'kanban') const cancel = setLoadingPinia(this)
const bucketService = new BucketService() const bucketService = new BucketService()
try { try {
const response = await bucketService.delete(bucket) const response = await bucketService.delete(bucket)
ctx.commit('removeBucket', bucket) this.removeBucket(bucket)
// We reload all buckets because tasks are being moved from the deleted bucket // We reload all buckets because tasks are being moved from the deleted bucket
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params}) this.loadBucketsForList({listId: bucket.listId, params})
return response return response
} finally { } finally {
cancel() cancel()
} }
}, },
async updateBucket(ctx, updatedBucketData) { async updateBucket(updatedBucketData: IBucket) {
const cancel = setLoading(ctx, 'kanban') const cancel = setLoadingPinia(this)
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id) const bucketIndex = findIndexById(this.buckets, updatedBucketData.id)
const oldBucket = cloneDeep(ctx.state.buckets[bucketIndex]) const oldBucket = cloneDeep(this.buckets[bucketIndex])
const updatedBucket = { const updatedBucket = {
...oldBucket, ...oldBucket,
...updatedBucketData, ...updatedBucketData,
} }
ctx.commit('setBucketByIndex', {bucketIndex, bucket: updatedBucket}) this.setBucketByIndex({bucketIndex, bucket: updatedBucket})
const bucketService = new BucketService() const bucketService = new BucketService()
try { try {
const returnedBucket = await bucketService.update(updatedBucket) const returnedBucket = await bucketService.update(updatedBucket)
ctx.commit('setBucketByIndex', {bucketIndex, bucket: returnedBucket}) this.setBucketByIndex({bucketIndex, bucket: returnedBucket})
return returnedBucket return returnedBucket
} catch(e) { } catch(e) {
// restore original state // restore original state
ctx.commit('setBucketByIndex', {bucketIndex, bucket: oldBucket}) this.setBucketByIndex({bucketIndex, bucket: oldBucket})
throw e throw e
} finally { } finally {
@ -373,23 +377,21 @@ const kanbanStore : Module<KanbanState, RootStoreState> = {
} }
}, },
async updateBucketTitle(ctx, { id, title }) { async updateBucketTitle({ id, title }: { id: IBucket['id'], title: IBucket['title'] }) {
const bucket = findById(ctx.state.buckets, id) const bucket = findById(this.buckets, id)
if (bucket?.title === title) { if (bucket?.title === title) {
// bucket title has not changed // bucket title has not changed
return return
} }
const updatedBucketData = { await this.updateBucket({ id, title })
id,
title,
}
await ctx.dispatch('updateBucket', updatedBucketData)
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')}) success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
}, },
}, },
} })
export default kanbanStore // support hot reloading
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useKanbanStore, import.meta.hot))
}

View file

@ -28,6 +28,7 @@ import type {TaskState} from '@/store/types'
import {useLabelStore} from '@/stores/labels' import {useLabelStore} from '@/stores/labels'
import {useListStore} from '@/stores/lists' import {useListStore} from '@/stores/lists'
import {useAttachmentStore} from '@/stores/attachments' import {useAttachmentStore} from '@/stores/attachments'
import {useKanbanStore} from '@/stores/kanban'
import {playPop} from '@/helpers/playPop' import {playPop} from '@/helpers/playPop'
import {store} from '@/store' import {store} from '@/store'
@ -101,7 +102,7 @@ export const useTaskStore = defineStore('task', {
const taskService = new TaskService() const taskService = new TaskService()
try { try {
const updatedTask = await taskService.update(task) const updatedTask = await taskService.update(task)
store.commit('kanban/setTaskInBucket', updatedTask) useKanbanStore().setTaskInBucket(updatedTask)
if (task.done) { if (task.done) {
playPop() playPop()
} }
@ -114,7 +115,7 @@ export const useTaskStore = defineStore('task', {
async delete(task: ITask) { async delete(task: ITask) {
const taskService = new TaskService() const taskService = new TaskService()
const response = await taskService.delete(task) const response = await taskService.delete(task)
store.commit('kanban/removeTaskInBucket', task) useKanbanStore().removeTaskInBucket(task)
return response return response
}, },
@ -127,7 +128,8 @@ export const useTaskStore = defineStore('task', {
taskId: ITask['id'] taskId: ITask['id']
attachment: IAttachment attachment: IAttachment
}) { }) {
const t = store.getters['kanban/getTaskById'](taskId) const kanbanStore = useKanbanStore()
const t = kanbanStore.getTaskById(taskId)
if (t.task !== null) { if (t.task !== null) {
const attachments = [ const attachments = [
...t.task.attachments, ...t.task.attachments,
@ -141,7 +143,7 @@ export const useTaskStore = defineStore('task', {
attachments, attachments,
}, },
} }
store.commit('kanban/setTaskInBucketByIndex', newTask) kanbanStore.setTaskInBucketByIndex(newTask)
} }
const attachmentStore = useAttachmentStore() const attachmentStore = useAttachmentStore()
attachmentStore.add(attachment) attachmentStore.add(attachment)
@ -154,12 +156,13 @@ export const useTaskStore = defineStore('task', {
user: IUser, user: IUser,
taskId: ITask['id'] taskId: ITask['id']
}) { }) {
const kanbanStore = useKanbanStore()
const taskAssigneeService = new TaskAssigneeService() const taskAssigneeService = new TaskAssigneeService()
const r = await taskAssigneeService.create(new TaskAssigneeModel({ const r = await taskAssigneeService.create(new TaskAssigneeModel({
userId: user.id, userId: user.id,
taskId: taskId, taskId: taskId,
})) }))
const t = store.getters['kanban/getTaskById'](taskId) const t = kanbanStore.getTaskById(taskId)
if (t.task === null) { if (t.task === null) {
// Don't try further adding a label if the task is not in kanban // Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now. // Usually this means the kanban board hasn't been accessed until now.
@ -168,7 +171,7 @@ export const useTaskStore = defineStore('task', {
return r return r
} }
store.commit('kanban/setTaskInBucketByIndex', { kanbanStore.setTaskInBucketByIndex({
...t, ...t,
task: { task: {
...t.task, ...t.task,
@ -188,12 +191,13 @@ export const useTaskStore = defineStore('task', {
user: IUser, user: IUser,
taskId: ITask['id'] taskId: ITask['id']
}) { }) {
const kanbanStore = useKanbanStore()
const taskAssigneeService = new TaskAssigneeService() const taskAssigneeService = new TaskAssigneeService()
const response = await taskAssigneeService.delete(new TaskAssigneeModel({ const response = await taskAssigneeService.delete(new TaskAssigneeModel({
userId: user.id, userId: user.id,
taskId: taskId, taskId: taskId,
})) }))
const t = store.getters['kanban/getTaskById'](taskId) const t = kanbanStore.getTaskById(taskId)
if (t.task === null) { if (t.task === null) {
// Don't try further adding a label if the task is not in kanban // Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now. // Usually this means the kanban board hasn't been accessed until now.
@ -204,7 +208,7 @@ export const useTaskStore = defineStore('task', {
const assignees = t.task.assignees.filter(({ id }) => id !== user.id) const assignees = t.task.assignees.filter(({ id }) => id !== user.id)
store.commit('kanban/setTaskInBucketByIndex', { kanbanStore.setTaskInBucketByIndex({
...t, ...t,
task: { task: {
...t.task, ...t.task,
@ -222,12 +226,13 @@ export const useTaskStore = defineStore('task', {
label: ILabel, label: ILabel,
taskId: ITask['id'] taskId: ITask['id']
}) { }) {
const kanbanStore = useKanbanStore()
const labelTaskService = new LabelTaskService() const labelTaskService = new LabelTaskService()
const r = await labelTaskService.create(new LabelTaskModel({ const r = await labelTaskService.create(new LabelTaskModel({
taskId, taskId,
labelId: label.id, labelId: label.id,
})) }))
const t = store.getters['kanban/getTaskById'](taskId) const t = kanbanStore.getTaskById(taskId)
if (t.task === null) { if (t.task === null) {
// Don't try further adding a label if the task is not in kanban // Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now. // Usually this means the kanban board hasn't been accessed until now.
@ -236,7 +241,7 @@ export const useTaskStore = defineStore('task', {
return r return r
} }
store.commit('kanban/setTaskInBucketByIndex', { kanbanStore.setTaskInBucketByIndex({
...t, ...t,
task: { task: {
...t.task, ...t.task,
@ -254,12 +259,13 @@ export const useTaskStore = defineStore('task', {
{label, taskId}: {label, taskId}:
{label: ILabel, taskId: ITask['id']}, {label: ILabel, taskId: ITask['id']},
) { ) {
const kanbanStore = useKanbanStore()
const labelTaskService = new LabelTaskService() const labelTaskService = new LabelTaskService()
const response = await labelTaskService.delete(new LabelTaskModel({ const response = await labelTaskService.delete(new LabelTaskModel({
taskId, labelId: taskId, labelId:
label.id, label.id,
})) }))
const t = store.getters['kanban/getTaskById'](taskId) const t = kanbanStore.getTaskById(taskId)
if (t.task === null) { if (t.task === null) {
// Don't try further adding a label if the task is not in kanban // Don't try further adding a label if the task is not in kanban
// Usually this means the kanban board hasn't been accessed until now. // Usually this means the kanban board hasn't been accessed until now.
@ -271,7 +277,7 @@ export const useTaskStore = defineStore('task', {
// Remove the label from the list // Remove the label from the list
const labels = t.task.labels.filter(({ id }) => id !== label.id) const labels = t.task.labels.filter(({ id }) => id !== label.id)
store.commit('kanban/setTaskInBucketByIndex', { kanbanStore.setTaskInBucketByIndex({
...t, ...t,
task: { task: {
...t.task, ...t.task,

View file

@ -229,9 +229,9 @@ import draggable from 'zhyswan-vuedraggable'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import BucketModel from '../../models/bucket' import BucketModel from '../../models/bucket'
import {mapState} from 'vuex' import {mapState as mapStateVuex} from 'vuex'
import {mapState} from 'pinia'
import {RIGHTS as Rights} from '@/constants/rights' import {RIGHTS as Rights} from '@/constants/rights'
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
import ListWrapper from './ListWrapper.vue' import ListWrapper from './ListWrapper.vue'
import FilterPopup from '@/components/list/partials/filter-popup.vue' import FilterPopup from '@/components/list/partials/filter-popup.vue'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
@ -241,6 +241,7 @@ import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import {isSavedFilter} from '@/helpers/savedFilter' import {isSavedFilter} from '@/helpers/savedFilter'
import {useTaskStore} from '@/stores/tasks' import {useTaskStore} from '@/stores/tasks'
import {useKanbanStore} from '@/stores/kanban'
const DRAG_OPTIONS = { const DRAG_OPTIONS = {
// sortable options // sortable options
@ -342,16 +343,18 @@ export default defineComponent({
], ],
} }
}, },
buckets() { ...mapStateVuex({
return this.$store.state.kanban.buckets
},
...mapState({
loadedListId: state => state.kanban.listId,
loading: state => state[LOADING] && state[LOADING_MODULE] === 'kanban',
taskLoading: state => state[LOADING] && state[LOADING_MODULE] === 'tasks',
canWrite: state => state.currentList.maxRight > Rights.READ, canWrite: state => state.currentList.maxRight > Rights.READ,
list: state => state.currentList, list: state => state.currentList,
}), }),
...mapState(useKanbanStore, {
buckets: state => state.buckets,
loadedListId: state => state.listId,
loading: state => state.isLoading,
}),
...mapState(useTaskStore, {
taskLoading: state => state.isLoading,
}),
}, },
methods: { methods: {
@ -364,7 +367,7 @@ export default defineComponent({
console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $attrs = ${this.$attrs} $route.params =`, this.$route.params) console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $attrs = ${this.$attrs} $route.params =`, this.$route.params)
this.$store.dispatch('kanban/loadBucketsForList', {listId, params}) useKanbanStore().loadBucketsForList({listId, params})
}, },
setTaskContainerRef(id, el) { setTaskContainerRef(id, el) {
@ -382,7 +385,7 @@ export default defineComponent({
return return
} }
this.$store.dispatch('kanban/loadNextTasksForBucket', { useKanbanStore().loadNextTasksForBucket({
listId: listId, listId: listId,
params: this.params, params: this.params,
bucketId: id, bucketId: id,
@ -390,12 +393,13 @@ export default defineComponent({
}, },
updateTasks(bucketId, tasks) { updateTasks(bucketId, tasks) {
const kanbanStore = useKanbanStore()
const newBucket = { const newBucket = {
...this.$store.getters['kanban/getBucketById'](bucketId), ...kanbanStore.getBucketById(bucketId),
tasks, tasks,
} }
this.$store.commit('kanban/setBucketById', newBucket) kanbanStore.setBucketById(newBucket)
}, },
async updateTaskPosition(e) { async updateTaskPosition(e) {
@ -472,7 +476,7 @@ export default defineComponent({
listId: this.listId, listId: this.listId,
}) })
this.newTaskText = '' this.newTaskText = ''
this.$store.commit('kanban/addTaskToBucket', task) useKanbanStore().addTaskToBucket(task)
this.scrollTaskContainerToBottom(bucketId) this.scrollTaskContainerToBottom(bucketId)
}, },
@ -494,7 +498,7 @@ export default defineComponent({
listId: this.listId, listId: this.listId,
}) })
await this.$store.dispatch('kanban/createBucket', newBucket) await useKanbanStore().createBucket(newBucket)
this.newBucketTitle = '' this.newBucketTitle = ''
this.showNewBucketInput = false this.showNewBucketInput = false
}, },
@ -515,7 +519,7 @@ export default defineComponent({
}) })
try { try {
await this.$store.dispatch('kanban/deleteBucket', { await useKanbanStore().deleteBucket({
bucket, bucket,
params: this.params, params: this.params,
}) })
@ -537,13 +541,13 @@ export default defineComponent({
title: bucketTitle, title: bucketTitle,
} }
await this.$store.dispatch('kanban/updateBucketTitle', updatedBucketData) await useKanbanStore().updateBucketTitle(updatedBucketData)
this.bucketTitleEditable = false this.bucketTitleEditable = false
}, },
updateBuckets(value) { updateBuckets(value) {
// (1) buckets get updated in store and tasks positions get invalidated // (1) buckets get updated in store and tasks positions get invalidated
this.$store.commit('kanban/setBuckets', value) useKanbanStore().setBuckets(value)
}, },
updateBucketPosition(e) { updateBucketPosition(e) {
@ -562,7 +566,7 @@ export default defineComponent({
), ),
} }
this.$store.dispatch('kanban/updateBucket', updatedData) useKanbanStore().updateBucket(updatedData)
}, },
async setBucketLimit(bucketId, limit) { async setBucketLimit(bucketId, limit) {
@ -570,12 +574,14 @@ export default defineComponent({
return return
} }
const kanbanStore = useKanbanStore()
const newBucket = { const newBucket = {
...this.$store.getters['kanban/getBucketById'](bucketId), ...kanbanStore.getBucketById(bucketId),
limit, limit,
} }
await this.$store.dispatch('kanban/updateBucket', newBucket) await kanbanStore.updateBucket(newBucket)
this.$message.success({message: this.$t('list.kanban.bucketLimitSavedSuccess')}) this.$message.success({message: this.$t('list.kanban.bucketLimitSavedSuccess')})
}, },
@ -600,7 +606,7 @@ export default defineComponent({
...bucket, ...bucket,
isDoneBucket: !bucket.isDoneBucket, isDoneBucket: !bucket.isDoneBucket,
} }
await this.$store.dispatch('kanban/updateBucket', newBucket) await useKanbanStore().updateBucket(newBucket)
this.$message.success({message: this.$t('list.kanban.doneBucketSavedSuccess')}) this.$message.success({message: this.$t('list.kanban.doneBucketSavedSuccess')})
}, },

View file

@ -62,6 +62,7 @@ import {saveListToHistory} from '@/modules/listHistory'
import {useTitle} from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import {useStore} from '@/store' import {useStore} from '@/store'
import {useListStore} from '@/stores/lists' import {useListStore} from '@/stores/lists'
import {useKanbanStore} from '@/stores/kanban'
const props = defineProps({ const props = defineProps({
listId: { listId: {
@ -77,6 +78,7 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
const store = useStore() const store = useStore()
const kanbanStore = useKanbanStore()
const listStore = useListStore() const listStore = useListStore()
const listService = ref(new ListService()) const listService = ref(new ListService())
const loadedListId = ref(0) const loadedListId = ref(0)
@ -116,7 +118,7 @@ async function loadList(listIdToLoad: number) {
props.viewName === 'list.list' || props.viewName === 'list.list' ||
props.viewName === 'list.gantt' props.viewName === 'list.gantt'
) { ) {
store.commit('kanban/setListId', 0) kanbanStore.setListId(0)
} }
// Don't load the list if we either already loaded it or aren't dealing with a list at all currently and // Don't load the list if we either already loaded it or aren't dealing with a list at all currently and

View file

@ -465,6 +465,7 @@ import {colorIsDark} from '@/helpers/color/colorIsDark'
import {useNamespaceStore} from '@/stores/namespaces' import {useNamespaceStore} from '@/stores/namespaces'
import {useAttachmentStore} from '@/stores/attachments' import {useAttachmentStore} from '@/stores/attachments'
import {useTaskStore} from '@/stores/tasks' import {useTaskStore} from '@/stores/tasks'
import {useKanbanStore} from '@/stores/kanban'
function scrollIntoView(el) { function scrollIntoView(el) {
if (!el) { if (!el) {
@ -748,7 +749,7 @@ export default defineComponent({
}, },
async changeList(list: IList) { async changeList(list: IList) {
this.$store.commit('kanban/removeTaskInBucket', this.task) useKanbanStore().removeTaskInBucket(this.task)
await this.saveTask({ await this.saveTask({
task: { task: {
...this.task, ...this.task,