Add rudimentary reordering buckets
This commit is contained in:
parent
4be420ace6
commit
15ba57c3ce
@ -17,8 +17,6 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
|
||||
.kanban {
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
height: calc(#{$crazy-height-calculation});
|
||||
@ -28,6 +26,11 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
@media screen and (max-width: $tablet) {
|
||||
height: calc(#{$crazy-height-calculation} - #{$filter-container-height});
|
||||
}
|
||||
|
||||
&-bucket-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.bucket {
|
||||
background-color: $bucket-background;
|
||||
|
@ -16,148 +16,164 @@
|
||||
v-model="params"
|
||||
/>
|
||||
</div>
|
||||
<div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container">
|
||||
<div
|
||||
:key="`bucket${bucket.id}`"
|
||||
class="bucket"
|
||||
:class="{'is-collapsed': collapsedBuckets[bucket.id]}"
|
||||
v-for="(bucket, k) in buckets"
|
||||
<div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban kanban-bucket-container loader-container">
|
||||
<draggable
|
||||
v-model="buckets"
|
||||
@start="() => drag = true"
|
||||
@end="updateBucketPosition"
|
||||
group="buckets"
|
||||
v-bind="dragOptions"
|
||||
:disabled="!canWrite"
|
||||
>
|
||||
<div class="bucket-header" @click="() => unCollapseBucket(bucket)">
|
||||
<span
|
||||
v-if="bucket.isDoneBucket"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
v-tooltip="$t('list.kanban.doneBucketHint')"
|
||||
<transition-group type="transition" :name="!drag ? 'move-bucket': null" tag="div" class="kanban-bucket-container">
|
||||
<div
|
||||
:key="`bucket${bucket.id}`"
|
||||
class="bucket"
|
||||
:class="{'is-collapsed': collapsedBuckets[bucket.id]}"
|
||||
v-for="(bucket, k) in buckets"
|
||||
>
|
||||
<icon icon="check-double"/>
|
||||
</span>
|
||||
<h2
|
||||
:ref="`bucket${bucket.id}title`"
|
||||
@focusout="() => saveBucketTitle(bucket.id)"
|
||||
@keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)"
|
||||
class="title input"
|
||||
:contenteditable="canWrite && !collapsedBuckets[bucket.id]"
|
||||
spellcheck="false">{{ bucket.title }}</h2>
|
||||
<span
|
||||
:class="{'is-max': bucket.tasks.length >= bucket.limit}"
|
||||
class="limit"
|
||||
v-if="bucket.limit > 0">
|
||||
{{ bucket.tasks.length }}/{{ bucket.limit }}
|
||||
</span>
|
||||
<dropdown
|
||||
class="is-right options"
|
||||
v-if="canWrite && !collapsedBuckets[bucket.id]"
|
||||
trigger-icon="ellipsis-v"
|
||||
@close="() => showSetLimitInput = false"
|
||||
>
|
||||
<a
|
||||
@click.stop="showSetLimitInput = true"
|
||||
class="dropdown-item"
|
||||
>
|
||||
<div class="field has-addons" v-if="showSetLimitInput">
|
||||
<div class="control">
|
||||
<input
|
||||
@change="() => setBucketLimit(bucket)"
|
||||
@keyup.enter="() => setBucketLimit(bucket)"
|
||||
@keyup.esc="() => showSetLimitInput = false"
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
v-focus.always
|
||||
v-model="bucket.limit"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:disabled="bucket.limit < 0"
|
||||
:icon="['far', 'save']"
|
||||
:shadow="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
{{
|
||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||
}}
|
||||
</template>
|
||||
</a>
|
||||
<a
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
class="dropdown-item"
|
||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||
>
|
||||
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}"><icon
|
||||
icon="check-double"/></span>
|
||||
{{ $t('list.kanban.doneBucket') }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click.stop="() => collapseBucket(bucket)"
|
||||
>
|
||||
{{ $t('list.kanban.collapse') }}
|
||||
</a>
|
||||
<a
|
||||
:class="{'is-disabled': buckets.length <= 1}"
|
||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||
class="dropdown-item has-text-danger"
|
||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||
>
|
||||
<span class="icon is-small"><icon icon="trash-alt"/></span>
|
||||
{{ $t('misc.delete') }}
|
||||
</a>
|
||||
</dropdown>
|
||||
</div>
|
||||
<div :ref="`tasks-container${bucket.id}`" class="tasks">
|
||||
<draggable
|
||||
v-model="bucket.tasks"
|
||||
@start="() => drag = true"
|
||||
@end="updateTaskPosition"
|
||||
:group="{name: 'buckets', put: shouldAcceptDrop(bucket)}"
|
||||
v-bind="dragOptions"
|
||||
:disabled="!canWrite"
|
||||
:data-bucket-index="k"
|
||||
class="dropper"
|
||||
>
|
||||
<transition-group type="transition" :name="!drag ? 'move-card': null" tag="div">
|
||||
<kanban-card
|
||||
:key="`bucket${bucket.id}-task${task.id}`"
|
||||
v-for="task in bucket.tasks"
|
||||
:task="task"
|
||||
/>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="bucket-footer" v-if="canWrite">
|
||||
<div class="field" v-if="showNewTaskInput[bucket.id]">
|
||||
<div class="control" :class="{'is-loading': loading}">
|
||||
<input
|
||||
class="input"
|
||||
:disabled="loading"
|
||||
@focusout="toggleShowNewTaskInput(bucket.id)"
|
||||
@keyup.enter="addTaskToBucket(bucket.id)"
|
||||
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
||||
:placeholder="$t('list.kanban.addTaskPlaceholder')"
|
||||
type="text"
|
||||
v-focus.always
|
||||
v-model="newTaskText"
|
||||
/>
|
||||
<div class="bucket-header" @click="() => unCollapseBucket(bucket)">
|
||||
<span
|
||||
v-if="bucket.isDoneBucket"
|
||||
class="icon is-small has-text-success mr-2"
|
||||
v-tooltip="$t('list.kanban.doneBucketHint')"
|
||||
>
|
||||
<icon icon="check-double"/>
|
||||
</span>
|
||||
<h2
|
||||
:ref="`bucket${bucket.id}title`"
|
||||
@focusout="() => saveBucketTitle(bucket.id)"
|
||||
@keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)"
|
||||
class="title input"
|
||||
:contenteditable="canWrite && !collapsedBuckets[bucket.id]"
|
||||
spellcheck="false">{{ bucket.title }}</h2>
|
||||
<span
|
||||
:class="{'is-max': bucket.tasks.length >= bucket.limit}"
|
||||
class="limit"
|
||||
v-if="bucket.limit > 0">
|
||||
{{ bucket.tasks.length }}/{{ bucket.limit }}
|
||||
</span>
|
||||
<dropdown
|
||||
class="is-right options"
|
||||
v-if="canWrite && !collapsedBuckets[bucket.id]"
|
||||
trigger-icon="ellipsis-v"
|
||||
@close="() => showSetLimitInput = false"
|
||||
>
|
||||
<a
|
||||
@click.stop="showSetLimitInput = true"
|
||||
class="dropdown-item"
|
||||
>
|
||||
<div class="field has-addons" v-if="showSetLimitInput">
|
||||
<div class="control">
|
||||
<input
|
||||
@change="() => setBucketLimit(bucket)"
|
||||
@keyup.enter="() => setBucketLimit(bucket)"
|
||||
@keyup.esc="() => showSetLimitInput = false"
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
v-focus.always
|
||||
v-model="bucket.limit"
|
||||
/>
|
||||
</div>
|
||||
<div class="control">
|
||||
<x-button
|
||||
:disabled="bucket.limit < 0"
|
||||
:icon="['far', 'save']"
|
||||
:shadow="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
{{
|
||||
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
|
||||
}}
|
||||
</template>
|
||||
</a>
|
||||
<a
|
||||
@click.stop="toggleDoneBucket(bucket)"
|
||||
class="dropdown-item"
|
||||
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
|
||||
>
|
||||
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
|
||||
<icon icon="check-double"/>
|
||||
</span>
|
||||
{{ $t('list.kanban.doneBucket') }}
|
||||
</a>
|
||||
<a
|
||||
class="dropdown-item"
|
||||
@click.stop="() => collapseBucket(bucket)"
|
||||
>
|
||||
{{ $t('list.kanban.collapse') }}
|
||||
</a>
|
||||
<a
|
||||
:class="{'is-disabled': buckets.length <= 1}"
|
||||
@click.stop="() => deleteBucketModal(bucket.id)"
|
||||
class="dropdown-item has-text-danger"
|
||||
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<icon icon="trash-alt"/>
|
||||
</span>
|
||||
{{ $t('misc.delete') }}
|
||||
</a>
|
||||
</dropdown>
|
||||
</div>
|
||||
<div :ref="`tasks-container${bucket.id}`" class="tasks">
|
||||
<draggable
|
||||
v-model="bucket.tasks"
|
||||
@start="() => drag = true"
|
||||
@end="updateTaskPosition"
|
||||
:group="{name: 'tasks', put: shouldAcceptDrop(bucket)}"
|
||||
v-bind="dragOptions"
|
||||
:disabled="!canWrite"
|
||||
:data-bucket-index="k"
|
||||
class="dropper"
|
||||
>
|
||||
<transition-group type="transition" :name="!drag ? 'move-card': null" tag="div">
|
||||
<kanban-card
|
||||
:key="`bucket${bucket.id}-task${task.id}`"
|
||||
v-for="task in bucket.tasks"
|
||||
:task="task"
|
||||
/>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="bucket-footer" v-if="canWrite">
|
||||
<div class="field" v-if="showNewTaskInput[bucket.id]">
|
||||
<div class="control" :class="{'is-loading': loading}">
|
||||
<input
|
||||
class="input"
|
||||
:disabled="loading"
|
||||
@focusout="toggleShowNewTaskInput(bucket.id)"
|
||||
@keyup.enter="addTaskToBucket(bucket.id)"
|
||||
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
|
||||
:placeholder="$t('list.kanban.addTaskPlaceholder')"
|
||||
type="text"
|
||||
v-focus.always
|
||||
v-model="newTaskText"
|
||||
/>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="newTaskError[bucket.id] && newTaskText === ''">
|
||||
{{ $t('list.list.addTitleRequired') }}
|
||||
</p>
|
||||
</div>
|
||||
<x-button
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
class="is-transparent is-fullwidth has-text-centered"
|
||||
:shadow="false"
|
||||
v-if="!showNewTaskInput[bucket.id]"
|
||||
icon="plus"
|
||||
type="secondary"
|
||||
>
|
||||
{{
|
||||
bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask')
|
||||
}}
|
||||
</x-button>
|
||||
</div>
|
||||
<p class="help is-danger" v-if="newTaskError[bucket.id] && newTaskText === ''">
|
||||
{{ $t('list.list.addTitleRequired') }}
|
||||
</p>
|
||||
</div>
|
||||
<x-button
|
||||
@click="toggleShowNewTaskInput(bucket.id)"
|
||||
class="is-transparent is-fullwidth has-text-centered"
|
||||
:shadow="false"
|
||||
v-if="!showNewTaskInput[bucket.id]"
|
||||
icon="plus"
|
||||
type="secondary"
|
||||
>
|
||||
{{ bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask') }}
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
|
||||
<div class="bucket new-bucket" v-if="canWrite && !loading && buckets.length > 0">
|
||||
<input
|
||||
@ -340,7 +356,7 @@ export default {
|
||||
// new bucket id when a task has been moved between buckets, only the new bucket. Using the data-bucket-id
|
||||
// of the drop target works all the time.
|
||||
const bucketIndex = parseInt(e.to.parentNode.dataset.bucketIndex)
|
||||
|
||||
|
||||
const newBucket = this.buckets[bucketIndex]
|
||||
const task = newBucket.tasks[e.newIndex]
|
||||
const taskBefore = newBucket.tasks[e.newIndex - 1] ?? null
|
||||
@ -465,6 +481,9 @@ export default {
|
||||
this.error(e)
|
||||
})
|
||||
},
|
||||
updateBucketPosition(e) {
|
||||
console.log('drop bucket', e)
|
||||
},
|
||||
setBucketLimit(bucket) {
|
||||
if (bucket.limit < 0) {
|
||||
return
|
||||
|
Reference in New Issue
Block a user