feat: ListList script setup (#2441)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2441 Reviewed-by: konrad <k@knt.li> Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de> Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
63f2e6ba6f
commit
bbf4ef4697
6 changed files with 163 additions and 177 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<dropdown>
|
||||
<template v-if="isSavedFilter">
|
||||
<template v-if="isSavedFilter(list)">
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
|
@ -78,7 +78,7 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, computed, watchEffect, type PropType} from 'vue'
|
||||
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
import {isSavedFilter} from '@/helpers/savedFilter'
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||
|
@ -100,5 +100,4 @@ watchEffect(() => {
|
|||
|
||||
const configStore = useConfigStore()
|
||||
const backgroundsEnabled = computed(() => configStore.enabledBackgroundProviders?.length > 0)
|
||||
const isSavedFilter = computed(() => getSavedFilterIdFromListId(props.list.id) > 0)
|
||||
</script>
|
||||
|
|
|
@ -2,6 +2,7 @@ import {ref, shallowReactive, watch, computed} from 'vue'
|
|||
import {useRoute} from 'vue-router'
|
||||
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import type { ITask } from '@/modelTypes/ITask'
|
||||
|
||||
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
||||
export const getDefaultParams = () => ({
|
||||
|
@ -70,7 +71,7 @@ export function useTaskList(listId, sortByDefault = SORT_BY_DEFAULT) {
|
|||
const loading = computed(() => taskCollectionService.loading)
|
||||
const totalPages = computed(() => taskCollectionService.totalPages)
|
||||
|
||||
const tasks = ref([])
|
||||
const tasks = ref<ITask[]>([])
|
||||
async function loadTasks() {
|
||||
tasks.value = []
|
||||
tasks.value = await taskCollectionService.getAll(...getAllTasksParams.value)
|
||||
|
@ -81,10 +82,10 @@ export function useTaskList(listId, sortByDefault = SORT_BY_DEFAULT) {
|
|||
watch(() => route.query, (query) => {
|
||||
const { page: pageQueryValue, search: searchQuery } = query
|
||||
if (searchQuery !== undefined) {
|
||||
search.value = searchQuery
|
||||
search.value = searchQuery as string
|
||||
}
|
||||
if (pageQueryValue !== undefined) {
|
||||
page.value = parseInt(pageQueryValue)
|
||||
page.value = Number(pageQueryValue)
|
||||
}
|
||||
|
||||
}, { immediate: true })
|
||||
|
|
|
@ -8,3 +8,7 @@ export function getSavedFilterIdFromListId(listId: IList['id']) {
|
|||
}
|
||||
return filterId
|
||||
}
|
||||
|
||||
export function isSavedFilter(list: IList) {
|
||||
return getSavedFilterIdFromListId(list.id) > 0
|
||||
}
|
|
@ -9,8 +9,6 @@ import type {ITask} from '@/modelTypes/ITask'
|
|||
import type {INamespace} from '@/modelTypes/INamespace'
|
||||
import type {ISubscription} from '@/modelTypes/ISubscription'
|
||||
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
|
||||
export default class ListModel extends AbstractModel<IList> implements IList {
|
||||
id = 0
|
||||
title = ''
|
||||
|
@ -52,12 +50,4 @@ export default class ListModel extends AbstractModel<IList> implements IList {
|
|||
this.created = new Date(this.created)
|
||||
this.updated = new Date(this.updated)
|
||||
}
|
||||
|
||||
isSavedFilter() {
|
||||
return this.getSavedFilterId() > 0
|
||||
}
|
||||
|
||||
getSavedFilterId() {
|
||||
return getSavedFilterIdFromListId(this.id)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<ListWrapper class="list-kanban" :list-id="listId" viewName="kanban">
|
||||
<template #header>
|
||||
<div class="filter-container" v-if="isSavedFilter">
|
||||
<div class="filter-container" v-if="isSavedFilter(list)">
|
||||
<div class="items">
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
|
@ -239,6 +239,7 @@ import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveC
|
|||
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
||||
import KanbanCard from '@/components/tasks/partials/kanban-card.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import {isSavedFilter} from '@/helpers/savedFilter'
|
||||
|
||||
const DRAG_OPTIONS = {
|
||||
// sortable options
|
||||
|
@ -324,9 +325,6 @@ export default defineComponent({
|
|||
})
|
||||
},
|
||||
|
||||
isSavedFilter() {
|
||||
return this.list.isSavedFilter && !this.list.isSavedFilter()
|
||||
},
|
||||
loadBucketParameter() {
|
||||
return {
|
||||
listId: this.listId,
|
||||
|
@ -356,6 +354,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
isSavedFilter,
|
||||
|
||||
loadBuckets() {
|
||||
const {listId, params} = this.loadBucketParameter
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #header>
|
||||
<div
|
||||
class="filter-container"
|
||||
v-if="list.isSavedFilter && !list.isSavedFilter()"
|
||||
v-if="!isSavedFilter(list)"
|
||||
>
|
||||
<div class="items">
|
||||
<div class="search">
|
||||
|
@ -58,7 +58,7 @@
|
|||
>
|
||||
<add-task
|
||||
@taskAdded="updateTaskList"
|
||||
ref="addTask"
|
||||
ref="addTaskRef"
|
||||
:default-position="firstNewPosition"
|
||||
/>
|
||||
</template>
|
||||
|
@ -76,7 +76,7 @@
|
|||
v-if="tasks && tasks.length > 0"
|
||||
>
|
||||
<draggable
|
||||
v-bind="dragOptions"
|
||||
v-bind="DRAG_OPTIONS"
|
||||
v-model="tasks"
|
||||
group="tasks"
|
||||
@start="() => drag = true"
|
||||
|
@ -94,7 +94,7 @@
|
|||
<single-task-in-list
|
||||
:show-list-color="false"
|
||||
:disabled="!canWrite"
|
||||
:can-mark-as-done="canWrite || (list.isSavedFilter && list.isSavedFilter())"
|
||||
:can-mark-as-done="canWrite || isSavedFilter(list)"
|
||||
:the-task="t"
|
||||
@taskUpdated="updateTasks"
|
||||
>
|
||||
|
@ -114,7 +114,7 @@
|
|||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<edit-task
|
||||
<EditTask
|
||||
v-if="isTaskEdit"
|
||||
class="taskedit mt-0"
|
||||
:title="$t('list.list.editTask')"
|
||||
|
@ -135,25 +135,32 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRef, defineComponent } from 'vue'
|
||||
export default { name: 'List' }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, toRef, nextTick, onMounted} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
|
||||
import ListWrapper from './ListWrapper.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
||||
import ListWrapper from './ListWrapper.vue'
|
||||
import EditTask from '@/components/tasks/edit-task.vue'
|
||||
import AddTask from '@/components/tasks/add-task.vue'
|
||||
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList.vue'
|
||||
import { useTaskList } from '@/composables/taskList'
|
||||
import {RIGHTS as Rights} from '@/constants/rights'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
import {HAS_TASKS} from '@/store/mutation-types'
|
||||
import Nothing from '@/components/misc/nothing.vue'
|
||||
import Pagination from '@/components/misc/pagination.vue'
|
||||
import {ALPHABETICAL_SORT} from '@/components/list/partials/filters.vue'
|
||||
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
||||
import {useStore} from '@/store'
|
||||
import {HAS_TASKS} from '@/store/mutation-types'
|
||||
import {useTaskList} from '@/composables/taskList'
|
||||
import {RIGHTS as Rights} from '@/constants/rights'
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {isSavedFilter} from '@/helpers/savedFilter'
|
||||
|
||||
function sortTasks(tasks: ITask[]) {
|
||||
if (tasks === null || Array.isArray(tasks) && tasks.length === 0) {
|
||||
|
@ -173,142 +180,129 @@ function sortTasks(tasks: ITask[]) {
|
|||
})
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'List',
|
||||
|
||||
props: {
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
data() {
|
||||
return {
|
||||
ctaVisible: false,
|
||||
showTaskSearch: false,
|
||||
const ctaVisible = ref(false)
|
||||
const showTaskSearch = ref(false)
|
||||
|
||||
drag: false,
|
||||
dragOptions: {
|
||||
const drag = ref(false)
|
||||
const DRAG_OPTIONS = {
|
||||
animation: 100,
|
||||
ghostClass: 'ghost',
|
||||
},
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BaseButton,
|
||||
ListWrapper,
|
||||
Nothing,
|
||||
FilterPopup,
|
||||
SingleTaskInList,
|
||||
EditTask,
|
||||
AddTask,
|
||||
draggable,
|
||||
Pagination,
|
||||
ButtonLink,
|
||||
},
|
||||
} as const
|
||||
|
||||
setup(props) {
|
||||
const taskEditTask = ref(null)
|
||||
const isTaskEdit = ref(false)
|
||||
|
||||
// This function initializes the tasks page and loads the first page of tasks
|
||||
// function beforeLoad() {
|
||||
// taskEditTask.value = null
|
||||
// isTaskEdit.value = false
|
||||
// }
|
||||
const taskEditTask = ref(null)
|
||||
const isTaskEdit = ref(false)
|
||||
|
||||
const taskList = useTaskList(toRef(props, 'listId'), {
|
||||
position: 'asc',
|
||||
})
|
||||
const {
|
||||
tasks,
|
||||
loading,
|
||||
totalPages,
|
||||
currentPage,
|
||||
loadTasks,
|
||||
searchTerm,
|
||||
params,
|
||||
// sortByParam,
|
||||
} = useTaskList(toRef(props, 'listId'), {position: 'asc' })
|
||||
|
||||
return {
|
||||
taskEditTask,
|
||||
isTaskEdit,
|
||||
...taskList,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAlphabeticalSorting() {
|
||||
return this.params.sort_by.find( sortBy => sortBy === ALPHABETICAL_SORT ) !== undefined
|
||||
},
|
||||
firstNewPosition() {
|
||||
if (this.tasks.length === 0) {
|
||||
|
||||
const isAlphabeticalSorting = computed(() => {
|
||||
return params.value.sort_by.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
|
||||
})
|
||||
|
||||
const firstNewPosition = computed(() => {
|
||||
if (tasks.value.length === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return calculateItemPosition(null, this.tasks[0].position)
|
||||
},
|
||||
canWrite() {
|
||||
return this.list.maxRight > Rights.READ && this.list.id > 0
|
||||
},
|
||||
list() {
|
||||
return this.$store.state.currentList
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => (this.ctaVisible = true))
|
||||
},
|
||||
methods: {
|
||||
searchTasks() {
|
||||
return calculateItemPosition(null, tasks.value[0].position)
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const list = computed(() => store.state.currentList)
|
||||
|
||||
const canWrite = computed(() => {
|
||||
return list.value.maxRight > Rights.READ && list.value.id > 0
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
ctaVisible.value = true
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
function searchTasks() {
|
||||
// Only search if the search term changed
|
||||
if (this.$route.query === this.searchTerm) {
|
||||
if (route.query as unknown as string === searchTerm.value) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
router.push({
|
||||
name: 'list.list',
|
||||
query: {search: this.searchTerm},
|
||||
query: {search: searchTerm.value},
|
||||
})
|
||||
},
|
||||
hideSearchBar() {
|
||||
}
|
||||
|
||||
function hideSearchBar() {
|
||||
// This is a workaround.
|
||||
// When clicking on the search button, @blur from the input is fired. If we
|
||||
// would then directly hide the whole search bar directly, no click event
|
||||
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
||||
// everything so the button has a chance of firing the search event.
|
||||
setTimeout(() => {
|
||||
this.showTaskSearch = false
|
||||
showTaskSearch.value = false
|
||||
}, 200)
|
||||
},
|
||||
focusNewTaskInput() {
|
||||
this.$refs.addTask.focusTaskInput()
|
||||
},
|
||||
updateTaskList(task: ITask) {
|
||||
if ( this.isAlphabeticalSorting ) {
|
||||
}
|
||||
|
||||
const addTaskRef = ref<typeof AddTask | null>(null)
|
||||
function focusNewTaskInput() {
|
||||
addTaskRef.value?.focusTaskInput()
|
||||
}
|
||||
|
||||
function updateTaskList(task: ITask) {
|
||||
if (isAlphabeticalSorting.value ) {
|
||||
// reload tasks with current filter and sorting
|
||||
this.loadTasks(1, undefined, undefined, true)
|
||||
loadTasks(1, undefined, undefined, true)
|
||||
}
|
||||
else {
|
||||
this.tasks = [
|
||||
tasks.value = [
|
||||
task,
|
||||
...this.tasks,
|
||||
...tasks.value,
|
||||
]
|
||||
}
|
||||
|
||||
this.$store.commit(HAS_TASKS, true)
|
||||
},
|
||||
editTask(id: ITask['id']) {
|
||||
this.taskEditTask = {...this.tasks.find(t => t.id === parseInt(id))}
|
||||
this.isTaskEdit = true
|
||||
},
|
||||
updateTasks(updatedTask: ITask) {
|
||||
for (const t in this.tasks) {
|
||||
if (this.tasks[t].id === updatedTask.id) {
|
||||
this.tasks[t] = updatedTask
|
||||
store.commit(HAS_TASKS, true)
|
||||
}
|
||||
|
||||
function editTask(id: ITask['id']) {
|
||||
taskEditTask.value = {...tasks.value.find(t => t.id === Number(id))}
|
||||
isTaskEdit.value = true
|
||||
}
|
||||
|
||||
function updateTasks(updatedTask: ITask) {
|
||||
for (const t in tasks.value) {
|
||||
if (tasks.value[t].id === updatedTask.id) {
|
||||
tasks.value[t] = updatedTask
|
||||
break
|
||||
}
|
||||
}
|
||||
// FIXME: Use computed
|
||||
sortTasks(this.tasks)
|
||||
},
|
||||
sortTasks(tasks.value)
|
||||
}
|
||||
|
||||
async saveTaskPosition(e) {
|
||||
this.drag = false
|
||||
async function saveTaskPosition(e) {
|
||||
drag.value = false
|
||||
|
||||
const task = this.tasks[e.newIndex]
|
||||
const taskBefore = this.tasks[e.newIndex - 1] ?? null
|
||||
const taskAfter = this.tasks[e.newIndex + 1] ?? null
|
||||
const task = tasks.value[e.newIndex]
|
||||
const taskBefore = tasks.value[e.newIndex - 1] ?? null
|
||||
const taskAfter = tasks.value[e.newIndex + 1] ?? null
|
||||
|
||||
const newTask = {
|
||||
...task,
|
||||
|
@ -317,9 +311,7 @@ export default defineComponent({
|
|||
|
||||
const updatedTask = await this.$store.dispatch('tasks/update', newTask)
|
||||
this.tasks[e.newIndex] = updatedTask
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
Loading…
Reference in a new issue