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 4f7d2bacaa..01cf9e80ce 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 3ac560569b..5f6a2816c7 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 a874f0d879..16f4eebd1a 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 eec4688861..c754fb649e 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 fbdc4a97b5..8c79f592c0 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 8c2daa666c..cabb786155 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 79b0d3be10..2a78c4d412 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 1bccfec9b5..b5d6a55a3e 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 a0a1a42977..9be8f729d0 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 db0472c7ff..3f262eecbb 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 a23e3d8090..3c78f326c3 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 48269e2429..5c1878578a 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 022bce2aca..6b76960bde 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 333c234c9d..51830862b4 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 fc74327290..2bf2375c0d 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 a062ff930e..ae80da005e 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 685460d95c..d1e379c1aa 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 4f03c5b6cb..d04af92742 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 01cb0a39f6..9927c82bd5 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 02e56308d5..3ceb428ca4 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 e65b547cfc..74d67692d8 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 3f51ee39a4..7cdcda5928 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 7fea1d874a..64473dff4d 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 16e1db2f11..f246f8b497 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 381d6be783..8e85e022d9 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 }} +