feat: color the task color button when the task has a color set (#2331)

Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2331
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
konrad 2022-09-15 12:46:12 +00:00
commit f70b1d2902
6 changed files with 96 additions and 28 deletions

View file

@ -56,10 +56,10 @@
class="menu-label" class="menu-label"
v-tooltip="namespaceTitles[nk]" v-tooltip="namespaceTitles[nk]"
> >
<span <ColorBubble
v-if="n.hexColor !== ''" v-if="n.hexColor !== ''"
:style="{ backgroundColor: n.hexColor }" :color="n.hexColor"
class="color-bubble" class="mr-1"
/> />
<span class="name">{{ namespaceTitles[nk] }}</span> <span class="name">{{ namespaceTitles[nk] }}</span>
<div <div
@ -114,11 +114,11 @@
<span class="icon handle"> <span class="icon handle">
<icon icon="grip-lines"/> <icon icon="grip-lines"/>
</span> </span>
<span <ColorBubble
:style="{ backgroundColor: l.hexColor }" v-if="l.hexColor !== ''"
class="color-bubble" :color="l.hexColor"
v-if="l.hexColor !== ''"> class="mr-1"
</span> />
<span class="list-menu-title">{{ getListTitle(l) }}</span> <span class="list-menu-title">{{ getListTitle(l) }}</span>
</BaseButton> </BaseButton>
<BaseButton <BaseButton
@ -158,6 +158,7 @@ import {getListTitle} from '@/helpers/getListTitle'
import {useEventListener} from '@vueuse/core' import {useEventListener} from '@vueuse/core'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
import type {INamespace} from '@/modelTypes/INamespace' import type {INamespace} from '@/modelTypes/INamespace'
import ColorBubble from '@/components/misc/colorBubble.vue'
const drag = ref(false) const drag = ref(false)
const dragOptions = { const dragOptions = {

View file

@ -9,9 +9,16 @@
} }
]" ]"
> >
<icon :icon="icon" v-if="showIconOnly"/> <icon
v-if="showIconOnly"
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : false}"
/>
<span class="icon is-small" v-else-if="icon !== ''"> <span class="icon is-small" v-else-if="icon !== ''">
<icon :icon="icon"/> <icon
:icon="icon"
:style="{'color': iconColor !== '' ? iconColor : false}"
/>
</span> </span>
<slot /> <slot />
</BaseButton> </BaseButton>
@ -42,6 +49,10 @@ const props = defineProps({
type: [String, Array], type: [String, Array],
default: '', default: '',
}, },
iconColor: {
type: String,
default: '',
},
loading: { loading: {
type: Boolean, type: Boolean,
default: false, default: false,

View file

@ -0,0 +1,24 @@
<template>
<span
:style="{backgroundColor: color }"
class="color-bubble"
></span>
</template>
<script lang="ts" setup>
import type { Color } from 'csstype'
defineProps< {
color: Color,
}>()
</script>
<style scoped>
.color-bubble {
display: inline-block;
border-radius: 100%;
height: 10px;
width: 10px;
flex-shrink: 0;
}
</style>

View file

@ -1,7 +1,12 @@
<template> <template>
<div class="heading"> <div class="heading">
<BaseButton @click="copyUrl"><h1 class="title task-id">{{ textIdentifier }}</h1></BaseButton> <BaseButton @click="copyUrl"><h1 class="title task-id">{{ textIdentifier }}</h1></BaseButton>
<Done class="heading__done" :is-done="task.done" /> <Done class="heading__done" :is-done="task.done"/>
<ColorBubble
v-if="task.hexColor !== ''"
:color="task.getHexColor()"
class="mt-1 ml-2"
/>
<h1 <h1
class="title input" class="title input"
:class="{'disabled': !canWrite}" :class="{'disabled': !canWrite}"
@ -42,6 +47,7 @@ import Done from '@/components/misc/Done.vue'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import ColorBubble from '@/components/misc/colorBubble.vue'
const props = defineProps({ const props = defineProps({
task: { task: {
@ -58,8 +64,9 @@ const emit = defineEmits(['update:task'])
const router = useRouter() const router = useRouter()
const copy = useCopyToClipboard() const copy = useCopyToClipboard()
async function copyUrl() { async function copyUrl() {
const route = router.resolve({ name: 'task.detail', query: { taskId: props.task.id}}) const route = router.resolve({name: 'task.detail', query: {taskId: props.task.id}})
const absoluteURL = new URL(route.href, window.location.href).href const absoluteURL = new URL(route.href, window.location.href).href
await copy(absoluteURL) await copy(absoluteURL)
@ -95,8 +102,7 @@ async function save(title: string) {
setTimeout(() => { setTimeout(() => {
showSavedMessage.value = false showSavedMessage.value = false
}, 2000) }, 2000)
} } finally {
finally {
saving.value = false saving.value = false
} }
} }
@ -106,4 +112,9 @@ async function save(title: string) {
.heading__done { .heading__done {
margin-left: .5rem; margin-left: .5rem;
} }
.color-bubble {
height: .75rem;
width: .75rem;
}
</style> </style>

View file

@ -1,12 +1,11 @@
<template> <template>
<div :class="{'is-loading': taskService.loading}" class="task loader-container"> <div :class="{'is-loading': taskService.loading}" class="task loader-container">
<fancycheckbox :disabled="(isArchived || disabled) && !canMarkAsDone" @change="markAsDone" v-model="task.done"/> <fancycheckbox :disabled="(isArchived || disabled) && !canMarkAsDone" @change="markAsDone" v-model="task.done"/>
<span <ColorBubble
v-if="showListColor && listColor !== ''" v-if="showListColor && listColor !== ''"
:style="{backgroundColor: listColor }" :color="listColor"
class="color-bubble" class="mr-1"
> />
</span>
<router-link <router-link
:to="taskDetailRoute" :to="taskDetailRoute"
:class="{ 'done': task.done}" :class="{ 'done': task.done}"
@ -15,11 +14,17 @@
<router-link <router-link
:to="{ name: 'list.list', params: { listId: task.listId } }" :to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list" class="task-list"
:class="{'mr-2': task.hexColor !== ''}"
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null" v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})"> v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
{{ $store.getters['lists/getListById'](task.listId).title }} {{ $store.getters['lists/getListById'](task.listId).title }}
</router-link> </router-link>
<ColorBubble
v-if="task.hexColor !== ''"
:color="task.getHexColor()"
class="mr-1"
/>
<!-- Show any parent tasks to make it clear this task is a sub task of something --> <!-- Show any parent tasks to make it clear this task is a sub task of something -->
<span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'"> <span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'">
<template v-for="(pt, i) in task.relatedTasks.parenttask"> <template v-for="(pt, i) in task.relatedTasks.parenttask">
@ -30,7 +35,7 @@
{{ task.title }} {{ task.title }}
</span> </span>
<labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0" /> <labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0"/>
<user <user
:avatar-size="27" :avatar-size="27"
:is-inline="true" :is-inline="true"
@ -111,6 +116,7 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {playPop} from '@/helpers/playPop' import {playPop} from '@/helpers/playPop'
import ChecklistSummary from './checklist-summary.vue' import ChecklistSummary from './checklist-summary.vue'
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate' import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
import ColorBubble from '@/components/misc/colorBubble.vue'
export default defineComponent({ export default defineComponent({
name: 'singleTaskInList', name: 'singleTaskInList',
@ -122,6 +128,7 @@ export default defineComponent({
} }
}, },
components: { components: {
ColorBubble,
BaseButton, BaseButton,
ChecklistSummary, ChecklistSummary,
DeferTask, DeferTask,
@ -282,11 +289,6 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
} }
.color-bubble {
height: 10px;
flex: 0 0 10px;
}
.avatar { .avatar {
border-radius: 50%; border-radius: 50%;
vertical-align: bottom; vertical-align: bottom;

View file

@ -378,6 +378,7 @@
@click="setFieldActive('color')" @click="setFieldActive('color')"
variant="secondary" variant="secondary"
icon="fill-drip" icon="fill-drip"
:icon-color="color"
v-shortcut="'c'" v-shortcut="'c'"
> >
{{ $t('task.detail.actions.color') }} {{ $t('task.detail.actions.color') }}
@ -429,7 +430,7 @@ import {defineComponent} from 'vue'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import TaskService from '../../services/task' import TaskService from '../../services/task'
import TaskModel from '@/models/task' import TaskModel, {TASK_DEFAULT_COLOR} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import { PRIORITIES as priorites } from '@/constants/priorities' import { PRIORITIES as priorites } from '@/constants/priorities'
@ -461,6 +462,7 @@ import { setTitle } from '@/helpers/setTitle'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle' import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getListTitle} from '@/helpers/getListTitle' import {getListTitle} from '@/helpers/getListTitle'
import type { IList } from '@/modelTypes/IList' import type { IList } from '@/modelTypes/IList'
import {colorIsDark} from '@/helpers/color/colorIsDark'
function scrollIntoView(el) { function scrollIntoView(el) {
if (!el) { if (!el) {
@ -528,6 +530,8 @@ export default defineComponent({
// Used to avoid flashing of empty elements if the task content is not yet loaded. // Used to avoid flashing of empty elements if the task content is not yet loaded.
visible: false, visible: false,
TASK_DEFAULT_COLOR,
activeFields: { activeFields: {
assignees: false, assignees: false,
priority: false, priority: false,
@ -594,6 +598,15 @@ export default defineComponent({
shouldShowClosePopup() { shouldShowClosePopup() {
return this.$route.name.includes('kanban') return this.$route.name.includes('kanban')
}, },
color() {
const color = this.task.getHexColor
? this.task.getHexColor()
: false
return color === TASK_DEFAULT_COLOR
? ''
: color
},
}, },
methods: { methods: {
getNamespaceTitle, getNamespaceTitle,
@ -745,6 +758,8 @@ export default defineComponent({
this.task = await this.taskService.update(this.task) this.task = await this.taskService.update(this.task)
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist') this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
}, },
colorIsDark,
}, },
}) })
</script> </script>
@ -933,6 +948,10 @@ $flash-background-duration: 750ms;
width: 100%; width: 100%;
margin-bottom: .5rem; margin-bottom: .5rem;
justify-content: left; justify-content: left;
&.has-light-text {
color: var(--white);
}
} }
} }