a video in a carousel gets its poster, not a 404
A video has no width rungs — poster-448.webp and friends are cut from a still — so asking for 320.webp returns a 404 and the grid drew a broken tile with no clue why. Every grid in the panel was doing it, and a carousel is explicitly allowed to hold both kinds. posterUrl already existed for this and nothing called it. mediaThumbUrl decides from the extension so a screen showing mixed media does not have to know the difference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
import { MediaPicker } from '../MediaPicker';
|
import { MediaPicker } from '../MediaPicker';
|
||||||
import { readableError } from '../errors';
|
import { readableError } from '../errors';
|
||||||
import { thumbUrl } from '../thumbnail';
|
import { mediaThumbUrl } from '../thumbnail';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One carousel's contents: what is in it, in what order, and what each picture is called.
|
* One carousel's contents: what is in it, in what order, and what each picture is called.
|
||||||
@@ -151,7 +151,7 @@ export const CarouselItems = ({ carouselId, onChanged }: { carouselId: number; o
|
|||||||
and a browser cannot show the original at all.
|
and a browser cannot show the original at all.
|
||||||
*/}
|
*/}
|
||||||
<img
|
<img
|
||||||
src={thumbUrl(item.url)}
|
src={mediaThumbUrl(item.url)}
|
||||||
alt=""
|
alt=""
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className="size-24 shrink-0 rounded bg-muted object-cover"
|
className="size-24 shrink-0 rounded bg-muted object-cover"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { CarouselPicker } from '../CarouselPicker';
|
import { CarouselPicker } from '../CarouselPicker';
|
||||||
import { readableError } from '../errors';
|
import { readableError } from '../errors';
|
||||||
import { useEditorSlot } from '../useEditorSlot';
|
import { useEditorSlot } from '../useEditorSlot';
|
||||||
import { thumbUrl } from '../thumbnail';
|
import { mediaThumbUrl } from '../thumbnail';
|
||||||
import { SubjectFields } from './SubjectFields';
|
import { SubjectFields } from './SubjectFields';
|
||||||
import type { SubjectPhoto, SubjectRecord, SubjectSection } from './subjects';
|
import type { SubjectPhoto, SubjectRecord, SubjectSection } from './subjects';
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ export const SubjectPanel = <T extends SubjectRecord>({ section }: { section: Su
|
|||||||
{photos.map((photo) => (
|
{photos.map((photo) => (
|
||||||
<li key={photo.path}>
|
<li key={photo.path}>
|
||||||
<div className="overflow-hidden rounded-md border border-border">
|
<div className="overflow-hidden rounded-md border border-border">
|
||||||
<img src={thumbUrl(photo.path, 320)} alt="" className="aspect-square w-full object-cover" />
|
<img src={mediaThumbUrl(photo.path, 320)} alt="" className="aspect-square w-full object-cover" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 truncate font-mono text-[10px] text-muted-foreground/70">{photo.name}</p>
|
<p className="mt-1 truncate font-mono text-[10px] text-muted-foreground/70">{photo.name}</p>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -48,3 +48,20 @@ export const originalUrl = (path: string, width: number): string => {
|
|||||||
const encoded = path.split('/').map(encodeURIComponent).join('/');
|
const encoded = path.split('/').map(encodeURIComponent).join('/');
|
||||||
return `/api/fofos-admin/original/${encoded}?width=${Math.round(width)}&token=${encodeURIComponent(token)}`;
|
return `/api/fofos-admin/original/${encoded}?width=${Math.round(width)}&token=${encodeURIComponent(token)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Extensions av-club treats as video. The same list the site's own `kindFor` uses. */
|
||||||
|
const VIDEO = /\.(mp4|mov|webm|m4v|avi|mkv)$/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thumbnail for anything in the media store, picture or video.
|
||||||
|
*
|
||||||
|
* A video has no width rungs — `poster-448.webp` and friends are cut from a still, and asking for
|
||||||
|
* `320.webp` gets a 404 and a broken image. Every grid in this panel was doing exactly that, so a
|
||||||
|
* carousel holding a video showed a broken tile with no clue why.
|
||||||
|
*
|
||||||
|
* `posterUrl` already existed for this and nothing called it. One helper that decides from the
|
||||||
|
* extension, so a screen showing mixed media does not have to know the difference — which is the
|
||||||
|
* whole point, since a carousel is explicitly allowed to hold both.
|
||||||
|
*/
|
||||||
|
export const mediaThumbUrl = (path: string, width: 256 | 320 | 480 | 640 | 960 | 1280 = 320): string =>
|
||||||
|
VIDEO.test(path) ? posterUrl(path) : thumbUrl(path, width);
|
||||||
|
|||||||
Reference in New Issue
Block a user