vikunja-frontend/src/store/modules/tasks.js

318 lines
No EOL
9.3 KiB
JavaScript

import router from '@/router'
import TaskService from '@/services/task'
import TaskAssigneeService from '@/services/taskAssignee'
import TaskAssigneeModel from '../../models/taskAssignee'
import LabelTaskModel from '../../models/labelTask'
import LabelTaskService from '@/services/labelTask'
import {HAS_TASKS} from '../mutation-types'
import {setLoading} from '../helper'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {parseTaskText} from '@/modules/parseTaskText'
import TaskModel from '@/models/task'
import {formatISO} from 'date-fns'
import LabelTask from '@/models/labelTask'
import LabelModel from '@/models/label'
import UserService from '@/services/user'
// IDEA: maybe use a small fuzzy search here to prevent errors
function findPropertyByValue(object, key, value) {
return Object.values(object).find(
(l) => l[key]?.toLowerCase() === value.toLowerCase(),
)
}
// Check if the user exists
function validateUsername(users, username) {
return findPropertyByValue(users, 'username', username)
}
// Check if the label exists
function validateLabel(labels, label) {
return findPropertyByValue(labels, 'title', label)
}
function addLabelToTask(task, label) {
const labelTask = new LabelTask({
taskId: task.id,
labelId: label.id,
})
const labelTaskService = new LabelTaskService()
return labelTaskService.create(labelTask)
.then(result => {
task.labels.push(label)
return result
})
}
async function findAssignees(parsedTaskAssignees) {
if (parsedTaskAssignees.length <= 0) {
return []
}
const userService = new UserService()
const assignees = parsedTaskAssignees.map(async a => {
const users = await userService.getAll({}, {s: a})
return validateUsername(users, a)
})
const validatedUsers = await Promise.all(assignees)
return validatedUsers.filter((item) => Boolean(item))
}
export default {
namespaced: true,
state: () => ({}),
actions: {
loadTasks(ctx, params) {
const taskService = new TaskService()
const cancel = setLoading(ctx, 'tasks')
return taskService.getAll({}, params)
.then(r => {
ctx.commit(HAS_TASKS, r.length > 0, {root: true})
return r
})
.finally(() => {
cancel()
})
},
update(ctx, task) {
const cancel = setLoading(ctx, 'tasks')
const taskService = new TaskService()
return taskService.update(task)
.then(t => {
ctx.commit('kanban/setTaskInBucket', t, {root: true})
return t
})
.finally(() => {
cancel()
})
},
delete(ctx, task) {
const taskService = new TaskService()
return taskService.delete(task)
.then(t => {
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
return t
})
},
// Adds a task attachment in store.
// This is an action to be able to commit other mutations
addTaskAttachment(ctx, {taskId, attachment}) {
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task !== null) {
const attachments = [
...t.task.attachments,
attachment,
]
const newTask = {
...t,
task: {
...t.task,
attachments,
},
}
ctx.commit('kanban/setTaskInBucketByIndex', newTask, {root: true})
}
ctx.commit('attachments/add', attachment, {root: true})
},
addAssignee(ctx, {user, taskId}) {
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
const taskAssigneeService = new TaskAssigneeService()
return taskAssigneeService.create(taskAssignee)
.then(r => {
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// 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.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not add assignee to task in kanban, task not found', t)
return r
}
// FIXME: direct store manipulation (task)
t.task.assignees.push(user)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
},
removeAssignee(ctx, {user, taskId}) {
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
const taskAssigneeService = new TaskAssigneeService()
return taskAssigneeService.delete(taskAssignee)
.then(r => {
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// 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.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not remove assignee from task in kanban, task not found', t)
return r
}
for (const a in t.task.assignees) {
if (t.task.assignees[a].id === user.id) {
// FIXME: direct store manipulation (task)
t.task.assignees.splice(a, 1)
break
}
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
},
addLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.create(labelTask)
.then(r => {
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// 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.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not add label to task in kanban, task not found', t)
return r
}
// FIXME: direct store manipulation (task)
t.task.labels.push(label)
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
},
removeLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
return labelTaskService.delete(labelTask)
.then(r => {
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
if (t.task === null) {
// 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.
// Vuex seems to have its difficulties with that, so we just log the error and fail silently.
console.debug('Could not remove label from task in kanban, task not found', t)
return r
}
// Remove the label from the list
for (const l in t.task.labels) {
if (t.task.labels[l].id === label.id) {
// FIXME: direct store manipulation (task)
t.task.labels.splice(l, 1)
break
}
}
ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
return r
})
},
// Do everything that is involved in finding, creating and adding the label to the task
async addLabelsToTask({rootState, dispatch}, { task, parsedLabels }) {
if (parsedLabels.length <= 0) {
return task
}
const {labels} = rootState.labels
const labelAddsToWaitFor = parsedLabels.map(async labelTitle => {
let label = validateLabel(labels, labelTitle)
if (typeof label !== 'undefined') {
return label
}
// label not found, create it
const labelModel = new LabelModel({title: labelTitle})
await dispatch('labels/createLabel', labelModel)
addLabelToTask(task, label)
})
// This waits until all labels are created and added to the task
await Promise.all(labelAddsToWaitFor)
return task
},
findListId({ rootGetters }, { list, listId }) {
let foundListId = null
// Uses the following ways to get the list id of the new task:
// 1. If specified in quick add magic, look in store if it exists and use it if it does
if (list !== null) {
const list = rootGetters['lists/findListByExactname'](list)
foundListId = list === null ? null : list.id
}
// 2. Else check if a list was passed as parameter
if (listId !== 0) {
foundListId = listId
}
// 3. Otherwise use the id from the route parameter
if (typeof router.currentRoute.value.params.listId !== 'undefined') {
foundListId = parseInt(router.currentRoute.value.params.listId)
}
// 4. If none of the above worked, reject the promise with an error.
if (typeof foundListId === 'undefined' || listId === null) {
throw new Error('NO_LIST')
}
return foundListId
},
async createNewTask({dispatch}, {
title,
bucketId,
listId,
position,
}) {
const parsedTask = parseTaskText(title, getQuickAddMagicMode())
const foundListId = await dispatch('findListId', {
list: parsedTask.list,
listId: listId || 0,
})
const assignees = await findAssignees(parsedTask.assignees)
// I don't know why, but it all goes up in flames when I just pass in the date normally.
const dueDate = parsedTask.date !== null ? formatISO(parsedTask.date) : null
const task = new TaskModel({
title: parsedTask.text,
listId: foundListId,
dueDate,
priority: parsedTask.priority,
assignees,
bucketId: bucketId || 0,
position,
})
const taskService = new TaskService()
return taskService.create(task)
.then(task => dispatch('addLabelsToTask', {
task,
parsedLabels:parsedTask.labels,
}))
},
},
}