140 lines
4.7 KiB
Vue
140 lines
4.7 KiB
Vue
<template>
|
|
<div class="shrink-0">
|
|
<div class="flex items-start gap-1.5">
|
|
<div v-if="stillId && (hidden || !failed.still)" class="flex w-12 flex-col items-center gap-0.5">
|
|
<span v-if="hidden" class="relative h-12 w-12 overflow-hidden rounded-lg">
|
|
<span class="sensitive-glow block h-full w-full" />
|
|
</span>
|
|
<button
|
|
v-else
|
|
type="button"
|
|
class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900"
|
|
aria-label="Start still"
|
|
@click.stop="openPreview(stillFull, 'Start')"
|
|
>
|
|
<img
|
|
:src="stillThumb"
|
|
alt=""
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="h-full w-full object-cover"
|
|
@error="failed.still = true"
|
|
>
|
|
</button>
|
|
<span class="text-[10px] leading-none text-zinc-500">Start</span>
|
|
</div>
|
|
<div v-if="fromClipId && (hidden || !failed.from)" class="flex w-12 flex-col items-center gap-0.5">
|
|
<span v-if="hidden" class="relative h-12 w-12 overflow-hidden rounded-lg">
|
|
<span class="sensitive-glow block h-full w-full" />
|
|
</span>
|
|
<button
|
|
v-else
|
|
type="button"
|
|
class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900"
|
|
aria-label="Last frame of previous clip"
|
|
@click.stop="openPreview(fromFull, 'From prev')"
|
|
>
|
|
<img
|
|
:src="fromThumb"
|
|
alt=""
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="h-full w-full object-cover"
|
|
@error="failed.from = true"
|
|
>
|
|
</button>
|
|
<span class="text-[10px] leading-none text-zinc-500">From prev</span>
|
|
</div>
|
|
<div v-if="showFirstFrame && clipId && (hidden || !failed.first)" class="flex w-12 flex-col items-center gap-0.5">
|
|
<span v-if="hidden" class="relative h-12 w-12 overflow-hidden rounded-lg">
|
|
<span class="sensitive-glow block h-full w-full" />
|
|
</span>
|
|
<button
|
|
v-else
|
|
type="button"
|
|
class="relative h-12 w-12 overflow-hidden rounded-lg bg-zinc-900"
|
|
aria-label="First frame of this clip"
|
|
@click.stop="openPreview(firstFull, 'This clip')"
|
|
>
|
|
<img
|
|
:src="firstThumb"
|
|
alt=""
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="h-full w-full object-cover"
|
|
@error="failed.first = true"
|
|
>
|
|
</button>
|
|
<span class="text-[10px] leading-none text-zinc-500">This clip</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Teleport to="body">
|
|
<div
|
|
v-if="preview"
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4"
|
|
@click.self="closePreview"
|
|
>
|
|
<img
|
|
:src="preview.src"
|
|
:alt="preview.label"
|
|
class="max-h-[70vh] max-w-[min(90vw,56rem)] object-contain"
|
|
@click.stop
|
|
>
|
|
</div>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
hidden?: boolean
|
|
stillId?: string
|
|
fromClipId?: string
|
|
clipId: string
|
|
showFirstFrame?: boolean
|
|
}>()
|
|
|
|
const failed = reactive({ still: false, from: false, first: false })
|
|
const preview = ref<{ src: string; label: string } | null>(null)
|
|
|
|
const stillThumb = computed(() => !props.hidden && props.stillId ? `/api/library/stills/${props.stillId}?thumb=1` : '')
|
|
const stillFull = computed(() => !props.hidden && props.stillId ? `/api/library/stills/${props.stillId}` : '')
|
|
const fromThumb = computed(() => !props.hidden && props.fromClipId ? `/api/library/clips/${props.fromClipId}/last-frame?thumb=1` : '')
|
|
const fromFull = computed(() => !props.hidden && props.fromClipId ? `/api/library/clips/${props.fromClipId}/last-frame` : '')
|
|
const firstThumb = computed(() => !props.hidden && props.clipId ? `/api/library/clips/${props.clipId}/first-frame?thumb=1` : '')
|
|
const firstFull = computed(() => !props.hidden && props.clipId ? `/api/library/clips/${props.clipId}/first-frame` : '')
|
|
|
|
watch(
|
|
() => [props.hidden, props.stillId, props.fromClipId, props.clipId, props.showFirstFrame],
|
|
() => {
|
|
failed.still = false
|
|
failed.from = false
|
|
failed.first = false
|
|
if (props.hidden) closePreview()
|
|
}
|
|
)
|
|
|
|
function openPreview(src: string, label: string) {
|
|
if (props.hidden || !src) return
|
|
preview.value = { src, label }
|
|
}
|
|
|
|
function closePreview() {
|
|
preview.value = null
|
|
}
|
|
|
|
function onKey(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') closePreview()
|
|
}
|
|
|
|
watch(preview, (value) => {
|
|
if (value) window.addEventListener('keydown', onKey)
|
|
else window.removeEventListener('keydown', onKey)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keydown', onKey)
|
|
})
|
|
</script>
|