feat: open modal automatically depending on route meta setting

This commit is contained in:
Dominik Pschenitschni 2022-11-23 17:55:28 +01:00
parent 2a63549be2
commit 65c176d18c
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
15 changed files with 178 additions and 85 deletions

View File

@ -16,7 +16,7 @@
{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}
</h1>
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}, state: {backdropRoutePath: $route.fullPath}}" class="info-button">
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id},}" class="info-button">
<icon icon="circle-info"/>
</BaseButton>
@ -75,7 +75,7 @@
{{ $t('keyboardShortcuts.title') }}
</dropdown-item>
<dropdown-item
:to="{name: 'about', state: {backdropRoutePath: $route.fullPath}}"
:to="{name: 'about',}"
>
{{ $t('about.title') }}
</dropdown-item>

View File

@ -10,13 +10,13 @@
<template v-if="isSavedFilter(list)">
<dropdown-item
:to="{ name: 'filter.settings.edit', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'filter.settings.delete', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
icon="trash-alt"
>
{{ $t('misc.delete') }}
@ -25,7 +25,7 @@
<template v-else-if="list.isArchived">
<dropdown-item
:to="{ name: 'list.settings.archive', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
@ -33,32 +33,32 @@
</template>
<template v-else>
<dropdown-item
:to="{ name: 'list.settings.edit', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
v-if="backgroundsEnabled"
:to="{ name: 'list.settings.background', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
icon="image"
>
{{ $t('menu.setBackground') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.share', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.duplicate', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
icon="paste"
>
{{ $t('menu.duplicate') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.settings.archive', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
@ -73,7 +73,7 @@
type="dropdown"
/>
<dropdown-item
:to="{ name: 'list.settings.delete', params: { listId: list.id }, state: {backdropRoutePath: $route.fullPath} }"
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
icon="trash-alt"
class="has-text-danger"
>

View File

@ -10,7 +10,7 @@
<template v-if="namespace.isArchived">
<dropdown-item
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
@ -18,25 +18,25 @@
</template>
<template v-else>
<dropdown-item
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.create', params: { namespaceId: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
icon="plus"
>
{{ $t('menu.newList') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
@ -51,7 +51,7 @@
type="dropdown"
/>
<dropdown-item
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
icon="trash-alt"
class="has-text-danger"
>

View File

@ -153,7 +153,6 @@ function openTask(e: {
router.push({
name: 'task.detail',
params: {id: e.bar.ganttBarConfig.id},
state: {backdropRoutePath: router.currentRoute.value.fullPath},
})
}

View File

@ -121,7 +121,6 @@ function openTaskDetail() {
router.push({
name: 'task.detail',
params: {id: props.task.id},
state: {backdropRoutePath: router.currentRoute.value.fullPath},
})
}

View File

@ -13,11 +13,7 @@
/>
<router-link
:to="{
name: 'task.detail',
params: {id: task.id},
state: { backdropRoutePath: $route.fullPath },
}"
:to="{name: 'task.detail', params: {id: task.id}}"
:class="{ 'done': task.done}"
class="tasktext"
>

View File

@ -1,6 +1,6 @@
import {computed, shallowRef, watch, h, type VNode, ref} from 'vue'
import {useRoute, useRouter, loadRouteLocation, type RouteLocationNormalizedLoaded, type RouteLocationRaw, START_LOCATION, type RouteLocationNormalized} from 'vue-router'
import router from '@/router'
import {useRoute, useRouter, loadRouteLocation, type RouteLocationNormalizedLoaded, type RouteLocationRaw, START_LOCATION} from 'vue-router'
import router, { handleRedirectRecord } from '@/router'
// this is adapted from vue-router
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
@ -16,7 +16,14 @@ function getRouteProps(route: RouteLocationNormalizedLoaded) {
}
function resolveAndLoadRoute(route: RouteLocationRaw, currentLocation?: RouteLocationNormalizedLoaded) {
return loadRouteLocation(router.resolve(route, currentLocation))
const resolvedRoute = router.resolve(route, currentLocation)
// resolvedRoute.matched.forEach((record) => console.log(record.redirect))
// e.g. 'list.index' will always redirect, so we need to resolve the redirected route record
const redirectedRoute = handleRedirectRecord(resolvedRoute) || resolvedRoute
return loadRouteLocation(router.resolve(redirectedRoute, currentLocation))
}
export function useRouteWithModal() {
@ -31,33 +38,64 @@ export function useRouteWithModal() {
: undefined
})
const isInitialNavigation = ref(true)
router.beforeEach((to, from) => {
isInitialNavigation.value = from === START_LOCATION
})
const lastBackdropRoutePath = ref<string>()
router.afterEach((to, from) => {
const resolvedRoute = router.resolve(to)
// if (!resolvedRoute.meta.forceModal) {
if (!resolvedRoute.meta.modal) {
// this route doesn't define that it can be a modal
return
}
let routeNormalized: RouteLocationNormalized
if (typeof resolvedRoute.meta?.modal === 'boolean' || resolvedRoute.meta.modal?.force !== true) {
// TODO: also check route history here
routeNormalized = from
let backdropRoutePath
if (resolvedRoute.meta?.modal === true || resolvedRoute.meta.modal?.force !== true) {
// this route can be shown as modal or as normal view
// TODO: add new state or prop instead of checking backdropRoutePath here
// e.g. `modal` with `'normal' | 'sidebar' | 'modal' | 'sheet'`
if (!history.state.backdropRoutePath) {
// since this modal is optional and we don't have a backdropRoutePath we don't do anything
return
}
} else if (lastBackdropRoutePath.value) {
// there was already a modal in the last route
// we have to save this in the lastBackdropRoutePath ref because
// the state gets overwritten
// let's show that backdropRoute again
// TODO: add support for multiple modal layers here
// FIXME: use `history.state.backdropRoutePath` from last route
// FIXME: this might be wrong, because we redirect to our current path forever??
backdropRoutePath = lastBackdropRoutePath.value
} else if (isInitialNavigation.value === false) {
backdropRoutePath = from.fullPath
} else if (resolvedRoute.meta.modal?.defaultBackdropRoute) {
const {defaultBackdropRoute} = resolvedRoute.meta.modal
const routeRaw = typeof defaultBackdropRoute === 'function'
? defaultBackdropRoute(resolvedRoute)
: defaultBackdropRoute
routeNormalized = router.resolve(routeRaw)
} else {
backdropRoutePath = router.resolve(routeRaw).fullPath
}
if (backdropRoutePath === undefined) {
console.log('No defaultBackdropRoute defined for this route')
// TODO: maybe load parent route here in the future,
// via: route.matched[route.matched.length - 2]
// see: https://router.vuejs.org/guide/migration/#removal-of-parent-from-route-locations
routeNormalized = router.resolve({ name: 'home' })
backdropRoutePath = router.resolve({ name: 'home' }).fullPath
}
lastBackdropRoutePath.value = backdropRoutePath
history.replaceState({
...history.state,
foobar: routeNormalized.fullPath,
backdropRoutePath,
}, '')
})
@ -140,7 +178,9 @@ export function useRouteWithModal() {
async function closeModal() {
await router.isReady()
if (backdropRoute.value !== undefined) {
if (isInitialNavigation.value === false) {
return router.back()
} else if (backdropRoute.value !== undefined) {
// TODO: Dialog modals might want to replace the route here via router.replace()
return router.push(backdropRoute.value)
}

View File

@ -87,7 +87,7 @@ declare module 'vue-router' {
/** Forces the view to be always shown as modal */
force?: boolean,
/** Show this route as backdrop if there is no route history */
defaultBackdropRoute?: RouteLocationRaw | ((currentRoute: RouteLocation) => RouteLocationRaw),
defaultBackdropRoute?: RouteLocationRaw | ((to: RouteLocationNormalized) => RouteLocationRaw),
}
}
}
@ -236,7 +236,10 @@ const router = createRouter({
name: 'namespace.settings.edit',
component: NamespaceSettingEdit,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
props: route => ({ namespaceId: Number(route.params.id as string) }),
},
@ -245,7 +248,10 @@ const router = createRouter({
name: 'namespace.settings.share',
component: NamespaceSettingShare,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
},
{
@ -253,7 +259,10 @@ const router = createRouter({
name: 'namespace.settings.archive',
component: NamespaceSettingArchive,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
props: route => ({ namespaceId: Number(route.params.id as string) }),
},
@ -262,7 +271,10 @@ const router = createRouter({
name: 'namespace.settings.delete',
component: NamespaceSettingDelete,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
props: route => ({ namespaceId: Number(route.params.id as string) }),
},
@ -274,7 +286,9 @@ const router = createRouter({
meta: {
modal: {
force: true,
defaultBackdropRoute: {name: 'home'},
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
@ -294,7 +308,10 @@ const router = createRouter({
name: 'list.create',
component: NewListComponent,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
},
{
@ -303,7 +320,12 @@ const router = createRouter({
component: ListSettingEdit,
props: route => ({ listId: Number(route.params.listId as string) }),
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -311,7 +333,12 @@ const router = createRouter({
name: 'list.settings.background',
component: ListSettingBackground,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -319,7 +346,12 @@ const router = createRouter({
name: 'list.settings.duplicate',
component: ListSettingDuplicate,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -327,7 +359,12 @@ const router = createRouter({
name: 'list.settings.share',
component: ListSettingShare,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -335,7 +372,12 @@ const router = createRouter({
name: 'list.settings.delete',
component: ListSettingDelete,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -343,7 +385,12 @@ const router = createRouter({
name: 'list.settings.archive',
component: ListSettingArchive,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
},
{
@ -351,7 +398,12 @@ const router = createRouter({
name: 'filter.settings.edit',
component: FilterEdit,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
@ -360,7 +412,12 @@ const router = createRouter({
name: 'filter.settings.delete',
component: FilterDelete,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
@ -369,7 +426,12 @@ const router = createRouter({
name: 'list.info',
component: ListInfo,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute(route) {
return { name: 'list.index', props: { listId: route.params.listId}}
},
},
},
props: route => ({ listId: Number(route.params.listId as string) }),
},
@ -436,9 +498,13 @@ const router = createRouter({
name: 'teams.create',
component: NewTeamComponent,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'teams.index'},
},
},
},
// TODO: make modal
{
path: '/teams/:id/edit',
name: 'teams.edit',
@ -454,7 +520,10 @@ const router = createRouter({
name: 'labels.create',
component: NewLabelComponent,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'labels.index'},
},
},
},
{
@ -476,7 +545,10 @@ const router = createRouter({
name: 'filters.create',
component: FilterNew,
meta: {
modal: true,
modal: {
force: true,
defaultBackdropRoute: {name: 'namespaces.index'},
},
},
},
{
@ -489,7 +561,9 @@ const router = createRouter({
name: 'about',
component: About,
meta: {
modal: true,
modal: {
force: true,
},
},
},
],

View File

@ -21,7 +21,7 @@
<template v-if="defaultNamespaceId > 0">
<p class="mt-4">{{ $t('home.list.newText') }}</p>
<x-button
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId }, state: { backdropRoutePath: $route.fullPath } }"
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId } }"
:shadow="false"
class="ml-2"
>

View File

@ -1,10 +1,7 @@
<template>
<div :class="{ 'is-loading': loading}" class="loader-container">
<x-button
:to="{
name:'labels.create',
state: { backdropRoutePath: $route.fullPath }
}"
:to="{ name:'labels.create' }"
class="is-pulled-right"
icon="plus"
>
@ -18,10 +15,7 @@
</p>
<p v-else class="has-text-centered has-text-grey is-italic">
{{ $t('label.newCTA') }}
<router-link :to="{
name:'labels.create',
state: { backdropRoutePath: $route.fullPath }
}">{{ $t('label.create.title') }}.</router-link>
<router-link :to="{name:'labels.create'}">{{ $t('label.create.title') }}.</router-link>
</p>
</div>

View File

@ -181,7 +181,6 @@
<script setup lang="ts">
import {toRef, computed, type Ref} from 'vue'
import {useRoute} from 'vue-router'
import {useStorage} from '@vueuse/core'
import ListWrapper from '@/components/list/ListWrapper.vue'
@ -274,14 +273,12 @@ function sort(property: keyof SortBy) {
sortByParam.value = sortBy.value
}
const route = useRoute()
const taskDetailRoutes = computed(() => Object.fromEntries(
tasks.value.map(({id}) => ([
id,
{
name: 'task.detail',
params: {id},
state: { backdropRoutePath: route.fullPath },
},
])),
))

View File

@ -5,7 +5,7 @@
:primary-label="$t('misc.save')"
@primary="save"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId }, state: {backdropRoutePath: $route.fullPath} })"
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId } })"
#default="{onClose}"
>
<div class="field">

View File

@ -6,10 +6,10 @@
</fancycheckbox>
<div class="action-buttons">
<x-button :to="{name: 'filters.create', state: {backdropRoutePath: $route.fullPath}}" icon="filter">
<x-button :to="{name: 'filters.create'}" icon="filter">
{{ $t('filters.create.title') }}
</x-button>
<x-button :to="{name: 'namespace.create', state: {backdropRoutePath: $route.fullPath}}" icon="plus" v-cy="'new-namespace'">
<x-button :to="{name: 'namespace.create'}" icon="plus" v-cy="'new-namespace'">
{{ $t('namespace.create.title') }}
</x-button>
</div>
@ -17,7 +17,7 @@
<p v-if="namespaces.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
{{ $t('namespace.noneAvailable') }}
<BaseButton :to="{name: 'namespace.create', state: {backdropRoutePath: $route.fullPath}}">
<BaseButton :to="{name: 'namespace.create'}">
{{ $t('namespace.create.title') }}.
</BaseButton>
</p>
@ -25,7 +25,7 @@
<section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
<x-button
v-if="n.id > 0 && n.lists.length > 0"
:to="{name: 'list.create', params: {namespaceId: n.id}, state: { backdropRoutePath: $route.fullPath }}"
:to="{name: 'list.create', params: {namespaceId: n.id}}"
class="is-pulled-right"
variant="secondary"
icon="plus"
@ -34,7 +34,7 @@
</x-button>
<x-button
v-if="n.isArchived"
:to="{name: 'namespace.settings.archive', params: {id: n.id}, state: { backdropRoutePath: $route.fullPath } }"
:to="{name: 'namespace.settings.archive', params: {id: n.id} }"
class="is-pulled-right mr-4"
variant="secondary"
icon="archive"
@ -51,7 +51,7 @@
<p v-if="n.lists.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
{{ $t('namespace.noLists') }}
<BaseButton :to="{name: 'list.create', params: {namespaceId: n.id}, state: { backdropRoutePath: $route.fullPath }}">
<BaseButton :to="{name: 'list.create', params: {namespaceId: n.id}}">
{{ $t('namespace.createList') }}
</BaseButton>
</p>

View File

@ -5,7 +5,7 @@
:primary-label="$t('misc.save')"
@primary="save"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id }, state: { backdropRoutePath: $route.fullPath } })"
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
#default="{onClose}"
>
<form @submit.prevent="save(onClose)">

View File

@ -1,10 +1,7 @@
<template>
<div class="content loader-container is-max-width-desktop" :class="{ 'is-loading': teamService.loading}">
<x-button
:to="{
name:'teams.create',
state: { backdropRoutePath: $route.fullPath },
}"
:to="{name:'teams.create'}"
class="is-pulled-right"
icon="plus"
>
@ -21,10 +18,7 @@
</ul>
<p v-else-if="!teamService.loading" class="has-text-centered has-text-grey is-italic">
{{ $t('team.noTeams') }}
<router-link :to="{
name: 'teams.create',
state: { backdropRoutePath: $route.fullPath },
}">
<router-link :to="{name: 'teams.create'}">
{{ $t('team.create.title') }}.
</router-link>
</p>