Files
media_viewer/src/components/MediaViewerPopover.vue
T
2026-06-17 23:14:59 -04:00

90 lines
2.3 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import type { MediaItem, ResolveSource } from '@/types'
import { findViewer } from '@/services/viewer.ts'
import MediaStage from './MediaStage.vue'
/**
* Lightweight hover-preview surface. Mounted inside a host trigger element and
* anchored to it via Vuetify's `activator="parent"`, so the host only needs to
* drop this component into the element it wants previewed.
*
* The menu renders its content lazily, so the source (and any blob fetch) is
* only resolved once the menu actually opens after `openDelay` — a quick
* fly-over never triggers a fetch. Clicking the preview emits `expand` so the
* host can open the fullscreen viewer.
*/
const props = withDefaults(
defineProps<{
item: MediaItem | null
resolveSource: ResolveSource
/** Hover dwell before opening, in ms. */
openDelay?: number
}>(),
{ openDelay: 400 },
)
const emit = defineEmits<{
expand: [item: MediaItem]
}>()
// Only offer a hover preview when a matching viewer exists and opts in
// (meta.popover !== false). Standalone viewers default to opting out.
const enabled = computed(() => {
const mime = props.item?.mime
if (!mime) return false
const viewer = findViewer(mime)
return !!viewer && viewer.meta?.popover !== false
})
function expand() {
if (props.item) emit('expand', props.item)
}
</script>
<template>
<v-menu
v-if="enabled"
activator="parent"
open-on-hover
:open-delay="openDelay"
:close-delay="100"
location="end"
:close-on-content-click="false"
>
<v-card class="media-popover" @click="expand">
<div class="media-popover__stage">
<MediaStage :item="item" :resolve-source="resolveSource" presentation="popover" />
</div>
<div class="media-popover__hint text-caption text-medium-emphasis">
Click to open
</div>
</v-card>
</v-menu>
</template>
<style scoped>
.media-popover {
width: 320px;
max-width: 80vw;
cursor: pointer;
overflow: hidden;
}
.media-popover__stage {
width: 100%;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: rgb(var(--v-theme-surface-light, var(--v-theme-surface)));
}
.media-popover__hint {
text-align: center;
padding: 4px 8px;
border-top: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
</style>