From cac8b0926332379e5c2c831c114e4bb493e16817 Mon Sep 17 00:00:00 2001 From: konrad Date: Fri, 4 Sep 2020 20:01:02 +0000 Subject: [PATCH] Add limits for kanban boards (#234) Prevent dropping a task onto a bucket which has its limit reached Fix closing the dropdown Add notice to show the limit Add input to change kanban bucket limit Add menu item to save bucket limit Fix parsing dates from the api Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/234 --- src/helpers/colorIsDark.js | 15 +++++---- src/main.js | 2 ++ src/models/bucket.js | 3 +- src/models/task.js | 2 +- src/services/attachment.js | 2 +- src/services/bucket.js | 9 +++++- src/services/label.js | 4 +-- src/services/linkShare.js | 4 +-- src/services/list.js | 4 +-- src/services/listUsers.js | 4 +-- src/services/namespace.js | 4 +-- src/services/task.js | 4 +-- src/services/taskAssignee.js | 2 +- src/services/taskCollection.js | 4 +-- src/services/taskComment.js | 4 +-- src/services/taskRelation.js | 2 +- src/services/team.js | 4 +-- src/services/teamList.js | 4 +-- src/services/teamMember.js | 4 +-- src/services/teamNamespace.js | 4 +-- src/services/user.js | 4 +-- src/services/userList.js | 4 +-- src/services/userNamespace.js | 4 +-- src/styles/components/kanban.scss | 17 ++++++++++ src/views/list/views/Kanban.vue | 53 ++++++++++++++++++++++++++++++- 25 files changed, 124 insertions(+), 43 deletions(-) diff --git a/src/helpers/colorIsDark.js b/src/helpers/colorIsDark.js index 4f7d2bac..01cf9e80 100644 --- a/src/helpers/colorIsDark.js +++ b/src/helpers/colorIsDark.js @@ -1,15 +1,18 @@ - export const colorIsDark = color => { if (color === '#' || color === '') { return true // Defaults to dark } - let rgb = parseInt(color.substring(1, 7), 16); // convert rrggbb to decimal - let r = (rgb >> 16) & 0xff; // extract red - let g = (rgb >> 8) & 0xff; // extract green - let b = (rgb >> 0) & 0xff; // extract blue + if (color.substring(0, 1) !== '#') { + color = '#' + color + } + + let rgb = parseInt(color.substring(1, 7), 16) // convert rrggbb to decimal + let r = (rgb >> 16) & 0xff // extract red + let g = (rgb >> 8) & 0xff // extract green + let b = (rgb >> 0) & 0xff // extract blue // luma will be a value 0..255 where 0 indicates the darkest, and 255 the brightest - let luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709 + let luma = 0.2126 * r + 0.7152 * g + 0.0722 * b // per ITU-R BT.709 return luma > 128 } \ No newline at end of file diff --git a/src/main.js b/src/main.js index 3ac56056..5f6a2816 100644 --- a/src/main.js +++ b/src/main.js @@ -70,6 +70,7 @@ import { faFilter } from '@fortawesome/free-solid-svg-icons' import { faFillDrip } from '@fortawesome/free-solid-svg-icons' import { faKeyboard } from '@fortawesome/free-solid-svg-icons' import { faComments } from '@fortawesome/free-regular-svg-icons' +import { faSave } from '@fortawesome/free-regular-svg-icons' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' library.add(faSignOutAlt) @@ -117,6 +118,7 @@ library.add(faEllipsisV) library.add(faFilter) library.add(faFillDrip) library.add(faKeyboard) +library.add(faSave) Vue.component('icon', FontAwesomeIcon) diff --git a/src/models/bucket.js b/src/models/bucket.js index a874f0d8..16f4eebd 100644 --- a/src/models/bucket.js +++ b/src/models/bucket.js @@ -1,6 +1,6 @@ import AbstractModel from './abstractModel' import UserModel from './user' -import TaskModel from "./task"; +import TaskModel from './task' export default class BucketModel extends AbstractModel { constructor(bucket) { @@ -18,6 +18,7 @@ export default class BucketModel extends AbstractModel { id: 0, title: '', listId: 0, + limit: 0, tasks: [], createdBy: null, diff --git a/src/models/task.js b/src/models/task.js index eec46888..c754fb64 100644 --- a/src/models/task.js +++ b/src/models/task.js @@ -75,7 +75,7 @@ export default class TaskModel extends AbstractModel { defaults() { return { id: 0, - text: '', + title: '', description: '', done: false, priority: 0, diff --git a/src/services/attachment.js b/src/services/attachment.js index fbdc4a97..8c79f592 100644 --- a/src/services/attachment.js +++ b/src/services/attachment.js @@ -12,7 +12,7 @@ export default class AttachmentService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) + model.created = formatISO(new Date(model.created)) return model } diff --git a/src/services/bucket.js b/src/services/bucket.js index 8c2daa66..cabb7861 100644 --- a/src/services/bucket.js +++ b/src/services/bucket.js @@ -1,5 +1,6 @@ import AbstractService from './abstractService' -import BucketModel from "../models/bucket"; +import BucketModel from '../models/bucket' +import TaskService from '@/services/task' export default class BucketService extends AbstractService { constructor() { @@ -14,4 +15,10 @@ export default class BucketService extends AbstractService { modelFactory(data) { return new BucketModel(data) } + + beforeUpdate(model) { + const taskService = new TaskService() + model.tasks = model.tasks.map(t => taskService.processModel(t)) + return model + } } \ No newline at end of file diff --git a/src/services/label.js b/src/services/label.js index 79b0d3be..2a78c4d4 100644 --- a/src/services/label.js +++ b/src/services/label.js @@ -14,8 +14,8 @@ export default class LabelService extends AbstractService { } processModel(label) { - label.created = formatISO(label.created) - label.updated = formatISO(label.updated) + label.created = formatISO(new Date(label.created)) + label.updated = formatISO(new Date(label.updated)) label.hexColor = label.hexColor.substring(1, 7) return label } diff --git a/src/services/linkShare.js b/src/services/linkShare.js index 1bccfec9..b5d6a55a 100644 --- a/src/services/linkShare.js +++ b/src/services/linkShare.js @@ -13,8 +13,8 @@ export default class ListService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/list.js b/src/services/list.js index a0a1a429..9be8f729 100644 --- a/src/services/list.js +++ b/src/services/list.js @@ -15,8 +15,8 @@ export default class ListService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/listUsers.js b/src/services/listUsers.js index db0472c7..3f262eec 100644 --- a/src/services/listUsers.js +++ b/src/services/listUsers.js @@ -10,8 +10,8 @@ export default class ListUserService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/namespace.js b/src/services/namespace.js index a23e3d80..3c78f326 100644 --- a/src/services/namespace.js +++ b/src/services/namespace.js @@ -14,8 +14,8 @@ export default class NamespaceService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/task.js b/src/services/task.js index 48269e24..5c187857 100644 --- a/src/services/task.js +++ b/src/services/task.js @@ -37,8 +37,8 @@ export default class TaskService extends AbstractService { model.dueDate = !model.dueDate ? null : formatISO(new Date(model.dueDate)) model.startDate = !model.startDate ? null : formatISO(new Date(model.startDate)) model.endDate = !model.endDate ? null : formatISO(new Date(model.endDate)) - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) // remove all nulls, these would create empty reminders for (const index in model.reminderDates) { diff --git a/src/services/taskAssignee.js b/src/services/taskAssignee.js index 022bce2a..6b76960b 100644 --- a/src/services/taskAssignee.js +++ b/src/services/taskAssignee.js @@ -11,7 +11,7 @@ export default class TaskAssigneeService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) + model.created = formatISO(new Date(model.created)) return model } diff --git a/src/services/taskCollection.js b/src/services/taskCollection.js index 333c234c..51830862 100644 --- a/src/services/taskCollection.js +++ b/src/services/taskCollection.js @@ -10,8 +10,8 @@ export default class TaskCollectionService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/taskComment.js b/src/services/taskComment.js index fc743272..2bf2375c 100644 --- a/src/services/taskComment.js +++ b/src/services/taskComment.js @@ -14,8 +14,8 @@ export default class TaskCommentService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/taskRelation.js b/src/services/taskRelation.js index a062ff93..ae80da00 100644 --- a/src/services/taskRelation.js +++ b/src/services/taskRelation.js @@ -11,7 +11,7 @@ export default class TaskRelationService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) + model.created = formatISO(new Date(model.created)) return model } diff --git a/src/services/team.js b/src/services/team.js index 685460d9..d1e379c1 100644 --- a/src/services/team.js +++ b/src/services/team.js @@ -14,8 +14,8 @@ export default class TeamService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/teamList.js b/src/services/teamList.js index 4f03c5b6..d04af927 100644 --- a/src/services/teamList.js +++ b/src/services/teamList.js @@ -14,8 +14,8 @@ export default class TeamListService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/teamMember.js b/src/services/teamMember.js index 01cb0a39..9927c82b 100644 --- a/src/services/teamMember.js +++ b/src/services/teamMember.js @@ -12,8 +12,8 @@ export default class TeamMemberService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/teamNamespace.js b/src/services/teamNamespace.js index 02e56308..3ceb428c 100644 --- a/src/services/teamNamespace.js +++ b/src/services/teamNamespace.js @@ -14,8 +14,8 @@ export default class TeamNamespaceService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/user.js b/src/services/user.js index e65b547c..74d67692 100644 --- a/src/services/user.js +++ b/src/services/user.js @@ -10,8 +10,8 @@ export default class UserService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/userList.js b/src/services/userList.js index 3f51ee39..7cdcda59 100644 --- a/src/services/userList.js +++ b/src/services/userList.js @@ -14,8 +14,8 @@ export default class UserListService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/services/userNamespace.js b/src/services/userNamespace.js index 7fea1d87..64473dff 100644 --- a/src/services/userNamespace.js +++ b/src/services/userNamespace.js @@ -14,8 +14,8 @@ export default class UserNamespaceService extends AbstractService { } processModel(model) { - model.created = formatISO(model.created) - model.updated = formatISO(model.updated) + model.created = formatISO(new Date(model.created)) + model.updated = formatISO(new Date(model.updated)) return model } diff --git a/src/styles/components/kanban.scss b/src/styles/components/kanban.scss index 16e1db2f..f246f8b4 100644 --- a/src/styles/components/kanban.scss +++ b/src/styles/components/kanban.scss @@ -213,6 +213,14 @@ $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1em - 1.5em - 8px'; width: 100%; } } + + a.dropdown-item { + padding-right: 1rem; + + .input { + height: 2.25em; + } + } } .bucket-header { @@ -221,6 +229,15 @@ $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1em - 1.5em - 8px'; justify-content: space-between; padding: .5em; + .limit { + padding-left: .5rem; + font-weight: bold; + + &.is-max { + color: $red; + } + } + .dropdown-trigger { cursor: pointer; } diff --git a/src/views/list/views/Kanban.vue b/src/views/list/views/Kanban.vue index 381d6be7..8e85e022 100644 --- a/src/views/list/views/Kanban.vue +++ b/src/views/list/views/Kanban.vue @@ -9,6 +9,12 @@ @focusout="() => saveBucketTitle(bucket.id)" :ref="`bucket${bucket.id}title`" @keyup.ctrl.enter="() => saveBucketTitle(bucket.id)">{{ bucket.title }} + + {{ bucket.tasks.length }}/{{ bucket.limit }} +