@@ -11,14 +11,37 @@ const { t } = useI18n();
1111
1212// Optional trick-play fraction (0..1 along the asset) while hovering a video.
1313const frac = ref <number | null >(null );
14- const imgFailed = ref (false );
14+
15+ // Cantemo doesn't auto-generate `/thumbnailresource` for static images, so the
16+ // thumbnail endpoint 404s for them. Fall back to the preview shape (the actual
17+ // image bytes) for image items only — for video/audio the preview is media,
18+ // not an image, so loading it into <img> would just waste bandwidth.
19+ type ImgStage = " thumbnail" | " preview" | " failed" ;
20+ const imgStage = ref <ImgStage >(" thumbnail" );
1521
1622const thumbSrc = computed (() => {
1723 let url = ` ${props .base }/vault/thumbnail?vxid=${encodeURIComponent (props .item .VXID )} ` ;
1824 if (frac .value != null ) url += ` &f=${frac .value } ` ;
1925 return url ;
2026});
2127
28+ const previewSrc = computed (
29+ () =>
30+ ` ${props .base }/vault/preview?vxid=${encodeURIComponent (props .item .VXID )} ` ,
31+ );
32+
33+ const imgSrc = computed (() =>
34+ imgStage .value === " thumbnail" ? thumbSrc .value : previewSrc .value ,
35+ );
36+
37+ function onImgError() {
38+ if (imgStage .value === " thumbnail" && props .item .mediaType === " image" ) {
39+ imgStage .value = " preview" ;
40+ } else {
41+ imgStage .value = " failed" ;
42+ }
43+ }
44+
2245const typeIcon = computed (() => {
2346 switch (props .item .mediaType ) {
2447 case " video" :
@@ -44,7 +67,7 @@ const durationLabel = computed(() => {
4467// Quantize to a handful of steps to limit thumbnail requests while hovering.
4568function onMove(e : MouseEvent ) {
4669 if (props .item .mediaType !== " video" ) return ;
47- if (imgFailed .value ) return ;
70+ if (imgStage .value === " failed " ) return ;
4871 const el = e .currentTarget as HTMLElement ;
4972 const rect = el .getBoundingClientRect ();
5073 const f = Math .min (Math .max ((e .clientX - rect .left ) / rect .width , 0 ), 1 );
@@ -69,12 +92,12 @@ function onLeave() {
6992 @mouseleave =" onLeave"
7093 >
7194 <img
72- v-if =" !imgFailed "
73- :src =" thumbSrc "
95+ v-if =" imgStage !== 'failed' "
96+ :src =" imgSrc "
7497 :alt =" item.title"
7598 loading =" lazy"
7699 class =" h-full w-full object-contain"
77- @error =" imgFailed = true "
100+ @error =" onImgError "
78101 />
79102 <UIcon v-else :name =" typeIcon " class="size-10 opacity-40" />
80103 <span
0 commit comments