feat: implement modals with vue router 4 #816
|
@ -22,12 +22,9 @@
|
|||
|
||||
<router-view :route="routeWithModal"/>
|
||||
|
||||
<!-- TODO: is this still used? -->
|
||||
<router-view name="popup" v-slot="{ Component }">
|
||||
<transition name="modal">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
<transition name="modal">
|
||||
dpschen marked this conversation as resolved
Outdated
|
||||
<component v-if="currentModal" :is="currentModal" />
|
||||
</transition>
|
||||
|
||||
<a
|
||||
class="keyboard-shortcuts-button"
|
||||
|
@ -42,7 +39,7 @@
|
|||
</template>
|
||||
dpschen marked this conversation as resolved
Outdated
konrad
commented
I suppose this is used because we don't have a modal wrapper? Using "TaskDetailView" in the general content with component feels a little out of place IMHO 😅 I suppose this is used because we don't have a modal wrapper? Using "TaskDetailView" in the general content with component feels a little out of place IMHO 😅
dpschen
commented
See it like the router-view conponent of vue router: it's also not a view itself. regardless I just wanted to make the most simple change. I guess it should be renamed to something like modal-view and maybe even merged with modal 🤔 - but that can also happen later. See it like the router-view conponent of vue router: it's also not a view itself. regardless I just wanted to make the most simple change. I guess it should be renamed to something like modal-view and maybe even merged with modal 🤔 - but that can also happen later.
dpschen
commented
I removed the component As a result this is obsolete now =) I removed the component `task-detail-view-modal` in https://kolaente.dev/vikunja/frontend/commit/6827390b77ae6e186e7b0163651c19ca9a247d2f and was able to merge it with the modal itself.
As a result this is obsolete now =)
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {watch, computed} from 'vue'
|
||||
import {watch, computed, shallowRef, watchEffect} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
|
@ -64,7 +61,16 @@ function useRouteWithModal() {
|
|||
}
|
||||
})
|
||||
|
||||
return { routeWithModal }
|
||||
|
||||
|
||||
const currentModal = shallowRef(null)
|
||||
watchEffect(() => {
|
||||
currentModal.value = historyState.value.backgroundView
|
||||
? route.matched[0]?.components.default
|
||||
: null
|
||||
})
|
||||
|
||||
return { routeWithModal, currentModal }
|
||||
}
|
||||
|
||||
useRouteWithModal()
|
||||
|
|
|
@ -2,21 +2,22 @@
|
|||
<dropdown>
|
||||
<template v-if="isSavedFilter">
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
|
||||
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
</dropdown-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="list.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
|
@ -24,32 +25,32 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.background`, params: { listId: list.id } }"
|
||||
v-if="backgroundsEnabled"
|
||||
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
|
||||
icon="image"
|
||||
>
|
||||
{{ $t('menu.setBackground') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.share`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.duplicate`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
|
||||
icon="paste"
|
||||
>
|
||||
{{ $t('menu.duplicate') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
|
@ -63,7 +64,7 @@
|
|||
@change="sub => subscription = sub"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
|
@ -101,24 +102,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
backgroundsEnabled() {
|
||||
return this.$store.state.config.enabledBackgroundProviders !== null && this.$store.state.config.enabledBackgroundProviders.length > 0
|
||||
},
|
||||
listRoutePrefix() {
|
||||
let name = 'list'
|
||||
|
||||
|
||||
if (this.$route.name !== null && this.$route.name.startsWith('list.')) {
|
||||
// HACK: we should implement a better routing for the modals
|
||||
const settingsRoutes = ['edit', 'delete', 'archive', 'background', 'share', 'duplicate']
|
||||
const suffix = settingsRoutes.find((route) => this.$route.name.endsWith(`.settings.${route}`))
|
||||
name = this.$route.name.replace(`.settings.${suffix}`,'')
|
||||
}
|
||||
|
||||
if (this.isSavedFilter) {
|
||||
name = name.replace('list.', 'filter.')
|
||||
}
|
||||
|
||||
return `${name}.settings`
|
||||
return this.$store.state.config.enabledBackgroundProviders?.length > 0
|
||||
},
|
||||
isSavedFilter() {
|
||||
return getSavedFilterIdFromListId(this.list.id) > 0
|
||||
|
|
|
@ -16,7 +16,7 @@ export const getDefaultParams = () => ({
|
|||
/**
|
||||
* This mixin provides a base set of methods and properties to get tasks on a list.
|
||||
*/
|
||||
export function createTaskList(initTasks) {
|
||||
export function useTaskList(initTasks) {
|
||||
const taskCollectionService = ref(new TaskCollectionService())
|
||||
dpschen marked this conversation as resolved
Outdated
dpschen
commented
Maybe shallowReactive fits here better? Maybe shallowReactive fits here better?
|
||||
const loading = computed(() => taskCollectionService.value.loading)
|
||||
const totalPages = computed(() => taskCollectionService.value.totalPages)
|
||||
|
@ -70,12 +70,14 @@ export function createTaskList(initTasks) {
|
|||
tasks.value = await taskCollectionService.value.getAll(list, loadParams, page)
|
||||
currentPage.value = page
|
||||
loadedList.value = JSON.parse(JSON.stringify(currentList))
|
||||
|
||||
return tasks.value
|
||||
}
|
||||
|
||||
async function loadTasksForPage(query) {
|
||||
const { page, search } = query
|
||||
initTasks(params)
|
||||
await loadTasks(
|
||||
return await loadTasks(
|
||||
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
|
||||
typeof page === 'undefined' ? 1 : Number(page),
|
||||
search,
|
||||
|
|
|
@ -13,8 +13,8 @@ import DataExportDownload from '../views/user/DataExportDownload'
|
|||
// Tasks
|
||||
import ShowTasksInRangeComponent from '../views/tasks/ShowTasksInRange'
|
||||
import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth'
|
||||
import TaskDetailView from '../views/tasks/TaskDetailView'
|
||||
import ListNamespaces from '../views/namespaces/ListNamespaces'
|
||||
import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal'
|
||||
// Team Handling
|
||||
import ListTeamsComponent from '../views/teams/ListTeams'
|
||||
// Label Handling
|
||||
|
@ -29,6 +29,7 @@ import Kanban from '../views/list/views/Kanban'
|
|||
import List from '../views/list/views/List'
|
||||
import Gantt from '../views/list/views/Gantt'
|
||||
import Table from '../views/list/views/Table'
|
||||
|
||||
// List Settings
|
||||
import ListSettingEdit from '../views/list/settings/edit'
|
||||
import ListSettingBackground from '../views/list/settings/background'
|
||||
|
@ -200,109 +201,123 @@ const router = createRouter({
|
|||
{
|
||||
path: '/namespaces/new',
|
||||
name: 'namespace.create',
|
||||
components: {
|
||||
popup: NewNamespaceComponent,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/list',
|
||||
name: 'list.create',
|
||||
components: {
|
||||
popup: NewListComponent,
|
||||
component: NewNamespaceComponent,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/edit',
|
||||
name: 'namespace.settings.edit',
|
||||
components: {
|
||||
popup: NamespaceSettingEdit,
|
||||
component: NamespaceSettingEdit,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/share',
|
||||
name: 'namespace.settings.share',
|
||||
components: {
|
||||
popup: NamespaceSettingShare,
|
||||
component: NamespaceSettingShare,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/archive',
|
||||
name: 'namespace.settings.archive',
|
||||
components: {
|
||||
popup: NamespaceSettingArchive,
|
||||
component: NamespaceSettingArchive,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/delete',
|
||||
name: 'namespace.settings.delete',
|
||||
components: {
|
||||
popup: NamespaceSettingDelete,
|
||||
component: NamespaceSettingDelete,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/tasks/:id',
|
||||
name: 'task.detail',
|
||||
component: TaskDetailView,
|
||||
component: TaskDetailViewModal,
|
||||
},
|
||||
{
|
||||
path: '/tasks/by/upcoming',
|
||||
name: 'tasks.range',
|
||||
component: ShowTasksInRangeComponent,
|
||||
},
|
||||
{
|
||||
path: '/lists/:id/new',
|
||||
name: 'list.create',
|
||||
component: NewListComponent,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'list.settings.edit',
|
||||
components: {
|
||||
popup: ListSettingEdit,
|
||||
component: ListSettingEdit,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/background',
|
||||
name: 'list.settings.background',
|
||||
components: {
|
||||
popup: ListSettingBackground,
|
||||
component: ListSettingBackground,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/duplicate',
|
||||
name: 'list.settings.duplicate',
|
||||
components: {
|
||||
popup: ListSettingDuplicate,
|
||||
component: ListSettingDuplicate,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/share',
|
||||
name: 'list.settings.share',
|
||||
components: {
|
||||
popup: ListSettingShare,
|
||||
component: ListSettingShare,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'list.settings.delete',
|
||||
components: {
|
||||
popup: ListSettingDelete,
|
||||
component: ListSettingDelete,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/archive',
|
||||
name: 'list.settings.archive',
|
||||
components: {
|
||||
popup: ListSettingArchive,
|
||||
component: ListSettingArchive,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/edit',
|
||||
name: 'filter.settings.edit',
|
||||
components: {
|
||||
popup: FilterEdit,
|
||||
component: FilterEdit,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/settings/delete',
|
||||
name: 'filter.settings.delete',
|
||||
components: {
|
||||
popup: FilterDelete,
|
||||
component: FilterDelete,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -340,8 +355,9 @@ const router = createRouter({
|
|||
{
|
||||
path: '/teams/new',
|
||||
name: 'teams.create',
|
||||
components: {
|
||||
popup: NewTeamComponent,
|
||||
component: NewTeamComponent,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -357,8 +373,9 @@ const router = createRouter({
|
|||
{
|
||||
path: '/labels/new',
|
||||
name: 'labels.create',
|
||||
components: {
|
||||
popup: NewLabelComponent,
|
||||
component: NewLabelComponent,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -374,8 +391,9 @@ const router = createRouter({
|
|||
{
|
||||
path: '/filters/new',
|
||||
name: 'filters.create',
|
||||
components: {
|
||||
popup: FilterNew,
|
||||
component: FilterNew,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {HTTPFactory} from '@/http-common'
|
||||
import {getCurrentLanguage, saveLanguage} from '@/i18n'
|
||||
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
||||
import {LOADING} from '../mutation-types'
|
||||
import UserModel from '@/models/user'
|
||||
import UserSettingsService from '@/services/userSettings'
|
||||
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
||||
import {setLoading} from '@/store/helper'
|
||||
import {i18n} from '@/i18n'
|
||||
import {success} from '@/message'
|
||||
import {redirectToProvider} from '@/helpers/redirectToProvider'
|
||||
|
||||
const AUTH_TYPES = {
|
||||
'UNKNOWN': 0,
|
||||
|
@ -201,7 +201,19 @@ export default {
|
|||
ctx.commit('authenticated', authenticated)
|
||||
if (!authenticated) {
|
||||
ctx.commit('info', null)
|
||||
ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
|
||||
ctx.dispatch('redirectToProviderIfNothingElseIsEnabled')
|
||||
}
|
||||
},
|
||||
|
||||
redirectToProviderIfNothingElseIsEnabled({rootState}) {
|
||||
const {auth} = rootState.config
|
||||
if (
|
||||
auth.local.enabled === false &&
|
||||
auth.openidConnect.enabled &&
|
||||
auth.openidConnect.providers?.length === 1 &&
|
||||
window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
|
||||
) {
|
||||
redirectToProvider(auth.openidConnect.providers[0], auth.openidConnect.redirectUrl)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {CONFIG} from '../mutation-types'
|
||||
import {HTTPFactory} from '@/http-common'
|
||||
import {objectToCamelCase} from '@/helpers/case'
|
||||
import {redirectToProvider} from '../../helpers/redirectToProvider'
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
export default {
|
||||
|
@ -75,16 +74,5 @@ export default {
|
|||
ctx.commit(CONFIG, info)
|
||||
return info
|
||||
},
|
||||
|
||||
redirectToProviderIfNothingElseIsEnabled(ctx) {
|
||||
if (ctx.state.auth.local.enabled === false &&
|
||||
ctx.state.auth.openidConnect.enabled &&
|
||||
ctx.state.auth.openidConnect.providers &&
|
||||
ctx.state.auth.openidConnect.providers.length === 1 &&
|
||||
window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
|
||||
) {
|
||||
redirectToProvider(ctx.state.auth.openidConnect.providers[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -51,10 +51,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<ShowTasks class="mt-4" :show-all="true" v-if="hasLists" :key="showTasksKey"/>
|
||||
|
||||
<transition name="modal">
|
||||
<task-detail-view-modal v-if="showTaskDetail" />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -71,9 +67,6 @@ import {getHistory} from '@/modules/listHistory'
|
|||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {useDateTimeSalutation} from '@/composables/useDateTimeSalutation'
|
||||
import TaskDetailViewModal, { useShowModal } from '@/views/tasks/TaskDetailViewModal.vue'
|
||||
|
||||
const showTaskDetail = useShowModal()
|
||||
|
||||
const welcome = useDateTimeSalutation()
|
||||
|
||||
|
|
|
@ -9,153 +9,128 @@
|
|||
v-shortcut="'g l'"
|
||||
:title="$t('keyboardShortcuts.list.switchToListView')"
|
||||
:class="{'is-active': $route.name === 'list.list'}"
|
||||
:to="{ name: 'list.list', params: { listId: listId } }">
|
||||
:to="{ name: 'list.list', params: { listId } }">
|
||||
{{ $t('list.list.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g g'"
|
||||
:title="$t('keyboardShortcuts.list.switchToGanttView')"
|
||||
:class="{'is-active': $route.name === 'list.gantt'}"
|
||||
:to="{ name: 'list.gantt', params: { listId: listId } }">
|
||||
:to="{ name: 'list.gantt', params: { listId } }">
|
||||
{{ $t('list.gantt.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g t'"
|
||||
:title="$t('keyboardShortcuts.list.switchToTableView')"
|
||||
:class="{'is-active': $route.name === 'list.table'}"
|
||||
:to="{ name: 'list.table', params: { listId: listId } }">
|
||||
:to="{ name: 'list.table', params: { listId } }">
|
||||
{{ $t('list.table.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g k'"
|
||||
:title="$t('keyboardShortcuts.list.switchToKanbanView')"
|
||||
:class="{'is-active': $route.name === 'list.kanban'}"
|
||||
:to="{ name: 'list.kanban', params: { listId: listId } }">
|
||||
:to="{ name: 'list.kanban', params: { listId } }">
|
||||
{{ $t('list.kanban.title') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<message variant="warning" v-if="currentList.isArchived" class="mb-4">
|
||||
<Message variant="warning" v-if="currentList.isArchived" class="mb-4">
|
||||
{{ $t('list.archived') }}
|
||||
</message>
|
||||
</Message>
|
||||
</transition>
|
||||
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import {ref, shallowRef, computed, watchEffect} from 'vue'
|
||||
import {useRouter, useRoute} from 'vue-router'
|
||||
|
||||
import Message from '@/components/misc/message'
|
||||
import ListModel from '../../models/list'
|
||||
import ListService from '../../services/list'
|
||||
import {CURRENT_LIST} from '../../store/mutation-types'
|
||||
import {getListView} from '../../helpers/saveListView'
|
||||
import {saveListToHistory} from '../../modules/listHistory'
|
||||
|
||||
export default {
|
||||
components: {Message},
|
||||
data() {
|
||||
return {
|
||||
listService: new ListService(),
|
||||
listLoaded: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// call again the method if the route changes
|
||||
'$route.path': {
|
||||
handler: 'loadList',
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// Computed property to let "listId" always have a value
|
||||
listId() {
|
||||
return typeof this.$route.params.listId === 'undefined' ? 0 : this.$route.params.listId
|
||||
},
|
||||
background() {
|
||||
return this.$store.state.background
|
||||
},
|
||||
currentList() {
|
||||
return typeof this.$store.state.currentList === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
isArchived: false,
|
||||
} : this.$store.state.currentList
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
replaceListView() {
|
||||
const savedListView = getListView(this.$route.params.listId)
|
||||
this.$router.replace({name: savedListView, params: {id: this.$route.params.listId}})
|
||||
console.debug('Replaced list view with', savedListView)
|
||||
},
|
||||
import ListModel from '@/models/list'
|
||||
import ListService from '@/services/list'
|
||||
|
||||
async loadList() {
|
||||
if (this.$route.name.includes('.settings.')) {
|
||||
return
|
||||
}
|
||||
import {store} from '@/store'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
|
||||
const listData = {id: parseInt(this.$route.params.listId)}
|
||||
import {getListView} from '@/helpers/saveListView'
|
||||
import {getListTitle} from '@/helpers/getListTitle'
|
||||
import {saveListToHistory} from '@/modules/listHistory'
|
||||
import { useTitle } from '@/composables/useTitle'
|
||||
|
||||
saveListToHistory(listData)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
this.setTitle(this.currentList.id ? this.getListTitle(this.currentList) : '')
|
||||
const listService = shallowRef(new ListService())
|
||||
const loadedListId = ref(0)
|
||||
|
||||
// This invalidates the loaded list at the kanban board which lets it reload its content when
|
||||
// switched to it. This ensures updates done to tasks in the gantt or list views are consistently
|
||||
// shown in all views while preventing reloads when closing a task popup.
|
||||
// We don't do this for the table view because that does not change tasks.
|
||||
if (
|
||||
this.$route.name === 'list.list' ||
|
||||
this.$route.name === 'list.gantt'
|
||||
) {
|
||||
this.$store.commit('kanban/setListId', 0)
|
||||
}
|
||||
// beforeRouteEnter(to) {
|
||||
// Redirect the user to list view by default
|
||||
if (route.name !== 'list.index') {
|
||||
const savedListView = getListView(route.params.listId)
|
||||
console.debug('Replaced list view with', savedListView)
|
||||
router.replace({name: 'list.list', params: {id: route.params.listId}})
|
||||
}
|
||||
// },
|
||||
|
||||
// // When clicking again on a list in the menu, there would be no list view selected which means no list
|
||||
// // at all. Users will then have to click on the list view menu again which is quite confusing.
|
||||
// if (this.$route.name === 'list.index') {
|
||||
// return this.replaceListView()
|
||||
// }
|
||||
const currentList = computed(() => {
|
||||
return typeof store.state.currentList === 'undefined' ? {
|
||||
id: 0,
|
||||
title: '',
|
||||
isArchived: false,
|
||||
} : store.state.currentList
|
||||
})
|
||||
|
||||
// Don't load the list if we either already loaded it or aren't dealing with a list at all currently and
|
||||
// the currently loaded list has the right set.
|
||||
if (
|
||||
(
|
||||
this.$route.params.listId === this.listLoaded ||
|
||||
typeof this.$route.params.listId === 'undefined' ||
|
||||
this.$route.params.listId === this.currentList.id ||
|
||||
parseInt(this.$route.params.listId) === this.currentList.id
|
||||
)
|
||||
&& typeof this.currentList !== 'undefined' && this.currentList.maxRight !== null
|
||||
) {
|
||||
return
|
||||
}
|
||||
// Computed property to let "listId" always have a value
|
||||
const listId = computed(() => typeof route.params.listId === 'undefined' ? 0 : parseInt(route.params.listId))
|
||||
// call again the method if the listId changes
|
||||
watchEffect(() => loadList(listId.value))
|
||||
|
||||
dpschen
commented
Remove this Remove this
|
||||
// Redirect the user to list view by default
|
||||
if (
|
||||
this.$route.name !== 'list.list' &&
|
||||
this.$route.name !== 'list.gantt' &&
|
||||
this.$route.name !== 'list.table' &&
|
||||
this.$route.name !== 'list.kanban'
|
||||
) {
|
||||
return this.replaceListView()
|
||||
}
|
||||
useTitle(() => currentList.value.id ? getListTitle(currentList.value) : '')
|
||||
|
||||
console.debug(`Loading list, $route.name = ${this.$route.name}, $route.params =`, this.$route.params, `, listLoaded = ${this.listLoaded}, currentList = `, this.currentList)
|
||||
async function loadList(listId) {
|
||||
const listData = {id: listId}
|
||||
saveListToHistory(listData)
|
||||
|
||||
// We create an extra list object instead of creating it in this.list because that would trigger a ui update which would result in bad ux.
|
||||
const list = new ListModel(listData)
|
||||
try {
|
||||
const loadedList = await this.listService.get(list)
|
||||
await this.$store.dispatch(CURRENT_LIST, loadedList)
|
||||
this.setTitle(this.getListTitle(loadedList))
|
||||
} finally {
|
||||
this.listLoaded = this.$route.params.listId
|
||||
}
|
||||
},
|
||||
},
|
||||
// This invalidates the loaded list at the kanban board which lets it reload its content when
|
||||
// switched to it. This ensures updates done to tasks in the gantt or list views are consistently
|
||||
// shown in all views while preventing reloads when closing a task popup.
|
||||
// We don't do this for the table view because that does not change tasks.
|
||||
// FIXME: remove this
|
||||
if (
|
||||
route.name === 'list.list' ||
|
||||
route.name === 'list.gantt'
|
||||
) {
|
||||
store.commit('kanban/setListId', 0)
|
||||
}
|
||||
|
||||
// Don't load the list if we either already loaded it or aren't dealing with a list at all currently and
|
||||
// the currently loaded list has the right set.
|
||||
if (
|
||||
(
|
||||
listId.value === loadedListId.value ||
|
||||
typeof listId.value === 'undefined' ||
|
||||
listId.value === currentList.value.id
|
||||
)
|
||||
&& typeof currentList.value !== 'undefined' && currentList.value.maxRight !== null
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
console.debug(`Loading list, $route.name = ${route.name}, $route.params =`, route.params, `, loadedListId = ${loadedListId.value}, currentList = `, currentList.value)
|
||||
|
||||
// We create an extra list object instead of creating it in list.value because that would trigger a ui update which would result in bad ux.
|
||||
const list = new ListModel(listData)
|
||||
try {
|
||||
const loadedList = await listService.value.get(list)
|
||||
await store.dispatch(CURRENT_LIST, loadedList)
|
||||
} finally {
|
||||
loadedListId.value = listId
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -52,61 +52,43 @@
|
|||
:show-taskswithout-dates="showTaskswithoutDates"
|
||||
/>
|
||||
|
||||
<transition name="modal">
|
||||
<task-detail-view-modal v-if="showTaskDetail" />
|
||||
</transition>
|
||||
</card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GanttChart from '../../../components/tasks/gantt-component'
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import Fancycheckbox from '../../../components/input/fancycheckbox'
|
||||
|
||||
import { i18n } from '@/i18n'
|
||||
import { store } from '@/store'
|
||||
|
||||
import GanttChart from '@/components/tasks/gantt-component'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
|
||||
import TaskDetailViewModal, { useShowModal } from '@/views/tasks/TaskDetailViewModal.vue'
|
||||
const route = useRoute()
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
saveListView(route.params.listId, route.name)
|
||||
|
||||
export default {
|
||||
name: 'Gantt',
|
||||
components: {
|
||||
Fancycheckbox,
|
||||
flatPickr,
|
||||
GanttChart,
|
||||
TaskDetailViewModal,
|
||||
const showTaskswithoutDates = ref(false)
|
||||
const dayWidth = ref(35)
|
||||
const dateFrom = ref(new Date((new Date()).setDate((new Date()).getDate() - 15)))
|
||||
const dateTo = ref(new Date((new Date()).setDate((new Date()).getDate() + 30)))
|
||||
|
||||
|
||||
const flatPickerConfig = computed(() => ({
|
||||
altFormat: i18n.global.t('date.altFormatShort'),
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d',
|
||||
enableTime: false,
|
||||
locale: {
|
||||
firstDayOfWeek: store.state.auth.settings.weekStart,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
showTaskDetail: useShowModal(),
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
saveListView(this.$route.params.listId, this.$route.name)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showTaskswithoutDates: false,
|
||||
dayWidth: 35,
|
||||
dateFrom: new Date((new Date()).setDate((new Date()).getDate() - 15)),
|
||||
dateTo: new Date((new Date()).setDate((new Date()).getDate() + 30)),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
flatPickerConfig() {
|
||||
return {
|
||||
altFormat: this.$t('date.altFormatShort'),
|
||||
altInput: true,
|
||||
dateFormat: 'Y-m-d',
|
||||
enableTime: false,
|
||||
locale: {
|
||||
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -205,9 +205,8 @@
|
|||
</div>
|
||||
|
||||
<transition name="modal">
|
||||
<task-detail-view-modal v-if="showTaskDetail" />
|
||||
<modal
|
||||
v-else-if="showBucketDeleteModal"
|
||||
v-if="showBucketDeleteModal"
|
||||
@close="showBucketDeleteModal = false"
|
||||
@submit="deleteBucket()"
|
||||
>
|
||||
|
@ -236,7 +235,6 @@ import Dropdown from '@/components/misc/dropdown.vue'
|
|||
import {getCollapsedBucketState, saveCollapsedBucketState} from '@/helpers/saveCollapsedBucketState'
|
||||
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
|
||||
import KanbanCard from '@/components/tasks/partials/kanban-card'
|
||||
import TaskDetailViewModal, { useShowModal } from '@/views/tasks/TaskDetailViewModal.vue'
|
||||
|
||||
const DRAG_OPTIONS = {
|
||||
// sortable options
|
||||
|
@ -256,7 +254,6 @@ export default {
|
|||
Dropdown,
|
||||
FilterPopup,
|
||||
draggable,
|
||||
TaskDetailViewModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -293,12 +290,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
showTaskDetail: useShowModal(),
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
|
|
|
@ -122,10 +122,6 @@
|
|||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
|
||||
<transition name="modal">
|
||||
<task-detail-view-modal v-if="showTaskDetail" />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -133,13 +129,10 @@
|
|||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import TaskService from '../../../services/task'
|
||||
import TaskModel from '../../../models/task'
|
||||
|
||||
import EditTask from '../../../components/tasks/edit-task'
|
||||
import AddTask from '../../../components/tasks/add-task'
|
||||
import SingleTaskInList from '../../../components/tasks/partials/singleTaskInList'
|
||||
import { createTaskList } from '@/composables/taskList'
|
||||
import { useTaskList } from '@/composables/taskList'
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
import Rights from '../../../models/constants/rights.json'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
|
@ -147,7 +140,6 @@ 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 TaskDetailViewModal, { useShowModal } from '@/views/tasks/TaskDetailViewModal.vue'
|
||||
|
||||
import draggable from 'vuedraggable'
|
||||
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
|
||||
|
@ -174,7 +166,6 @@ export default {
|
|||
name: 'List',
|
||||
data() {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
ctaVisible: false,
|
||||
showTaskSearch: false,
|
||||
|
||||
|
@ -193,11 +184,10 @@ export default {
|
|||
AddTask,
|
||||
draggable,
|
||||
Pagination,
|
||||
TaskDetailViewModal,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const taskEditTask = ref(TaskModel)
|
||||
const taskEditTask = ref(null)
|
||||
const isTaskEdit = ref(false)
|
||||
|
||||
// This function initializes the tasks page and loads the first page of tasks
|
||||
|
@ -206,17 +196,18 @@ export default {
|
|||
isTaskEdit.value = false
|
||||
}
|
||||
|
||||
const taskList = createTaskList(beforeLoad)
|
||||
const taskList = useTaskList(beforeLoad)
|
||||
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
const route = useRoute()
|
||||
saveListView(route.params.listId, route.name)
|
||||
|
||||
taskList.initTaskList()
|
||||
|
||||
return {
|
||||
taskEditTask,
|
||||
isTaskEdit,
|
||||
showTaskDetail: useShowModal(),
|
||||
...taskList,
|
||||
}
|
||||
},
|
||||
|
|
|
@ -68,19 +68,19 @@
|
|||
<tr>
|
||||
<th v-if="activeColumns.id">
|
||||
#
|
||||
<sort :order="sortBy.id" @click="sort('id')"/>
|
||||
<Sort :order="sortBy.id" @click="sort('id')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
<sort :order="sortBy.done" @click="sort('done')"/>
|
||||
<Sort :order="sortBy.done" @click="sort('done')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
<sort :order="sortBy.title" @click="sort('title')"/>
|
||||
<Sort :order="sortBy.title" @click="sort('title')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
<sort :order="sortBy.priority" @click="sort('priority')"/>
|
||||
<Sort :order="sortBy.priority" @click="sort('priority')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
|
@ -90,27 +90,27 @@
|
|||
</th>
|
||||
<th v-if="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
<sort :order="sortBy.due_date" @click="sort('due_date')"/>
|
||||
<Sort :order="sortBy.due_date" @click="sort('due_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
<sort :order="sortBy.start_date" @click="sort('start_date')"/>
|
||||
<Sort :order="sortBy.start_date" @click="sort('start_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
<sort :order="sortBy.end_date" @click="sort('end_date')"/>
|
||||
<Sort :order="sortBy.end_date" @click="sort('end_date')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
<sort :order="sortBy.percent_done" @click="sort('percent_done')"/>
|
||||
<Sort :order="sortBy.percent_done" @click="sort('percent_done')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
<sort :order="sortBy.created" @click="sort('created')"/>
|
||||
<Sort :order="sortBy.created" @click="sort('created')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
<sort :order="sortBy.updated" @click="sort('updated')"/>
|
||||
<Sort :order="sortBy.updated" @click="sort('updated')"/>
|
||||
</th>
|
||||
<th v-if="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
|
@ -173,22 +173,13 @@
|
|||
:current-page="currentPage"
|
||||
/>
|
||||
</card>
|
||||
|
||||
<!-- This router view is used to show the task popup while keeping the table view itself -->
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="modal">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, reactive, computed, toRaw } from 'vue'
|
||||
<script setup>
|
||||
import { ref, reactive, computed, toRaw } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
import { createTaskList } from '@/composables/taskList'
|
||||
import Done from '@/components/misc/Done.vue'
|
||||
import User from '@/components/misc/user'
|
||||
import PriorityLabel from '@/components/tasks/partials/priorityLabel'
|
||||
|
@ -196,11 +187,13 @@ import Labels from '@/components/tasks/partials/labels'
|
|||
import DateTableCell from '@/components/tasks/partials/date-table-cell'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||
import Sort from '@/components/tasks/partials/sort'
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
import Pagination from '@/components/misc/pagination.vue'
|
||||
import Popup from '@/components/misc/popup'
|
||||
|
||||
import { useTaskList } from '@/composables/taskList'
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
|
||||
const ACTIVE_COLUMNS_DEFAULT = {
|
||||
id: true,
|
||||
done: true,
|
||||
|
@ -233,102 +226,86 @@ function useSavedView(activeColumns, sortBy) {
|
|||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
components: {
|
||||
Popup,
|
||||
Done,
|
||||
FilterPopup,
|
||||
Sort,
|
||||
Fancycheckbox,
|
||||
DateTableCell,
|
||||
Labels,
|
||||
PriorityLabel,
|
||||
User,
|
||||
Pagination,
|
||||
},
|
||||
setup() {
|
||||
const activeColumns = reactive({ ...ACTIVE_COLUMNS_DEFAULT })
|
||||
const sortBy = ref({ ...SORT_BY_DEFAULT })
|
||||
const activeColumns = reactive({ ...ACTIVE_COLUMNS_DEFAULT })
|
||||
const sortBy = ref({ ...SORT_BY_DEFAULT })
|
||||
|
||||
useSavedView(activeColumns, sortBy)
|
||||
useSavedView(activeColumns, sortBy)
|
||||
|
||||
function beforeLoad(params) {
|
||||
// This makes sure an id sort order is always sorted last.
|
||||
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
|
||||
// precedence over everything else, making any other sort columns pretty useless.
|
||||
let hasIdFilter = false
|
||||
const sortKeys = Object.keys(sortBy.value)
|
||||
for (const s of sortKeys) {
|
||||
if (s === 'id') {
|
||||
sortKeys.splice(s, 1)
|
||||
hasIdFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasIdFilter) {
|
||||
sortKeys.push('id')
|
||||
}
|
||||
params.value.sort_by = sortKeys
|
||||
params.value.order_by = sortKeys.map(s => sortBy.value[s])
|
||||
function beforeLoad(params) {
|
||||
// This makes sure an id sort order is always sorted last.
|
||||
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
|
||||
// precedence over everything else, making any other sort columns pretty useless.
|
||||
let hasIdFilter = false
|
||||
const sortKeys = Object.keys(sortBy.value)
|
||||
for (const s of sortKeys) {
|
||||
if (s === 'id') {
|
||||
sortKeys.splice(s, 1)
|
||||
hasIdFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasIdFilter) {
|
||||
sortKeys.push('id')
|
||||
}
|
||||
params.value.sort_by = sortKeys
|
||||
params.value.order_by = sortKeys.map(s => sortBy.value[s])
|
||||
}
|
||||
|
||||
const taskList = createTaskList(beforeLoad)
|
||||
const {
|
||||
tasks,
|
||||
loading,
|
||||
showTaskFilter,
|
||||
params,
|
||||
loadTasks,
|
||||
totalPages,
|
||||
currentPage,
|
||||
searchTerm,
|
||||
initTaskList,
|
||||
} = useTaskList(beforeLoad)
|
||||
|
||||
Object.assign(taskList.params.value, {
|
||||
filter_by: [],
|
||||
filter_value: [],
|
||||
filter_comparator: [],
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const taskDetailRoutes = computed(() => Object.fromEntries(
|
||||
taskList.tasks.value.map(({id}) => ([
|
||||
id,
|
||||
{
|
||||
name: 'task.detail',
|
||||
params: { id },
|
||||
state: { backgroundView: router.currentRoute.value.fullPath },
|
||||
},
|
||||
])),
|
||||
))
|
||||
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
const route = useRoute()
|
||||
saveListView(route.params.listId, route.name)
|
||||
|
||||
function sort(property) {
|
||||
const order = sortBy.value[property]
|
||||
if (typeof order === 'undefined' || order === 'none') {
|
||||
sortBy.value[property] = 'desc'
|
||||
} else if (order === 'desc') {
|
||||
sortBy.value[property] = 'asc'
|
||||
} else {
|
||||
delete sortBy.value[property]
|
||||
}
|
||||
beforeLoad(taskList.currentPage.value, taskList.searchTerm.value)
|
||||
// Save the order to be able to retrieve them later
|
||||
localStorage.setItem('tableViewSortBy', JSON.stringify(sortBy.value))
|
||||
}
|
||||
|
||||
function saveTaskColumns() {
|
||||
localStorage.setItem('tableViewColumns', JSON.stringify(toRaw(activeColumns)))
|
||||
}
|
||||
|
||||
taskList.initTaskList()
|
||||
|
||||
return {
|
||||
...taskList,
|
||||
sortBy,
|
||||
activeColumns,
|
||||
sort,
|
||||
saveTaskColumns,
|
||||
taskDetailRoutes,
|
||||
}
|
||||
},
|
||||
Object.assign(params.value, {
|
||||
filter_by: [],
|
||||
filter_value: [],
|
||||
filter_comparator: [],
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const taskDetailRoutes = computed(() => Object.fromEntries(
|
||||
tasks.value.map(({id}) => ([
|
||||
id,
|
||||
{
|
||||
name: 'task.detail',
|
||||
params: { id },
|
||||
state: { backgroundView: router.currentRoute.value.fullPath },
|
||||
},
|
||||
])),
|
||||
))
|
||||
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
const route = useRoute()
|
||||
saveListView(route.params.listId, route.name)
|
||||
|
||||
function sort(property) {
|
||||
const order = sortBy.value[property]
|
||||
if (typeof order === 'undefined' || order === 'none') {
|
||||
sortBy.value[property] = 'desc'
|
||||
} else if (order === 'desc') {
|
||||
sortBy.value[property] = 'asc'
|
||||
} else {
|
||||
delete sortBy.value[property]
|
||||
}
|
||||
beforeLoad(currentPage.value, searchTerm.value)
|
||||
// Save the order to be able to retrieve them later
|
||||
localStorage.setItem('tableViewSortBy', JSON.stringify(sortBy.value))
|
||||
}
|
||||
|
||||
function saveTaskColumns() {
|
||||
localStorage.setItem('tableViewColumns', JSON.stringify(toRaw(activeColumns)))
|
||||
}
|
||||
|
||||
initTaskList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -4,35 +4,19 @@
|
|||
variant="scrolling"
|
||||
class="task-detail-view-modal"
|
||||
>
|
||||
<a @click="close()" class="close">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
<task-detail-view/>
|
||||
<a @click="close()" class="close">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
<task-detail-view/>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import TaskDetailView from './TaskDetailView'
|
||||
import {computed} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
import router from '@/router'
|
||||
|
||||
export function useShowModal() {
|
||||
const route = useRoute()
|
||||
const historyState = computed(() => route.fullPath && window.history.state)
|
||||
const show = computed(() => historyState.value.backgroundView)
|
||||
return show
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TaskDetailViewModal',
|
||||
components: {
|
||||
TaskDetailView,
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$router.back()
|
||||
},
|
||||
},
|
||||
function close() {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
Reference in New Issue
I don't think it is, we should make sure the modal views keep their transition though. Might make sense to include that in the modal component itself?
Either that or put the transition inside something like a provider component. In that we could use the new teleport component. I was always using portal-vue in vue 2 for this kind of stuff.
I think using the teleport component allows for a cleaner solution since there are situations where you want a transition handled by the route and others where you want to have it handled by the outer component (like delete modals).