feat: color the task color button when the task has a color set #2331
|
@ -56,10 +56,10 @@
|
|||
class="menu-label"
|
||||
v-tooltip="namespaceTitles[nk]"
|
||||
>
|
||||
<span
|
||||
<ColorBubble
|
||||
v-if="n.hexColor !== ''"
|
||||
:style="{ backgroundColor: n.hexColor }"
|
||||
class="color-bubble"
|
||||
:color="n.hexColor"
|
||||
class="mr-1"
|
||||
konrad marked this conversation as resolved
|
||||
/>
|
||||
<span class="name">{{ namespaceTitles[nk] }}</span>
|
||||
<div
|
||||
|
@ -114,11 +114,11 @@
|
|||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<span
|
||||
:style="{ backgroundColor: l.hexColor }"
|
||||
class="color-bubble"
|
||||
v-if="l.hexColor !== ''">
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="l.hexColor !== ''"
|
||||
:color="l.hexColor"
|
||||
class="mr-1"
|
||||
konrad marked this conversation as resolved
dpschen
commented
See comment in ColorBubble component See comment in ColorBubble component
|
||||
/>
|
||||
<span class="list-menu-title">{{ getListTitle(l) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
|
@ -158,6 +158,7 @@ import {getListTitle} from '@/helpers/getListTitle'
|
|||
import {useEventListener} from '@vueuse/core'
|
||||
import type {IList} from '@/modelTypes/IList'
|
||||
import type {INamespace} from '@/modelTypes/INamespace'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
const drag = ref(false)
|
||||
const dragOptions = {
|
||||
|
|
|
@ -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 !== ''">
|
||||
<icon :icon="icon"/>
|
||||
<icon
|
||||
:icon="icon"
|
||||
:style="{'color': iconColor !== '' ? iconColor : false}"
|
||||
/>
|
||||
</span>
|
||||
<slot />
|
||||
</BaseButton>
|
||||
|
@ -42,6 +49,10 @@ const props = defineProps({
|
|||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
iconColor: {
|
||||
dpschen
commented
What I still don't like here is that it's possible to choose a color that doesn't have any contrast to the button. While the color dot has the same problem we could fix it there by adding a slim border. What I still don't like here is that it's possible to choose a color that doesn't have any contrast to the button.
While the color dot has the same problem we could fix it there by adding a slim border.
The icon on the other hand has such slim lines that the contrast can get bad very easy.
konrad
commented
So we rather shouldn't set the color for the icon? So we rather shouldn't set the color for the icon?
dpschen
commented
That's my personal opinion :) That's my personal opinion :)
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<span
|
||||
:style="{backgroundColor: color }"
|
||||
class="color-bubble"
|
||||
></span>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Color } from 'csstype'
|
||||
|
||||
defineProps< {
|
||||
konrad marked this conversation as resolved
Outdated
dpschen
commented
Picky: you could improve types here by using:
Picky: you could improve types here by using:
```
import type { Color } from "csstype"
```
konrad
commented
Nice one. Done. Nice one. Done.
|
||||
color: Color,
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color-bubble {
|
||||
display: inline-block;
|
||||
border-radius: 100%;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
dpschen marked this conversation as resolved
dpschen
commented
If this margin should really be the same everywhere: We should build this element so that the bubble has that distance included, meaning e.g. we would need to wrap two elements (the outer has an inner padding, the inner is the actual bubble). If not: The margin should be controlled from the outside. If this margin should __really__ be the same everywhere:
We should build this element so that the bubble has that distance included, meaning e.g. we would need to wrap two elements (the outer has an inner padding, the inner is the actual bubble).
If not:
The margin should be controlled from the outside.
konrad
commented
It's intended to not have any margin - the margin here is a leftover from copying the styles from the theme. I've added the margins everywhere where the ColorBubble component is included and the margins are needed. It's intended to not have any margin - the margin here is a leftover from copying the styles from the theme. I've added the margins everywhere where the ColorBubble component is included and the margins are needed.
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<div class="heading">
|
||||
<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
|
||||
class="title input"
|
||||
:class="{'disabled': !canWrite}"
|
||||
|
@ -42,6 +47,7 @@ import Done from '@/components/misc/Done.vue'
|
|||
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
||||
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
const props = defineProps({
|
||||
task: {
|
||||
|
@ -58,10 +64,11 @@ const emit = defineEmits(['update:task'])
|
|||
|
||||
const router = useRouter()
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
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
|
||||
|
||||
|
||||
await copy(absoluteURL)
|
||||
}
|
||||
|
||||
|
@ -95,8 +102,7 @@ async function save(title: string) {
|
|||
setTimeout(() => {
|
||||
showSavedMessage.value = false
|
||||
}, 2000)
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
@ -106,4 +112,9 @@ async function save(title: string) {
|
|||
.heading__done {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
.color-bubble {
|
||||
height: .75rem;
|
||||
width: .75rem;
|
||||
}
|
||||
</style>
|
|
@ -1,12 +1,11 @@
|
|||
<template>
|
||||
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
|
||||
<fancycheckbox :disabled="(isArchived || disabled) && !canMarkAsDone" @change="markAsDone" v-model="task.done"/>
|
||||
<span
|
||||
<ColorBubble
|
||||
v-if="showListColor && listColor !== ''"
|
||||
:style="{backgroundColor: listColor }"
|
||||
class="color-bubble"
|
||||
>
|
||||
</span>
|
||||
:color="listColor"
|
||||
class="mr-1"
|
||||
konrad marked this conversation as resolved
dpschen
commented
See comment in ColorBubble component See comment in ColorBubble component
|
||||
/>
|
||||
<router-link
|
||||
:to="taskDetailRoute"
|
||||
:class="{ 'done': task.done}"
|
||||
|
@ -15,11 +14,17 @@
|
|||
<router-link
|
||||
:to="{ name: 'list.list', params: { listId: task.listId } }"
|
||||
class="task-list"
|
||||
:class="{'mr-2': task.hexColor !== ''}"
|
||||
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
|
||||
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
|
||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||
</router-link>
|
||||
|
||||
<ColorBubble
|
||||
v-if="task.hexColor !== ''"
|
||||
:color="task.getHexColor()"
|
||||
class="mr-1"
|
||||
konrad marked this conversation as resolved
dpschen
commented
See comment in ColorBubble component See comment in ColorBubble component
|
||||
/>
|
||||
<!-- 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'">
|
||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||
|
@ -30,7 +35,7 @@
|
|||
{{ task.title }}
|
||||
</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
|
||||
:avatar-size="27"
|
||||
:is-inline="true"
|
||||
|
@ -111,6 +116,7 @@ import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
|||
import {playPop} from '@/helpers/playPop'
|
||||
import ChecklistSummary from './checklist-summary.vue'
|
||||
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'singleTaskInList',
|
||||
|
@ -122,6 +128,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
components: {
|
||||
ColorBubble,
|
||||
BaseButton,
|
||||
ChecklistSummary,
|
||||
DeferTask,
|
||||
|
@ -282,11 +289,6 @@ export default defineComponent({
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.color-bubble {
|
||||
height: 10px;
|
||||
flex: 0 0 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
vertical-align: bottom;
|
||||
|
|
|
@ -378,6 +378,7 @@
|
|||
@click="setFieldActive('color')"
|
||||
variant="secondary"
|
||||
icon="fill-drip"
|
||||
:icon-color="color"
|
||||
v-shortcut="'c'"
|
||||
>
|
||||
{{ $t('task.detail.actions.color') }}
|
||||
|
@ -429,7 +430,7 @@ import {defineComponent} from 'vue'
|
|||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
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 { PRIORITIES as priorites } from '@/constants/priorities'
|
||||
|
@ -461,6 +462,7 @@ import { setTitle } from '@/helpers/setTitle'
|
|||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||
import {getListTitle} from '@/helpers/getListTitle'
|
||||
import type { IList } from '@/modelTypes/IList'
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
|
||||
function scrollIntoView(el) {
|
||||
if (!el) {
|
||||
|
@ -527,6 +529,8 @@ export default defineComponent({
|
|||
showDeleteModal: false,
|
||||
// Used to avoid flashing of empty elements if the task content is not yet loaded.
|
||||
visible: false,
|
||||
|
||||
TASK_DEFAULT_COLOR,
|
||||
|
||||
activeFields: {
|
||||
assignees: false,
|
||||
|
@ -594,6 +598,15 @@ export default defineComponent({
|
|||
shouldShowClosePopup() {
|
||||
return this.$route.name.includes('kanban')
|
||||
},
|
||||
color() {
|
||||
const color = this.task.getHexColor
|
||||
? this.task.getHexColor()
|
||||
: false
|
||||
|
||||
return color === TASK_DEFAULT_COLOR
|
||||
? ''
|
||||
: color
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getNamespaceTitle,
|
||||
|
@ -745,6 +758,8 @@ export default defineComponent({
|
|||
this.task = await this.taskService.update(this.task)
|
||||
this.$store.dispatch('namespaces/loadNamespacesIfFavoritesDontExist')
|
||||
},
|
||||
|
||||
colorIsDark,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
@ -932,7 +947,11 @@ $flash-background-duration: 750ms;
|
|||
.button {
|
||||
width: 100%;
|
||||
margin-bottom: .5rem;
|
||||
justify-content: left;
|
||||
justify-content: left;
|
||||
|
||||
&.has-light-text {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
See comment in ColorBubble component