diff --git a/public/images/previews/anime-tsundere.png b/public/images/previews/anime-tsundere.png new file mode 100644 index 0000000..b95d2df Binary files /dev/null and b/public/images/previews/anime-tsundere.png differ diff --git a/src/lib/theme.ts b/src/lib/theme.ts index 062c12d..dbd1f6e 100644 --- a/src/lib/theme.ts +++ b/src/lib/theme.ts @@ -138,3 +138,22 @@ export function themeScreenshots(screenshots: string[] | null | undefined): stri export function hasScreenshots(screenshots: string[] | null | undefined): boolean { return themeScreenshots(screenshots).length > 0; } + +/** + * The images to show for a theme listing, in order of preference: the listing's + * own screenshots, then a preview bundled with the site at + * `/images/previews/.png`, and otherwise nothing — the caller then falls + * back to the CSS mock box. + * + * `hasBundledPreview` is resolved by the page at build time via a glob, because + * a Cloudflare Worker has no filesystem to probe for the file at request time. + */ +export function themeGallery( + id: string, + screenshots: string[] | null | undefined, + hasBundledPreview: boolean, +): string[] { + const validated = themeScreenshots(screenshots); + if (validated.length > 0) return validated; + return hasBundledPreview ? [`/images/previews/${id}.png`] : []; +} diff --git a/src/pages/plugin/[id].astro b/src/pages/plugin/[id].astro index 6e1edde..b510691 100644 --- a/src/pages/plugin/[id].astro +++ b/src/pages/plugin/[id].astro @@ -7,7 +7,7 @@ import { isTheme, themePalette, themeSwatches, - themeScreenshots, + themeGallery, contrastText, themeApplyUri, orskinFileName, @@ -43,8 +43,24 @@ const published = Boolean(plugin?.sha256); const theme = Boolean(plugin && isTheme(plugin.category)); const palette = theme ? themePalette(plugin!.routes) : []; const swatches = theme ? themeSwatches(plugin!.routes) : []; -const screenshots = plugin ? themeScreenshots(plugin.screenshots) : []; -const showGallery = theme && screenshots.length > 0; + +// Bundled preview images live under public/images/previews/.png and are +// resolved at build time: a Cloudflare Worker has no filesystem to probe at +// request time, so a glob is the only way to know which ids ship a preview. +const previewModules = import.meta.glob('../../../public/images/previews/*.png', { + eager: true, + query: '?url', + import: 'default', +}); +const previewIds = new Set( + Object.keys(previewModules).map((path) => path.split('/').pop()!.replace(/\.png$/, '')), +); + +// Prefer the row's own screenshots, then a bundled preview, and only then fall +// back to the CSS mock box. +const gallery = plugin ? themeGallery(plugin.id, plugin.screenshots, previewIds.has(plugin.id)) : []; +const showGallery = theme && gallery.length > 0; +const showMockup = theme && gallery.length === 0; const applyUri = plugin ? themeApplyUri(plugin.id) : ''; const downloadName = plugin ? (theme ? orskinFileName(plugin.id) : `${plugin.id}.orplug`) : ''; @@ -386,11 +402,11 @@ const body = dbError {/* Featured frame — every image is rendered up front and toggled with `hidden`, so switching thumbnails never waits on a network fetch. */}
- {screenshots.map((shot, index) => ( + {gallery.map((shot, index) => ( {`${plugin.name} - {screenshots.length > 1 && ( + {gallery.length > 1 && (
- {screenshots.map((shot, index) => ( + {gallery.map((shot, index) => (