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 { .kanban {
display: flex;
align-items: flex-start;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
height: calc(#{$crazy-height-calculation}); height: calc(#{$crazy-height-calculation});
@ -28,6 +26,11 @@ $filter-container-height: '1rem - #{$switch-view-height}';
@media screen and (max-width: $tablet) { @media screen and (max-width: $tablet) {
height: calc(#{$crazy-height-calculation} - #{$filter-container-height}); height: calc(#{$crazy-height-calculation} - #{$filter-container-height});
} }
&-bucket-container {
display: flex;
align-items: flex-start;
}
.bucket { .bucket {
background-color: $bucket-background; background-color: $bucket-background;

View File

@ -16,148 +16,164 @@
v-model="params" v-model="params"
/> />
</div> </div>
<div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container"> <div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban kanban-bucket-container loader-container">
<div <draggable
:key="`bucket${bucket.id}`" v-model="buckets"
class="bucket" @start="() => drag = true"
:class="{'is-collapsed': collapsedBuckets[bucket.id]}" @end="updateBucketPosition"
v-for="(bucket, k) in buckets" group="buckets"
v-bind="dragOptions"
:disabled="!canWrite"
> >
<div class="bucket-header" @click="() => unCollapseBucket(bucket)"> <transition-group type="transition" :name="!drag ? 'move-bucket': null" tag="div" class="kanban-bucket-container">
<span <div
v-if="bucket.isDoneBucket" :key="`bucket${bucket.id}`"
class="icon is-small has-text-success mr-2" class="bucket"
v-tooltip="$t('list.kanban.doneBucketHint')" :class="{'is-collapsed': collapsedBuckets[bucket.id]}"
v-for="(bucket, k) in buckets"
> >
<icon icon="check-double"/> <div class="bucket-header" @click="() => unCollapseBucket(bucket)">
</span> <span
<h2 v-if="bucket.isDoneBucket"
:ref="`bucket${bucket.id}title`" class="icon is-small has-text-success mr-2"
@focusout="() => saveBucketTitle(bucket.id)" v-tooltip="$t('list.kanban.doneBucketHint')"
@keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)" >
class="title input" <icon icon="check-double"/>
:contenteditable="canWrite && !collapsedBuckets[bucket.id]" </span>
spellcheck="false">{{ bucket.title }}</h2> <h2
<span :ref="`bucket${bucket.id}title`"
:class="{'is-max': bucket.tasks.length >= bucket.limit}" @focusout="() => saveBucketTitle(bucket.id)"
class="limit" @keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)"
v-if="bucket.limit > 0"> class="title input"
{{ bucket.tasks.length }}/{{ bucket.limit }} :contenteditable="canWrite && !collapsedBuckets[bucket.id]"
</span> spellcheck="false">{{ bucket.title }}</h2>
<dropdown <span
class="is-right options" :class="{'is-max': bucket.tasks.length >= bucket.limit}"
v-if="canWrite && !collapsedBuckets[bucket.id]" class="limit"
trigger-icon="ellipsis-v" v-if="bucket.limit > 0">
@close="() => showSetLimitInput = false" {{ bucket.tasks.length }}/{{ bucket.limit }}
> </span>
<a <dropdown
@click.stop="showSetLimitInput = true" class="is-right options"
class="dropdown-item" v-if="canWrite && !collapsedBuckets[bucket.id]"
> trigger-icon="ellipsis-v"
<div class="field has-addons" v-if="showSetLimitInput"> @close="() => showSetLimitInput = false"
<div class="control"> >
<input <a
@change="() => setBucketLimit(bucket)" @click.stop="showSetLimitInput = true"
@keyup.enter="() => setBucketLimit(bucket)" class="dropdown-item"
@keyup.esc="() => showSetLimitInput = false" >
class="input" <div class="field has-addons" v-if="showSetLimitInput">
type="number" <div class="control">
min="0" <input
v-focus.always @change="() => setBucketLimit(bucket)"
v-model="bucket.limit" @keyup.enter="() => setBucketLimit(bucket)"
/> @keyup.esc="() => showSetLimitInput = false"
</div> class="input"
<div class="control"> type="number"
<x-button min="0"
:disabled="bucket.limit < 0" v-focus.always
:icon="['far', 'save']" v-model="bucket.limit"
:shadow="false" />
/> </div>
</div> <div class="control">
</div> <x-button
<template v-else> :disabled="bucket.limit < 0"
{{ :icon="['far', 'save']"
$t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')}) :shadow="false"
}} />
</template> </div>
</a> </div>
<a <template v-else>
@click.stop="toggleDoneBucket(bucket)" {{
class="dropdown-item" $t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit')})
v-tooltip="$t('list.kanban.doneBucketHintExtended')" }}
> </template>
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}"><icon </a>
icon="check-double"/></span> <a
{{ $t('list.kanban.doneBucket') }} @click.stop="toggleDoneBucket(bucket)"
</a> class="dropdown-item"
<a v-tooltip="$t('list.kanban.doneBucketHintExtended')"
class="dropdown-item" >
@click.stop="() => collapseBucket(bucket)" <span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}">
> <icon icon="check-double"/>
{{ $t('list.kanban.collapse') }} </span>
</a> {{ $t('list.kanban.doneBucket') }}
<a </a>
:class="{'is-disabled': buckets.length <= 1}" <a
@click.stop="() => deleteBucketModal(bucket.id)" class="dropdown-item"
class="dropdown-item has-text-danger" @click.stop="() => collapseBucket(bucket)"
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''" >
> {{ $t('list.kanban.collapse') }}
<span class="icon is-small"><icon icon="trash-alt"/></span> </a>
{{ $t('misc.delete') }} <a
</a> :class="{'is-disabled': buckets.length <= 1}"
</dropdown> @click.stop="() => deleteBucketModal(bucket.id)"
</div> class="dropdown-item has-text-danger"
<div :ref="`tasks-container${bucket.id}`" class="tasks"> v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
<draggable >
v-model="bucket.tasks" <span class="icon is-small">
@start="() => drag = true" <icon icon="trash-alt"/>
@end="updateTaskPosition" </span>
:group="{name: 'buckets', put: shouldAcceptDrop(bucket)}" {{ $t('misc.delete') }}
v-bind="dragOptions" </a>
:disabled="!canWrite" </dropdown>
:data-bucket-index="k" </div>
class="dropper" <div :ref="`tasks-container${bucket.id}`" class="tasks">
> <draggable
<transition-group type="transition" :name="!drag ? 'move-card': null" tag="div"> v-model="bucket.tasks"
<kanban-card @start="() => drag = true"
:key="`bucket${bucket.id}-task${task.id}`" @end="updateTaskPosition"
v-for="task in bucket.tasks" :group="{name: 'tasks', put: shouldAcceptDrop(bucket)}"
:task="task" v-bind="dragOptions"
/> :disabled="!canWrite"
</transition-group> :data-bucket-index="k"
</draggable> class="dropper"
</div> >
<div class="bucket-footer" v-if="canWrite"> <transition-group type="transition" :name="!drag ? 'move-card': null" tag="div">
<div class="field" v-if="showNewTaskInput[bucket.id]"> <kanban-card
<div class="control" :class="{'is-loading': loading}"> :key="`bucket${bucket.id}-task${task.id}`"
<input v-for="task in bucket.tasks"
class="input" :task="task"
:disabled="loading" />
@focusout="toggleShowNewTaskInput(bucket.id)" </transition-group>
@keyup.enter="addTaskToBucket(bucket.id)" </draggable>
@keyup.esc="toggleShowNewTaskInput(bucket.id)" </div>
:placeholder="$t('list.kanban.addTaskPlaceholder')" <div class="bucket-footer" v-if="canWrite">
type="text" <div class="field" v-if="showNewTaskInput[bucket.id]">
v-focus.always <div class="control" :class="{'is-loading': loading}">
v-model="newTaskText" <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> </div>
<p class="help is-danger" v-if="newTaskError[bucket.id] && newTaskText === ''">
{{ $t('list.list.addTitleRequired') }}
</p>
</div> </div>
<x-button </transition-group>
@click="toggleShowNewTaskInput(bucket.id)" </draggable>
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>
<div class="bucket new-bucket" v-if="canWrite && !loading && buckets.length > 0"> <div class="bucket new-bucket" v-if="canWrite && !loading && buckets.length > 0">
<input <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 // 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. // of the drop target works all the time.
const bucketIndex = parseInt(e.to.parentNode.dataset.bucketIndex) const bucketIndex = parseInt(e.to.parentNode.dataset.bucketIndex)
const newBucket = this.buckets[bucketIndex] const newBucket = this.buckets[bucketIndex]
const task = newBucket.tasks[e.newIndex] const task = newBucket.tasks[e.newIndex]
const taskBefore = newBucket.tasks[e.newIndex - 1] ?? null const taskBefore = newBucket.tasks[e.newIndex - 1] ?? null
@ -465,6 +481,9 @@ export default {
this.error(e) this.error(e)
}) })
}, },
updateBucketPosition(e) {
console.log('drop bucket', e)
},
setBucketLimit(bucket) { setBucketLimit(bucket) {
if (bucket.limit < 0) { if (bucket.limit < 0) {
return return