Preload pending thread media, hide tile hover overlays on click, and avoid stretching view transition snapshots
Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1288,19 +1288,20 @@
|
||||
::view-transition-group(upload-media) {
|
||||
animation-duration: 380ms;
|
||||
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
::view-transition-old(upload-media),
|
||||
::view-transition-new(upload-media) {
|
||||
animation-duration: 380ms;
|
||||
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
|
||||
/* Neuere Browser zeichnen die Snapshots mit natürlichem Seitenverhältnis -
|
||||
dann ragt der alte Snapshot bei Aspect-Wechsel (quadratisches Tile ->
|
||||
breite Thread-Box) unter der Gruppen-Box heraus und das Bild wirkt, als
|
||||
würde es erst in die falsche Richtung rutschen. Fill + volle Größe zwingt
|
||||
den Snapshot wieder exakt in die morphierende Box. */
|
||||
ohne Clipping ragt der alte quadratische Tile-Snapshot bei breiten
|
||||
Ziel-Medien unten aus der Gruppe heraus. cover + overflow:hidden auf der
|
||||
Group hält beide Snapshots unverzerrt exakt in der morphierenden Box. */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* Phosphor-Glyphen haben mehr Innenrand im ViewBox als Lucide -
|
||||
|
||||
@@ -144,8 +144,14 @@ function openUpload(e: MouseEvent) {
|
||||
>
|
||||
<Icon name="ph:bell-fill" class="size-3.5" />
|
||||
</span>
|
||||
<div class="absolute inset-0 bg-primary/30 opacity-0 transition-opacity group-hover:opacity-100" />
|
||||
<div class="absolute inset-0 flex items-end bg-gradient-to-t from-black/70 via-black/20 to-transparent p-2 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div
|
||||
class="absolute inset-0 bg-primary/30 opacity-0 transition-opacity"
|
||||
:class="!navigating && 'group-hover:opacity-100'"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 flex items-end bg-gradient-to-t from-black/70 via-black/20 to-transparent p-2 opacity-0 transition-opacity"
|
||||
:class="!navigating && 'group-hover:opacity-100'"
|
||||
>
|
||||
<div class="flex w-full items-end justify-between gap-2 text-xs text-white">
|
||||
<UserHoverCard
|
||||
v-if="upload.userId != null"
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'plyr/dist/plyr.css'
|
||||
|
||||
defineProps<{
|
||||
src: string
|
||||
poster?: string | null
|
||||
autoplay?: boolean
|
||||
}>()
|
||||
|
||||
@@ -47,6 +48,7 @@ function onLoadedMetadata() {
|
||||
<video
|
||||
ref="videoEl"
|
||||
:src="src"
|
||||
:poster="poster || undefined"
|
||||
:autoplay="autoplay"
|
||||
playsinline
|
||||
controls
|
||||
|
||||
+15
-8
@@ -323,18 +323,24 @@ function markThreadRead() {
|
||||
onMounted(markThreadRead)
|
||||
watch([notifications, id], markThreadRead)
|
||||
|
||||
// Medium laden, bevor die Seite fertig ist - sonst enthält der View-Transition-Snapshot eine leere Box
|
||||
if (import.meta.client && data.value) {
|
||||
if (data.value.upload.type === 'image') {
|
||||
// Medium laden, bevor die Seite fertig ist - sonst enthält der View-Transition-Snapshot eine leere Box.
|
||||
// Auf dem Client ist data.value hier noch null (useFetch läuft async), deshalb
|
||||
// auch pendingMedia berücksichtigen - ohne decode() hat <img class="w-auto"> noch 0×0.
|
||||
const preloadTarget = data.value?.upload ?? pendingMediaForPage.value
|
||||
if (import.meta.client && preloadTarget) {
|
||||
const preloadImgUrl = preloadTarget.type === 'image'
|
||||
? preloadTarget.url
|
||||
: ('previewImage' in preloadTarget ? preloadTarget.previewImage : preloadTarget.thumbUrl)
|
||||
if (preloadImgUrl) {
|
||||
const pre = new Image()
|
||||
pre.src = data.value.upload.url
|
||||
pre.src = preloadImgUrl
|
||||
try {
|
||||
await Promise.race([pre.decode(), new Promise(r => setTimeout(r, 2000))])
|
||||
await Promise.race([pre.decode(), new Promise(r => setTimeout(r, 1500))])
|
||||
}
|
||||
catch { /* Bild lädt dann normal nach */ }
|
||||
}
|
||||
else if (data.value.upload.type === 'video') {
|
||||
const url = data.value.upload.url
|
||||
else if (preloadTarget.type === 'video' && preloadTarget.url) {
|
||||
const url = preloadTarget.url
|
||||
await new Promise<void>((resolve) => {
|
||||
const v = document.createElement('video')
|
||||
v.preload = 'auto'
|
||||
@@ -342,7 +348,7 @@ if (import.meta.client && data.value) {
|
||||
v.onloadeddata = () => resolve()
|
||||
v.onerror = () => resolve()
|
||||
v.src = url
|
||||
setTimeout(resolve, 2000)
|
||||
setTimeout(resolve, 1500)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1346,6 +1352,7 @@ async function deleteComment(commentId: number) {
|
||||
<VideoPlayer
|
||||
v-else-if="media?.type === 'video' && media.url"
|
||||
:src="media.url"
|
||||
:poster="media.previewImage"
|
||||
/>
|
||||
<div
|
||||
v-else-if="media?.type === 'audio' && media.url"
|
||||
|
||||
Reference in New Issue
Block a user