feat(frontend): add simultaneous slide transitions on directory navigation

- Detect navigation direction (forward/backward) via router beforeEach guard
- Store transition direction in Pinia for cross-component access
- Wrap ExplorerView content in a CSS grid transition wrapper so old and new
  views overlap in the same grid cell during animation
- Add slide-forward/slide-backward transition classes with translate3d
  for GPU-accelerated, simultaneous enter/leave without gaps
- Keep fixed-position search loader outside the transition
This commit is contained in:
2026-05-05 16:07:07 +00:00
parent eea66c0013
commit e979d679b2
4 changed files with 111 additions and 15 deletions
+62
View File
@@ -57,6 +57,66 @@
align-self: stretch;
}
}
/* Directory navigation slide transitions */
.transition-wrapper {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.explorer-content {
grid-area: 1 / 1;
}
.slide-forward-enter-active,
.slide-backward-enter-active {
z-index: 2;
}
.slide-forward-leave-active,
.slide-backward-leave-active {
z-index: 1;
}
.slide-forward-enter-active,
.slide-forward-leave-active,
.slide-backward-enter-active,
.slide-backward-leave-active {
transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1);
}
.slide-forward-enter-from {
transform: translate3d(100%, 0, 0);
}
.slide-forward-enter-to {
transform: translate3d(0, 0, 0);
}
.slide-forward-leave-from {
transform: translate3d(0, 0, 0);
}
.slide-forward-leave-to {
transform: translate3d(-100%, 0, 0);
}
.slide-backward-enter-from {
transform: translate3d(-100%, 0, 0);
}
.slide-backward-enter-to {
transform: translate3d(0, 0, 0);
}
.slide-backward-leave-from {
transform: translate3d(0, 0, 0);
}
.slide-backward-leave-to {
transform: translate3d(100%, 0, 0);
}
@media print {
:root {
--primary-color: black;
@@ -206,6 +266,8 @@ main {
min-height: 0; /* Allow flex child to shrink below content size */
padding-bottom: 3em; /* convenience space on the bottom */
overflow-y: scroll;
overflow-x: hidden;
position: relative;
text-align: center;
}
+19
View File
@@ -1,6 +1,12 @@
import { useMainStore } from '@/stores/main'
import ExplorerView from '@/views/ExplorerView.vue'
import { createRouter, createWebHashHistory } from 'vue-router'
function getPathDepth(path: string): number {
const pathPart = decodeURIComponent(path).split('//')[0] ?? ''
return pathPart.split('/').filter(Boolean).length
}
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
@@ -12,4 +18,17 @@ const router = createRouter({
]
})
router.beforeEach((to, from) => {
const store = useMainStore()
const toDepth = getPathDepth(to.path)
const fromDepth = getPathDepth(from.path)
if (toDepth > fromDepth) {
store.transitionDirection = 'forward'
} else if (toDepth < fromDepth) {
store.transitionDirection = 'backward'
} else {
store.transitionDirection = 'none'
}
})
export default router
+1
View File
@@ -98,6 +98,7 @@ export const useMainStore = defineStore('main', {
privileged: false as boolean,
isLoggedIn: false as boolean
},
transitionDirection: 'none' as 'forward' | 'backward' | 'none',
space: {
disk: 0,
free: 0,
+29 -15
View File
@@ -1,24 +1,32 @@
<template>
<Gallery
v-if="store.prefs.gallery"
ref="fileExplorer"
:key="`gallery-${folderPath}`"
:path="props.path"
:documents="documents"
/>
<FileExplorer
v-else
ref="fileExplorer"
:key="`explorer-${folderPath}`"
:path="props.path"
:documents="documents"
/>
<div class="transition-wrapper">
<Transition
:name="transitionName"
@after-enter="store.transitionDirection = 'none'"
>
<div :key="folderPath" class="explorer-content">
<Gallery
v-if="store.prefs.gallery"
ref="fileExplorer"
:path="props.path"
:documents="documents"
/>
<FileExplorer
v-else
ref="fileExplorer"
:path="props.path"
:documents="documents"
/>
<EmptyFolder :documents="documents" :path="props.path" />
</div>
</Transition>
</div>
<div v-if="store.searchLoading" class="search-loading">Searching...</div>
<EmptyFolder :documents=documents :path=props.path />
</template>
<script setup lang="ts">
import FileExplorer from '@/components/FileExplorer.vue'
import Gallery from '@/components/Gallery.vue'
import { getDocuments } from '@/stores/documentStore'
import { useMainStore } from '@/stores/main'
import { collator } from '@/utils'
@@ -35,6 +43,12 @@ const props = defineProps<{
// Folder path for component keys - only recreate component when folder changes, not search
const folderPath = computed(() => props.path.join('/'))
const transitionName = computed(() => {
if (store.transitionDirection === 'forward') return 'slide-forward'
if (store.transitionDirection === 'backward') return 'slide-backward'
return ''
})
// Handle route-based search changes (back/forward navigation, direct URL)
// Skip if store.query already matches (means we triggered this via typing)
watch(