feat: singleTaskInList script setup #2463

Merged
konrad merged 1 commits from dpschen/frontend:feature/feat-singleTaskInList-script-setup into main 2022-11-01 10:43:03 +00:00
2 changed files with 171 additions and 152 deletions

View File

@ -61,8 +61,8 @@ const taskService = shallowReactive(new TaskService())
const task = ref<ITask>() const task = ref<ITask>()
// We're saving the due date seperately to prevent null errors in very short periods where the task is null. // We're saving the due date seperately to prevent null errors in very short periods where the task is null.
const dueDate = ref<Date>() const dueDate = ref<Date | null>()
const lastValue = ref<Date>() const lastValue = ref<Date | null>()
const changeInterval = ref<ReturnType<typeof setInterval>>() const changeInterval = ref<ReturnType<typeof setInterval>>()
watch( watch(

View File

@ -1,30 +1,38 @@
<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"
/>
<ColorBubble <ColorBubble
v-if="showListColor && listColor !== ''" v-if="showListColor && listColor !== ''"
:color="listColor" :color="listColor"
class="mr-1" class="mr-1"
/> />
<router-link <router-link
:to="taskDetailRoute" :to="taskDetailRoute"
:class="{ 'done': task.done}" :class="{ 'done': task.done}"
class="tasktext"> class="tasktext"
>
<span> <span>
<router-link <router-link
v-if="showList && taskList !== null"
: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 !== ''}" :class="{'mr-2': task.hexColor !== ''}"
v-if="showList && getListById(task.listId) !== null" v-tooltip="$t('task.detail.belongsToList', {list: taskList.title})">
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})"> {{ taskList.title }}
{{ getListById(task.listId).title }}
</router-link> </router-link>
<ColorBubble <ColorBubble
v-if="task.hexColor !== ''" v-if="task.hexColor !== ''"
:color="task.getHexColor()" :color="getHexColor(task.hexColor)"
class="mr-1" 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">
@ -35,15 +43,22 @@
{{ task.title }} {{ task.title }}
</span> </span>
<labels class="labels ml-2 mr-1" :labels="task.labels" v-if="task.labels.length > 0" /> <labels
<user v-if="task.labels.length > 0"
class="labels ml-2 mr-1"
:labels="task.labels"
/>
<User
v-for="(a, i) in task.assignees"
:avatar-size="27" :avatar-size="27"
:is-inline="true" :is-inline="true"
:key="task.id + 'assignee' + a.id + i" :key="task.id + 'assignee' + a.id + i"
:show-username="false" :show-username="false"
:user="a" :user="a"
v-for="(a, i) in task.assignees"
/> />
<!-- FIXME: use popup -->
<BaseButton <BaseButton
v-if="+new Date(task.dueDate) > 0" v-if="+new Date(task.dueDate) > 0"
class="dueDate" class="dueDate"
@ -62,7 +77,9 @@
<transition name="fade"> <transition name="fade">
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/> <defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
</transition> </transition>
<priority-label :priority="task.priority" :done="task.done"/> <priority-label :priority="task.priority" :done="task.done"/>
<span> <span>
<span class="list-task-icon" v-if="task.attachments.length > 0"> <span class="list-task-icon" v-if="task.attachments.length > 0">
<icon icon="paperclip"/> <icon icon="paperclip"/>
@ -74,74 +91,68 @@
<icon icon="history"/> <icon icon="history"/>
</span> </span>
</span> </span>
<checklist-summary :task="task"/> <checklist-summary :task="task"/>
</router-link> </router-link>
<progress <progress
class="progress is-small" class="progress is-small"
v-if="task.percentDone > 0" v-if="task.percentDone > 0"
:value="task.percentDone * 100" max="100"> :value="task.percentDone * 100" max="100"
>
{{ task.percentDone * 100 }}% {{ task.percentDone * 100 }}%
</progress> </progress>
<router-link <router-link
v-if="!showList && currentList.id !== task.listId && taskList !== null"
:to="{ name: 'list.list', params: { listId: task.listId } }" :to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list" class="task-list"
v-if="!showList && currentList.id !== task.listId && getListById(task.listId) !== null" v-tooltip="$t('task.detail.belongsToList', {list: taskList.title})"
v-tooltip="$t('task.detail.belongsToList', {list: getListById(task.listId).title})"> >
{{ getListById(task.listId).title }} {{ taskList.title }}
</router-link> </router-link>
<BaseButton <BaseButton
:class="{'is-favorite': task.isFavorite}" :class="{'is-favorite': task.isFavorite}"
@click="toggleFavorite" @click="toggleFavorite"
class="favorite"> class="favorite"
>
<icon icon="star" v-if="task.isFavorite"/> <icon icon="star" v-if="task.isFavorite"/>
<icon :icon="['far', 'star']" v-else/> <icon :icon="['far', 'star']" v-else/>
</BaseButton> </BaseButton>
<slot></slot> <slot />
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import {defineComponent, type PropType} from 'vue' import {ref, watch, shallowReactive, toRef, type PropType, onMounted, onBeforeUnmount, computed} from 'vue'
import {mapState} from 'pinia' import {useI18n} from 'vue-i18n'
import TaskModel from '@/models/task' import TaskModel, { getHexColor } from '@/models/task'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import PriorityLabel from './priorityLabel.vue'
import TaskService from '../../../services/task' import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
import Labels from '@/components/tasks/partials/labels.vue' import Labels from '@/components/tasks/partials//labels.vue'
import DeferTask from '@/components/tasks/partials//defer-task.vue'
import ChecklistSummary from '@/components/tasks/partials/checklist-summary.vue'
import User from '@/components/misc/user.vue' import User from '@/components/misc/user.vue'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import Fancycheckbox from '../../input/fancycheckbox.vue' import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import DeferTask from './defer-task.vue'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import ChecklistSummary from './checklist-summary.vue'
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
import ColorBubble from '@/components/misc/colorBubble.vue' import ColorBubble from '@/components/misc/colorBubble.vue'
import TaskService from '@/services/task'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
import {success} from '@/message'
import {useListStore} from '@/stores/lists' import {useListStore} from '@/stores/lists'
import {useNamespaceStore} from '@/stores/namespaces' import {useNamespaceStore} from '@/stores/namespaces'
import {useBaseStore} from '@/stores/base' import {useBaseStore} from '@/stores/base'
import {useTaskStore} from '@/stores/tasks' import {useTaskStore} from '@/stores/tasks'
export default defineComponent({ const props = defineProps({
name: 'singleTaskInList',
data() {
return {
taskService: new TaskService(),
task: new TaskModel(),
showDefer: false,
}
},
components: {
ColorBubble,
BaseButton,
ChecklistSummary,
DeferTask,
Fancycheckbox,
User,
Labels,
PriorityLabel,
},
props: {
theTask: { theTask: {
type: Object as PropType<ITask>, type: Object as PropType<ITask>,
required: true, required: true,
@ -166,61 +177,69 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
})
const emit = defineEmits(['task-updated'])
const {t} = useI18n({useScope: 'global'})
const taskService = shallowReactive(new TaskService())
const task = ref<ITask>(new TaskModel())
const showDefer = ref(false)
const theTask = toRef(props, 'theTask')
watch(
theTask,
newVal => {
task.value = newVal
}, },
emits: ['task-updated'], )
watch: {
theTask(newVal) { onMounted(() => {
this.task = newVal task.value = theTask.value
}, document.addEventListener('click', hideDeferDueDatePopup)
}, })
mounted() {
this.task = this.theTask onBeforeUnmount(() => {
document.addEventListener('click', this.hideDeferDueDatePopup) document.removeEventListener('click', hideDeferDueDatePopup)
}, })
beforeUnmount() {
document.removeEventListener('click', this.hideDeferDueDatePopup)
},
computed: {
...mapState(useListStore, {
getListById: 'getListById',
}),
listColor() {
const list = this.getListById(this.task.listId)
return list !== null ? list.hexColor : ''
},
currentList() {
const baseStore = useBaseStore() const baseStore = useBaseStore()
const listStore = useListStore()
const taskStore = useTaskStore()
const namespaceStore = useNamespaceStore()
const taskList = computed(() => listStore.getListById(task.value.listId))
const listColor = computed(() => taskList.value !== null ? taskList.value.hexColor : '')
const currentList = computed(() => {
return typeof baseStore.currentList === 'undefined' ? { return typeof baseStore.currentList === 'undefined' ? {
id: 0, id: 0,
title: '', title: '',
} : baseStore.currentList } : baseStore.currentList
}, })
taskDetailRoute() {
return {
name: 'task.detail',
params: {id: this.task.id},
// TODO: re-enable opening task detail in modal
// state: { backdropView: this.$router.currentRoute.value.fullPath },
}
},
},
methods: {
formatDateSince,
formatISO,
formatDateLong,
async markAsDone(checked: boolean) { const taskDetailRoute = computed(() => ({
name: 'task.detail',
params: {id: task.value.id},
// TODO: re-enable opening task detail in modal
// state: { backdropView: router.currentRoute.value.fullPath },
}))
async function markAsDone(checked: boolean) {
const updateFunc = async () => { const updateFunc = async () => {
const task = await useTaskStore().update(this.task) const newTask = await taskStore.update(task.value)
this.task = task task.value = newTask
this.$emit('task-updated', task) emit('task-updated', newTask)
this.$message.success({ success({
message: this.task.done ? message: task.value.done ?
this.$t('task.doneSuccess') : t('task.doneSuccess') :
this.$t('task.undoneSuccess'), t('task.undoneSuccess'),
}, [{ }, [{
title: 'Undo', title: 'Undo',
callback: () => this.undoDone(checked), callback: () => undoDone(checked),
}]) }])
} }
@ -229,29 +248,29 @@ export default defineComponent({
} else { } else {
await updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around await updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
} }
}, }
undoDone(checked: boolean) { function undoDone(checked: boolean) {
this.task.done = !this.task.done task.value.done = !task.value.done
this.markAsDone(!checked) markAsDone(!checked)
}, }
async toggleFavorite() { async function toggleFavorite() {
this.task.isFavorite = !this.task.isFavorite task.value.isFavorite = !task.value.isFavorite
this.task = await this.taskService.update(this.task) task.value = await taskService.update(task.value)
this.$emit('task-updated', this.task) emit('task-updated', task.value)
useNamespaceStore().loadNamespacesIfFavoritesDontExist() namespaceStore.loadNamespacesIfFavoritesDontExist()
}, }
hideDeferDueDatePopup(e) {
if (!this.showDefer) { const deferDueDate = ref<typeof DeferTask | null>(null)
function hideDeferDueDatePopup(e) {
if (!showDefer.value) {
return return
} }
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => { closeWhenClickedOutside(e, deferDueDate.value.$el, () => {
this.showDefer = false showDefer.value = false
})
},
},
}) })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>