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 {
|
.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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in New Issue