fix(detail+docker): per-aspect template preview + Debian frontend base
- Template detail page now shows the render matching the SELECTED aspect (poster +
preview video) instead of the 16:9 cover cropped into a 9:16/1:1 box. TemplateVariant
carries per-aspect image/previewVideo; fetchTemplateVariants + the detail page wire them.
- AppShowcase3D ships a distinct preview video per aspect (seed PERASPECT_VIDEO).
- Frontend Dockerfile: Alpine -> node:20-slim (glibc). Fixes next-swc ("ld-linux..."
load failure that broke `next build` once libc6-compat was removed) AND the original
CI Alpine-CDN issue. Healthcheck switched to node (slim has no wget).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ async function resolveTemplate(id: string): Promise<VideoCatalogTemplate | null>
|
||||
const base = adminProjectToCatalogTemplate(admin);
|
||||
const variants = (await fetchTemplateVariants(id))
|
||||
.filter((v) => SUPPORTED_ASPECTS.has(v.aspect as TemplateDetailAspectRatio))
|
||||
.map((v) => ({ aspect: v.aspect as TemplateDetailAspectRatio, projectId: v.projectId }));
|
||||
.map((v) => ({ aspect: v.aspect as TemplateDetailAspectRatio, projectId: v.projectId, image: v.image, previewVideo: v.previewVideo }));
|
||||
return { ...base, variants };
|
||||
}
|
||||
return VIDEO_TEMPLATES_CATALOG.find((item) => item.id === id) ?? null;
|
||||
|
||||
@@ -32,8 +32,10 @@ export function TemplateDetailPreview({
|
||||
onSelectAspect,
|
||||
}: TemplateDetailPreviewProps) {
|
||||
const aspectOptions = getTemplateDetailAspectRatios(template);
|
||||
const posterSrc = template.coverImageUrl ?? getVideoTemplateImageSrc(template.id);
|
||||
const videoSrc = template.previewVideoUrl ?? getTemplatePreviewVideoSrc(template.id);
|
||||
// Use the render that matches the selected aspect (not the 16:9 cover cropped).
|
||||
const variant = template.variants?.find((v) => v.aspect === selectedAspect);
|
||||
const posterSrc = variant?.image ?? template.coverImageUrl ?? getVideoTemplateImageSrc(template.id);
|
||||
const videoSrc = variant?.previewVideo ?? template.previewVideoUrl ?? getTemplatePreviewVideoSrc(template.id);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -214,13 +214,18 @@ export async function fetchProject(slug: string): Promise<AdminProject | null> {
|
||||
* studio copies. Returns [] when none / unreachable. */
|
||||
export async function fetchTemplateVariants(
|
||||
slug: string
|
||||
): Promise<Array<{ aspect: string; projectId: string }>> {
|
||||
): Promise<Array<{ aspect: string; projectId: string; image?: string; previewVideo?: string }>> {
|
||||
const c = await safeGet<{
|
||||
projects?: Array<{ id?: string; aspect?: string; is_published?: boolean }>;
|
||||
projects?: Array<{ id?: string; aspect?: string; is_published?: boolean; image?: string | null; full_demo?: string | null; demo?: string | null }>;
|
||||
}>(`/v1/templates/${encodeURIComponent(slug)}`);
|
||||
return (c?.projects ?? [])
|
||||
.filter((p) => p?.id && p?.is_published && p?.aspect)
|
||||
.map((p) => ({ aspect: p.aspect as string, projectId: p.id as string }));
|
||||
.map((p) => ({
|
||||
aspect: p.aspect as string,
|
||||
projectId: p.id as string,
|
||||
image: p.image ?? undefined,
|
||||
previewVideo: p.full_demo ?? p.demo ?? undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
/** True when the gateway content endpoint is reachable. */
|
||||
|
||||
@@ -63,6 +63,10 @@ export type TemplateDetailAspectRatio = "16:9" | "1:1" | "9:16";
|
||||
export interface TemplateVariant {
|
||||
aspect: TemplateDetailAspectRatio;
|
||||
projectId: string;
|
||||
/** Per-aspect thumbnail + preview video so the detail page shows the render
|
||||
* that actually matches the selected aspect (not the 16:9 cover cropped). */
|
||||
image?: string;
|
||||
previewVideo?: string;
|
||||
}
|
||||
|
||||
export const TEMPLATE_STYLE_COUNT = 4;
|
||||
|
||||
Reference in New Issue
Block a user