WIP: feat: route modals everywhere #2735
|
@ -5,12 +5,7 @@ export default { inheritAttrs: false }
|
|||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAttrs } from 'vue'
|
||||
|
||||
defineProps<{ is: any }>()
|
||||
|
||||
const attrs = useAttrs()
|
||||
console.log(JSON.parse(JSON.stringify(attrs)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -32,16 +32,14 @@
|
|||
|
||||
<quick-actions/>
|
||||
|
||||
<router-view :route="routeWithModal" v-slot="{ Component }">
|
||||
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
|
||||
<router-view v-if="routeWithModal" :route="routeWithModal" v-slot="{ Component }">
|
||||
<!-- <keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']"> -->
|
||||
<component :is="Component"/>
|
||||
</keep-alive>
|
||||
<!-- test -->
|
||||
<!-- </keep-alive> -->
|
||||
</router-view>
|
||||
|
||||
|
||||
<component
|
||||
:is="currentModal"
|
||||
@close="closeModal()"
|
||||
/>
|
||||
<component :is="currentModal" />
|
||||
|
||||
<BaseButton
|
||||
class="keyboard-shortcuts-button d-print-none"
|
||||
|
@ -69,7 +67,7 @@ import {useLabelStore} from '@/stores/labels'
|
|||
import {useRouteWithModal} from '@/composables/useRouteWithModal'
|
||||
import {useRenewTokenOnFocus} from '@/composables/useRenewTokenOnFocus'
|
||||
|
||||
const {routeWithModal, currentModal, closeModal} = useRouteWithModal()
|
||||
const {routeWithModal, currentModal} = useRouteWithModal()
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const background = computed(() => baseStore.background)
|
||||
|
|
|
@ -1,56 +1,112 @@
|
|||
import { computed, shallowRef, watchEffect, h, type VNode } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { computed, shallowRef, watch, h, type VNode, ref } from 'vue'
|
||||
import { useRoute, useRouter, loadRouteLocation, type RouteLocationNormalizedLoaded, type RouteLocationRaw } from 'vue-router'
|
||||
import router from '@/router'
|
||||
|
||||
// this is adapted from vue-router
|
||||
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
|
||||
function getRouteProps(route: RouteLocationNormalizedLoaded) {
|
||||
const routePropsOption = route.matched[0]?.props.default
|
||||
return routePropsOption
|
||||
? routePropsOption === true
|
||||
? route.params
|
||||
: typeof routePropsOption === 'function'
|
||||
? routePropsOption(route)
|
||||
: routePropsOption
|
||||
: {}
|
||||
}
|
||||
|
||||
function resolveAndLoadRoute(route: RouteLocationRaw) {
|
||||
return loadRouteLocation(router.resolve(route))
|
||||
}
|
||||
|
||||
export function useRouteWithModal() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const backdropView = computed(() => route.fullPath && window.history.state.backdropView)
|
||||
|
||||
const routeWithModal = computed(() => {
|
||||
return backdropView.value
|
||||
? router.resolve(backdropView.value)
|
||||
: route
|
||||
|
||||
const backdropView = computed<RouteLocationRaw | undefined>(() => {
|
||||
// every time the fullPath changes we check the history state
|
||||
// this happens also initially
|
||||
return route.fullPath
|
||||
? window.history.state?.backdropView
|
||||
: undefined
|
||||
})
|
||||
|
||||
const routerIsReady = ref(false)
|
||||
router.isReady().then(() => {
|
||||
routerIsReady.value = true
|
||||
})
|
||||
|
||||
const baseRoute = shallowRef<RouteLocationNormalizedLoaded>()
|
||||
watch(
|
||||
[backdropView, routerIsReady],
|
||||
async () => {
|
||||
if (routerIsReady.value === false || !route.fullPath) {
|
||||
return
|
||||
}
|
||||
|
||||
if (backdropView.value === undefined) {
|
||||
dpschen
commented
Something was broken here: since we checked for Something was broken here: since we checked for `historyState.value` in the `if` condition it could never have a value in the `else` cause.
|
||||
if (route.meta?.showAsModal !== true) {
|
||||
baseRoute.value = route
|
||||
return
|
||||
}
|
||||
// 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
|
||||
baseRoute.value = await resolveAndLoadRoute({ name: 'home' })
|
||||
return
|
||||
}
|
||||
|
||||
// we get the resolved route from the fullpath
|
||||
// and wait for the route component to be loaded before we assign it
|
||||
baseRoute.value = await resolveAndLoadRoute(backdropView.value)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
const backdropRoute = computed(() => route.fullPath !== baseRoute.value?.fullPath ? baseRoute.value : undefined)
|
||||
|
||||
const currentModal = shallowRef<VNode>()
|
||||
watchEffect(() => {
|
||||
if (!backdropView.value) {
|
||||
currentModal.value = undefined
|
||||
return
|
||||
watch(
|
||||
[backdropRoute, baseRoute, route],
|
||||
() => {
|
||||
if (routerIsReady.value === false || !route.fullPath || !backdropRoute.value) {
|
||||
currentModal.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const props = getRouteProps(route)
|
||||
|
||||
props.backdropView = backdropRoute.value
|
||||
props.onClose = closeModal
|
||||
|
||||
const component = route.matched[0]?.components?.default
|
||||
|
||||
if (!component) {
|
||||
currentModal.value = undefined
|
||||
return
|
||||
}
|
||||
currentModal.value = h(component, props)
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
|
||||
async function closeModal() {
|
||||
await router.isReady()
|
||||
if (backdropRoute.value !== undefined) {
|
||||
// TODO: Dialog modals might want to replace the route here via router.replace()
|
||||
return router.push(backdropRoute.value)
|
||||
}
|
||||
|
||||
// this is adapted from vue-router
|
||||
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
|
||||
const routePropsOption = route.matched[0]?.props.default
|
||||
const routeProps = routePropsOption
|
||||
? routePropsOption === true
|
||||
? route.params
|
||||
: typeof routePropsOption === 'function'
|
||||
? routePropsOption(route)
|
||||
: routePropsOption
|
||||
: {}
|
||||
|
||||
routeProps.backdropView = backdropView.value
|
||||
|
||||
const component = route.matched[0]?.components?.default
|
||||
|
||||
if (!component) {
|
||||
currentModal.value = undefined
|
||||
return
|
||||
}
|
||||
currentModal.value = h(component, routeProps)
|
||||
})
|
||||
|
||||
function closeModal() {
|
||||
const historyState = computed(() => route.fullPath && window.history.state)
|
||||
|
||||
if (historyState.value === undefined) {
|
||||
router.back()
|
||||
if (window.history.state === undefined) {
|
||||
return router.push({ name: 'home' })
|
||||
} else {
|
||||
const backdropRoute = historyState.value.backdropView && router.resolve(historyState.value.backdropView)
|
||||
router.push(backdropRoute)
|
||||
router.back()
|
||||
throw new Error('')
|
||||
}
|
||||
}
|
||||
|
||||
return {routeWithModal, currentModal, closeModal}
|
||||
return {
|
||||
routeWithModal: baseRoute,
|
||||
currentModal,
|
||||
closeModal,
|
||||
}
|
||||
}
|
|
@ -240,7 +240,7 @@ const router = createRouter({
|
|||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
props: route => ({ namespaceId: parseInt(route.params.id as string) }),
|
||||
props: route => ({ namespaceId: Number(route.params.id as string) }),
|
||||
},
|
||||
{
|
||||
path: '/namespaces/:id/settings/delete',
|
||||
|
@ -468,6 +468,9 @@ const router = createRouter({
|
|||
path: '/about',
|
||||
name: 'about',
|
||||
component: About,
|
||||
meta: {
|
||||
showAsModal: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -512,9 +512,6 @@ const props = defineProps({
|
|||
},
|
||||
})
|
||||
|
||||
defineEmits(['close'])
|
||||
const attrs = useAttrs()
|
||||
console.log(JSON.parse(JSON.stringify(attrs)))
|
||||
const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
dpschen
commented
Remove log Remove log
|
||||
|
|
Reference in New Issue
Every modal component now has to provide it's own modal.
This makes it possible for us to have different modal implementations. Currently we do this by changing the
type
prop of the modal. It might be easier for us if we create something like aBaseModal
to abstract the general modal functionality and then use that to create styled Modals with specific functionality. For example we coudl create a dedicatedDialog
modal (this might actually be the same as the currentcreate-edit
, I'm not sure here if that was the intended use @konrad).That sounds like it could be a good idea.
IIRC my main goal with the
create-edit
component was to be able to easily re-use a shell for creating or editing.