Add rudimentary reordering buckets

This commit is contained in:
kolaente 2021-07-28 18:29:09 +02:00
parent 4be420ace6
commit 15ba57c3ce
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
2 changed files with 163 additions and 141 deletions

View File

@ -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;

View File

@ -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