Zoomable file previewer

This commit is contained in:
2025-11-06 22:31:35 +08:00
parent cd7894ec87
commit 90de5fcdac

View File

@@ -94,11 +94,12 @@
password to download it. password to download it.
</v-alert> </v-alert>
</div> </div>
<div v-else class="file-preview"> <div v-else class="file-preview" @wheel="handleZoom" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @dblclick="handleDoubleClick">
<v-img <v-img
v-if="fileType === 'image'" v-if="fileType === 'image'"
:src="fileSource" :src="fileSource"
class="preview-image" class="preview-image"
:style="{ transform: `scale(${zoomLevel})` }"
/> />
<video <video
v-else-if="fileType === 'video'" v-else-if="fileType === 'video'"
@@ -187,7 +188,8 @@
</div> </div>
<v-card variant="outlined" class="pa-2"> <v-card variant="outlined" class="pa-2">
<pre <pre
class="overflow-x-auto text-xs" class="overflow-x-auto"
style="font-size: 14px;"
><code>{{ JSON.stringify(fileInfo?.fileMeta, null, 2) }}</code></pre> ><code>{{ JSON.stringify(fileInfo?.fileMeta, null, 2) }}</code></pre>
</v-card> </v-card>
</v-card-text> </v-card-text>
@@ -234,6 +236,11 @@ const infoDialog = ref<boolean>(false)
const secretDialog = ref<boolean>(false) const secretDialog = ref<boolean>(false)
const dialogPassword = ref<string>("") const dialogPassword = ref<string>("")
// Zoom functionality
const zoomLevel = ref<number>(1)
const initialDistance = ref<number>(0)
const isPinching = ref<boolean>(false)
// View transition state // View transition state
const isTransitioning = ref<boolean>(false) const isTransitioning = ref<boolean>(false)
const transitionImage = ref<string>("") const transitionImage = ref<string>("")
@@ -367,6 +374,54 @@ async function confirmDownload() {
await performDownload(dialogPassword.value) await performDownload(dialogPassword.value)
} }
function handleZoom(event: WheelEvent) {
if (fileType.value !== 'image') return
event.preventDefault()
const delta = event.deltaY > 0 ? -0.1 : 0.1
zoomLevel.value = Math.max(0.1, Math.min(5, zoomLevel.value + delta))
}
function handleTouchStart(event: TouchEvent) {
if (fileType.value !== 'image' || event.touches.length !== 2) return
event.preventDefault()
isPinching.value = true
const touch1 = event.touches[0]!
const touch2 = event.touches[1]!
initialDistance.value = Math.sqrt(
Math.pow(touch2.clientX - touch1.clientX, 2) +
Math.pow(touch2.clientY - touch1.clientY, 2)
)
}
function handleTouchMove(event: TouchEvent) {
if (fileType.value !== 'image' || !isPinching.value || event.touches.length !== 2) return
event.preventDefault()
const touch1 = event.touches[0]!
const touch2 = event.touches[1]!
const currentDistance = Math.sqrt(
Math.pow(touch2.clientX - touch1.clientX, 2) +
Math.pow(touch2.clientY - touch1.clientY, 2)
)
const scale = currentDistance / initialDistance.value
zoomLevel.value = Math.max(0.1, Math.min(5, scale))
}
function handleTouchEnd(event: TouchEvent) {
if (fileType.value !== 'image') return
isPinching.value = false
}
function handleDoubleClick() {
if (fileType.value !== 'image') return
zoomLevel.value = zoomLevel.value > 1 ? 1 : 2
}
async function performDownload(password: string) { async function performDownload(password: string) {
if (fileInfo.value!.isEncrypted) { if (fileInfo.value!.isEncrypted) {
downloadAndDecryptFile( downloadAndDecryptFile(
@@ -479,6 +534,8 @@ definePageMeta({
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
transition: all .3s ease-in-out;
will-change: contents;
} }
.preview-video, .preview-video,