Refactor app navigation and enhance background handling in various views

- Added error handling for router navigation to prevent unhandled promise rejections.
- Improved background image management in Dashboard.vue to dynamically set images based on route.
- Introduced timers for managing loading states and cleanup on component unmount in Apps.vue, Marketplace.vue, and other views.
- Updated app detail navigation to ensure smoother transitions and error handling.
- Enhanced clipboard copy functionality in Settings.vue with improved user feedback.
This commit is contained in:
Dorian
2026-03-01 18:07:35 +00:00
parent 7a05e11834
commit 94eb1e4283
7 changed files with 218 additions and 399 deletions
+78 -182
View File
@@ -2,33 +2,33 @@
<div class="min-h-screen flex relative dashboard-view" :class="{ 'glass-throw-active': showZoomIn }">
<!-- Background container with 3D perspective - full width to avoid letterboxing -->
<div class="bg-perspective-container">
<!-- Background - default (zoom animates on this layer, not container, to avoid letterboxing) -->
<!-- Background - primary layer (visible for all routes, transitions out only for detail pages) -->
<div
ref="bgDefault"
class="bg-layer bg-fullwidth"
:class="[
{ 'bg-transitioning-out': showAltBackground || showWeb5Background || showNetworkBackground || showSettingsBackground || showMyAppsBackground || showAppStoreBackground || showCloudBackground || showHomeBackground },
{ 'bg-transitioning-out': showAltBackground },
{ 'zoom-reveal-bg': showZoomIn }
]"
:style="{ backgroundImage: `url(/assets/img/${currentBackgroundImage})` }"
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
/>
<!-- Background - alternate for app details and Web5 -->
<!-- Background - detail layer (only visible during app/marketplace detail 3D transition) -->
<div
ref="bgAlt"
class="bg-layer bg-fullwidth"
:class="{ 'bg-transitioning-in': showAltBackground || showWeb5Background || showNetworkBackground || showSettingsBackground || showMyAppsBackground || showAppStoreBackground || showCloudBackground || showHomeBackground }"
:style="{ backgroundImage: `url(/assets/img/${altBackgroundImage})` }"
:class="{ 'bg-transitioning-in': showAltBackground }"
style="background-image: url(/assets/img/bg-intro-3.jpg)"
/>
<!-- Glitch overlays - trigger on background change -->
<div
class="bg-glitch-layer-1"
:class="{ 'glitch-active': isGlitching }"
:style="{ backgroundImage: `url(/assets/img/${altBackgroundImage})` }"
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
/>
<div
class="bg-glitch-layer-2"
:class="{ 'glitch-active': isGlitching }"
:style="{ backgroundImage: `url(/assets/img/${altBackgroundImage})` }"
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
/>
<div
class="bg-glitch-scan"
@@ -37,11 +37,11 @@
<!-- Continuous glitch/flash overlays - same as login, every 5s -->
<div
class="dashboard-glitch-layer dashboard-glitch-1 bg-fullwidth"
:style="{ backgroundImage: `url(/assets/img/${currentBackgroundImage})` }"
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
/>
<div
class="dashboard-glitch-layer dashboard-glitch-2 bg-fullwidth"
:style="{ backgroundImage: `url(/assets/img/${currentBackgroundImage})` }"
:style="{ backgroundImage: `url(/assets/img/${backgroundImage})` }"
/>
<div class="dashboard-glitch-scan" />
</div>
@@ -319,52 +319,61 @@ const store = useAppStore()
const loginTransition = useLoginTransitionStore()
const showZoomIn = ref(false)
const pendingTimers: ReturnType<typeof setTimeout>[] = []
function scheduledTimeout(fn: () => void, ms: number) {
const id = setTimeout(fn, ms)
pendingTimers.push(id)
return id
}
function isDetailRoute(path: string) {
return (path.includes('/apps/') && !path.endsWith('/apps')) ||
(path.includes('/marketplace/') && !path.endsWith('/marketplace'))
}
// Background swap for app details, Web5, Network, Settings, My Apps, App Store, Cloud, and Home
const showAltBackground = ref(false)
const isHomeRoute = computed(() => route.path === '/dashboard' || route.path === '/dashboard/')
const showHomeBackground = ref(route.path === '/dashboard' || route.path === '/dashboard/')
const showWeb5Background = ref(route.path.includes('/dashboard/web5'))
const showNetworkBackground = ref(route.path.includes('/dashboard/server'))
const showSettingsBackground = ref(route.path.includes('/dashboard/settings'))
const showMyAppsBackground = ref(route.path.includes('/dashboard/apps') && !route.path.includes('/dashboard/apps/'))
const showAppStoreBackground = ref(route.path.includes('/dashboard/marketplace'))
const showCloudBackground = ref(route.path.includes('/dashboard/cloud'))
const showWeb5Overlay = ref(route.path.includes('/dashboard/web5')) // Separate ref for overlay to handle transition delay
const showNetworkOverlay = ref(route.path.includes('/dashboard/server'))
const showSettingsOverlay = ref(route.path.includes('/dashboard/settings'))
const showMyAppsOverlay = ref(route.path.includes('/dashboard/apps') && !route.path.includes('/dashboard/apps/'))
const showAppStoreOverlay = ref(route.path.includes('/dashboard/marketplace'))
const showCloudOverlay = ref(route.path.includes('/dashboard/cloud'))
const isGlitching = ref(false)
// Background images
const currentBackgroundImage = computed(() => {
if (showWeb5Background.value) return 'bg-web5.jpg'
if (showNetworkBackground.value) return 'bg-network.jpg'
if (showSettingsBackground.value) return 'bg-settings.jpg'
if (showMyAppsBackground.value) return 'bg-myapps.jpg'
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
if (showCloudBackground.value) return 'bg-cloud.jpg'
if (showHomeBackground.value) return 'bg-home.jpg'
return 'bg-intro.jpg'
const ROUTE_BACKGROUNDS: Record<string, string> = {
'/dashboard': 'bg-home.jpg',
'/dashboard/': 'bg-home.jpg',
'/dashboard/apps': 'bg-myapps.jpg',
'/dashboard/marketplace': 'bg-appstore.jpg',
'/dashboard/cloud': 'bg-cloud.jpg',
'/dashboard/server': 'bg-network.jpg',
'/dashboard/web5': 'bg-web5.jpg',
'/dashboard/settings': 'bg-settings.jpg',
}
const backgroundImage = computed(() => {
if (isDetailRoute(route.path)) return 'bg-intro.jpg'
return ROUTE_BACKGROUNDS[route.path] || 'bg-home.jpg'
})
const altBackgroundImage = computed(() => {
if (showWeb5Background.value) return 'bg-web5.jpg'
if (showNetworkBackground.value) return 'bg-network.jpg'
if (showSettingsBackground.value) return 'bg-settings.jpg'
if (showMyAppsBackground.value) return 'bg-myapps.jpg'
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
if (showCloudBackground.value) return 'bg-cloud.jpg'
if (showHomeBackground.value) return 'bg-home.jpg'
return 'bg-intro-3.jpg'
const isDarkRoute = computed(() => {
const p = route.path
return p.includes('/dashboard/web5') ||
p.includes('/dashboard/server') ||
p.includes('/dashboard/settings') ||
(p.includes('/dashboard/apps') && !isDetailRoute(p)) ||
p.includes('/dashboard/marketplace') ||
p.includes('/dashboard/cloud')
})
// Check if overlay should be dark (0.8 opacity)
const showDarkOverlay = computed(() => {
return showWeb5Overlay.value || showNetworkOverlay.value || showSettingsOverlay.value || showMyAppsOverlay.value || showAppStoreOverlay.value || showCloudOverlay.value
const showDarkOverlay = ref(isDarkRoute.value)
let overlayTimer: ReturnType<typeof setTimeout> | null = null
watch(isDarkRoute, (dark) => {
if (overlayTimer) { clearTimeout(overlayTimer); overlayTimer = null }
if (dark) {
showDarkOverlay.value = true
} else {
overlayTimer = scheduledTimeout(() => { showDarkOverlay.value = false }, 450)
}
})
const mobileTabBar = ref<HTMLElement | null>(null)
const appsTabRef = ref<HTMLElement | null>(null)
const marketplaceTabRef = ref<HTMLElement | null>(null)
@@ -376,116 +385,17 @@ const cloudTabRef = ref<HTMLElement | null>(null)
const networkTabIndicatorLeft = ref(0)
const networkTabIndicatorWidth = ref(0)
function isDetailRoute(path: string) {
return (path.includes('/apps/') && !path.endsWith('/apps')) ||
(path.includes('/marketplace/') && !path.endsWith('/marketplace'))
}
watch(() => route.path, (newPath) => {
// Check if we're on app details OR marketplace app details
const isAppDetails = isDetailRoute(newPath)
const wasAppDetails = showAltBackground.value
// Check if we're on special background routes
const isHome = newPath === '/dashboard' || newPath === '/dashboard/'
const isWeb5 = newPath.includes('/dashboard/web5')
const wasWeb5 = showWeb5Background.value
const isNetwork = newPath.includes('/dashboard/server')
const wasNetwork = showNetworkBackground.value
const isSettings = newPath.includes('/dashboard/settings')
const wasSettings = showSettingsBackground.value
const isMyApps = newPath.includes('/dashboard/apps') && !newPath.includes('/dashboard/apps/')
const wasMyApps = showMyAppsBackground.value
const isAppStore = newPath.includes('/dashboard/marketplace')
const wasAppStore = showAppStoreBackground.value
const isCloud = newPath.includes('/dashboard/cloud')
const wasCloud = showCloudBackground.value
// Change background immediately
showAltBackground.value = isAppDetails
showHomeBackground.value = isHome
showWeb5Background.value = isWeb5
showNetworkBackground.value = isNetwork
showSettingsBackground.value = isSettings
showMyAppsBackground.value = isMyApps
showAppStoreBackground.value = isAppStore
showCloudBackground.value = isCloud
// Handle overlay transitions with delay when leaving special backgrounds
// Web5 overlay
if (isWeb5) {
showWeb5Overlay.value = true
} else if (wasWeb5 && !isWeb5) {
setTimeout(() => {
showWeb5Overlay.value = false
}, 450)
} else {
showWeb5Overlay.value = isWeb5
}
// Network overlay
if (isNetwork) {
showNetworkOverlay.value = true
} else if (wasNetwork && !isNetwork) {
setTimeout(() => {
showNetworkOverlay.value = false
}, 450)
} else {
showNetworkOverlay.value = isNetwork
}
// Settings overlay
if (isSettings) {
showSettingsOverlay.value = true
} else if (wasSettings && !isSettings) {
setTimeout(() => {
showSettingsOverlay.value = false
}, 450)
} else {
showSettingsOverlay.value = isSettings
}
// My Apps overlay
if (isMyApps) {
showMyAppsOverlay.value = true
} else if (wasMyApps && !isMyApps) {
setTimeout(() => {
showMyAppsOverlay.value = false
}, 450)
} else {
showMyAppsOverlay.value = isMyApps
}
// App Store overlay
if (isAppStore) {
showAppStoreOverlay.value = true
} else if (wasAppStore && !isAppStore) {
setTimeout(() => {
showAppStoreOverlay.value = false
}, 450)
} else {
showAppStoreOverlay.value = isAppStore
}
// Cloud overlay
if (isCloud) {
showCloudOverlay.value = true
} else if (wasCloud && !isCloud) {
setTimeout(() => {
showCloudOverlay.value = false
}, 450)
} else {
showCloudOverlay.value = isCloud
}
// Trigger glitch ONLY when going forward (to app details), not back
if (isAppDetails && !wasAppDetails) {
setTimeout(() => {
scheduledTimeout(() => {
isGlitching.value = true
setTimeout(() => {
isGlitching.value = false
}, 375) // Glitch duration - 25% faster
}, 500) // Wait for background 3D transition to complete
scheduledTimeout(() => { isGlitching.value = false }, 375)
}, 500)
}
})
@@ -550,6 +460,12 @@ function updateNetworkTabIndicator() {
networkTabIndicatorWidth.value = tabRect.width
}
function onResize() {
updateTabBarHeight()
updateAppsTabIndicator()
updateNetworkTabIndicator()
}
onMounted(() => {
document.body.classList.add('dashboard-active')
if (loginTransition.justLoggedIn) {
@@ -557,39 +473,30 @@ onMounted(() => {
showZoomIn.value = true
loginTransition.setPendingWelcomeTyping(true)
loginTransition.setJustLoggedIn(false)
// Trigger glitches during background reveal (0.5s, 1.2s, 2s into 2.8s zoom)
const triggerRevealGlitch = () => {
isGlitching.value = true
setTimeout(() => { isGlitching.value = false }, 380)
scheduledTimeout(() => { isGlitching.value = false }, 380)
}
setTimeout(triggerRevealGlitch, 500)
setTimeout(triggerRevealGlitch, 1200)
setTimeout(triggerRevealGlitch, 2000)
// Keep glass-throw active long enough for sidebar (last to animate, ~7.5s)
setTimeout(() => {
showZoomIn.value = false
}, 8000)
// Trigger welcome typing on Home after main content animation finishes
setTimeout(() => {
scheduledTimeout(triggerRevealGlitch, 500)
scheduledTimeout(triggerRevealGlitch, 1200)
scheduledTimeout(triggerRevealGlitch, 2000)
scheduledTimeout(() => { showZoomIn.value = false }, 8000)
scheduledTimeout(() => {
loginTransition.setStartWelcomeTyping(true)
loginTransition.setPendingWelcomeTyping(false)
}, 4000)
}
updateTabBarHeight()
updateAppsTabIndicator()
updateNetworkTabIndicator()
window.addEventListener('resize', () => {
updateTabBarHeight()
updateAppsTabIndicator()
updateNetworkTabIndicator()
})
onResize()
window.addEventListener('resize', onResize)
})
onBeforeUnmount(() => {
document.body.classList.remove('dashboard-active')
window.removeEventListener('resize', updateTabBarHeight)
window.removeEventListener('resize', onResize)
for (const id of pendingTimers) clearTimeout(id)
pendingTimers.length = 0
if (overlayTimer) { clearTimeout(overlayTimer); overlayTimer = null }
})
// Watch route changes to update indicator position
@@ -676,14 +583,6 @@ const mobileNavItems = [
},
]
// Use appropriate nav items based on screen size
// @ts-ignore - Computed kept for future use
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const navItems = computed(() => {
if (typeof window === 'undefined') return desktopNavItems
return window.innerWidth >= 768 ? desktopNavItems : mobileNavItems
})
function getIconPath(iconName: string): string[] {
const icons: Record<string, string[]> = {
home: ['M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'],
@@ -846,9 +745,6 @@ function getTransitionName(currentRoute: any) {
.zoom-reveal-bg {
animation: zoom-reveal 2.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
transform-origin: center center;
}
.zoom-reveal-bg {
opacity: 0;
transform: scale(0.15);
filter: blur(24px);