Add collapsing kanban buckets

This commit is contained in:
kolaente 2021-07-07 21:58:29 +02:00
parent 304ba797a0
commit ac6082a670
Signed by untrusted user: konrad
GPG Key ID: F40E70337AB24C9B
4 changed files with 81 additions and 8 deletions

View File

@ -0,0 +1,30 @@
const key = 'collapsedBuckets'
const getAllState = () => {
const saved = localStorage.getItem(key)
if (saved === null) {
return {}
}
return JSON.parse(saved)
}
export const saveCollapsedBucketState = (listId, collapsedBuckets) => {
const state = getAllState()
state[listId] = collapsedBuckets
for (const bucketId in state[listId]) {
if (!state[listId][bucketId]) {
delete state[listId][bucketId]
}
}
localStorage.setItem(key, JSON.stringify(state))
}
export const getCollapsedBucketState = listId => {
const state = getAllState()
if (typeof state[listId] !== 'undefined') {
return state[listId]
}
return {}
}

View File

@ -243,7 +243,8 @@
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.", "deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.", "deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.", "bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully." "bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
} }
}, },
"namespace": { "namespace": {

View File

@ -2,6 +2,8 @@ $bucket-background: $grey-100;
$task-background: $white; $task-background: $white;
$ease-out: all .3s cubic-bezier(0.23, 1, 0.32, 1); $ease-out: all .3s cubic-bezier(0.23, 1, 0.32, 1);
$bucket-width: 300px; $bucket-width: 300px;
$bucket-header-height: 60px;
$bucket-right-margin: 1rem;
$crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1rem - 1.5rem - 11px'; $crazy-height-calculation: '100vh - 4.5rem - 1.5rem - 1rem - 1.5rem - 11px';
$crazy-height-calculation-tasks: '#{$crazy-height-calculation} - 1rem - 2.5rem - 2rem - #{$button-height} - 1rem'; $crazy-height-calculation-tasks: '#{$crazy-height-calculation} - 1rem - 2.5rem - 2rem - #{$button-height} - 1rem';
@ -31,7 +33,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
position: relative; position: relative;
flex: 0 0 $bucket-width; flex: 0 0 $bucket-width;
margin: 0 1rem 0 0; margin: 0 $bucket-right-margin 0 0;
max-height: 100%; max-height: 100%;
min-height: 20px; min-height: 20px;
max-width: $bucket-width; max-width: $bucket-width;
@ -233,6 +235,18 @@ $filter-container-height: '1rem - #{$switch-view-height}';
a.dropdown-item { a.dropdown-item {
padding-right: 1rem; padding-right: 1rem;
} }
&.is-collapsed {
transform: rotate(90deg) translateX($bucket-width / 2 - $bucket-header-height / 2);
// Using negative margins instead of translateY here to make all other buckets fill the empty space
margin-left: ($bucket-width / 2 - $bucket-header-height / 2) * -1;
margin-right: calc(#{($bucket-width / 2 - $bucket-header-height / 2) * -1} + #{$bucket-right-margin});
cursor: pointer;
.tasks, .bucket-footer {
display: none;
}
}
} }
.bucket-header { .bucket-header {
@ -240,6 +254,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: .5rem; padding: .5rem;
height: $bucket-header-height;
.limit { .limit {
padding-left: .5rem; padding-left: .5rem;

View File

@ -17,8 +17,13 @@
/> />
</div> </div>
<div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container"> <div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container">
<div :key="`bucket${bucket.id}`" class="bucket" v-for="bucket in buckets"> <div
<div class="bucket-header"> :key="`bucket${bucket.id}`"
class="bucket"
:class="{'is-collapsed': collapsedBuckets[bucket.id]}"
v-for="bucket in buckets"
>
<div class="bucket-header" @click="() => unCollapseBucket(bucket)">
<span <span
v-if="bucket.isDoneBucket" v-if="bucket.isDoneBucket"
class="icon is-small has-text-success mr-2" class="icon is-small has-text-success mr-2"
@ -31,7 +36,7 @@
@focusout="() => saveBucketTitle(bucket.id)" @focusout="() => saveBucketTitle(bucket.id)"
@keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)" @keydown.enter.prevent.stop="() => saveBucketTitle(bucket.id)"
class="title input" class="title input"
:contenteditable="canWrite" :contenteditable="canWrite && !collapsedBuckets[bucket.id]"
spellcheck="false">{{ bucket.title }}</h2> spellcheck="false">{{ bucket.title }}</h2>
<span <span
:class="{'is-max': bucket.tasks.length >= bucket.limit}" :class="{'is-max': bucket.tasks.length >= bucket.limit}"
@ -41,7 +46,7 @@
</span> </span>
<dropdown <dropdown
class="is-right options" class="is-right options"
v-if="canWrite" v-if="canWrite && !collapsedBuckets[bucket.id]"
trigger-icon="ellipsis-v" trigger-icon="ellipsis-v"
@close="() => showSetLimitInput = false" @close="() => showSetLimitInput = false"
> >
@ -77,7 +82,7 @@
</template> </template>
</a> </a>
<a <a
@click="toggleDoneBucket(bucket)" @click.stop="toggleDoneBucket(bucket)"
class="dropdown-item" class="dropdown-item"
v-tooltip="$t('list.kanban.doneBucketHintExtended')" v-tooltip="$t('list.kanban.doneBucketHintExtended')"
> >
@ -85,9 +90,15 @@
icon="check-double"/></span> icon="check-double"/></span>
{{ $t('list.kanban.doneBucket') }} {{ $t('list.kanban.doneBucket') }}
</a> </a>
<a
class="dropdown-item"
@click.stop="() => collapseBucket(bucket)"
>
{{ $t('list.kanban.collapse') }}
</a>
<a <a
:class="{'is-disabled': buckets.length <= 1}" :class="{'is-disabled': buckets.length <= 1}"
@click="() => deleteBucketModal(bucket.id)" @click.stop="() => deleteBucketModal(bucket.id)"
class="dropdown-item has-text-danger" class="dropdown-item has-text-danger"
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''" v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
> >
@ -283,6 +294,7 @@ import FilterPopup from '@/components/list/partials/filter-popup'
import Dropdown from '@/components/misc/dropdown' import Dropdown from '@/components/misc/dropdown'
import {playPop} from '@/helpers/playPop' import {playPop} from '@/helpers/playPop'
import createTask from '@/components/tasks/mixins/createTask' import createTask from '@/components/tasks/mixins/createTask'
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
export default { export default {
name: 'Kanban', name: 'Kanban',
@ -315,6 +327,7 @@ export default {
showNewBucketInput: false, showNewBucketInput: false,
newTaskError: {}, newTaskError: {},
showSetLimitInput: false, showSetLimitInput: false,
collapsedBuckets: {},
// We're using this to show the loading animation only at the task when updating it // We're using this to show the loading animation only at the task when updating it
taskUpdating: {}, taskUpdating: {},
@ -369,6 +382,8 @@ export default {
return return
} }
this.collapsedBuckets = getCollapsedBucketState(this.$route.params.listId)
console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $route.params =`, this.$route.params) console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $route.params =`, this.$route.params)
this.filtersChanged = false this.filtersChanged = false
@ -612,6 +627,18 @@ export default {
bucket.isDoneBucket = !bucket.isDoneBucket bucket.isDoneBucket = !bucket.isDoneBucket
}) })
}, },
collapseBucket(bucket) {
this.$set(this.collapsedBuckets, bucket.id, true)
saveCollapsedBucketState(this.$route.params.listId, this.collapsedBuckets)
},
unCollapseBucket(bucket) {
if (!this.collapsedBuckets[bucket.id]) {
return
}
this.$set(this.collapsedBuckets, bucket.id, false)
saveCollapsedBucketState(this.$route.params.listId, this.collapsedBuckets)
},
}, },
} }
</script> </script>