Fix PSD template preview fallback
This commit is contained in:
parent
2a793dcbba
commit
169bf2fe4c
@ -271,6 +271,111 @@ function buildTemplatePreviewThumbUrl(
|
||||
return `/api/image-proxy?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
function buildTemplatePreviewFallbackUrls(
|
||||
template: Pick<AgentPsdTemplate, "previewUrl" | "renderBaseUrl">,
|
||||
params?: {
|
||||
width?: number;
|
||||
quality?: number;
|
||||
preferRenderBase?: boolean;
|
||||
}
|
||||
) {
|
||||
const sourceUrls = (
|
||||
params?.preferRenderBase
|
||||
? [template.renderBaseUrl, template.previewUrl]
|
||||
: [template.previewUrl, template.renderBaseUrl]
|
||||
)
|
||||
.map((url) => String(url || "").trim())
|
||||
.filter(Boolean);
|
||||
const candidates = [
|
||||
...sourceUrls.map((url) =>
|
||||
buildTemplatePreviewThumbUrl(url, {
|
||||
width: params?.width,
|
||||
quality: params?.quality,
|
||||
})
|
||||
),
|
||||
...sourceUrls,
|
||||
];
|
||||
const seen = new Set<string>();
|
||||
|
||||
return candidates.filter((url) => {
|
||||
if (!url || seen.has(url)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(url);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function TemplatePreviewImage({
|
||||
template,
|
||||
alt,
|
||||
className,
|
||||
width,
|
||||
quality,
|
||||
preferRenderBase,
|
||||
decoding = "async",
|
||||
fetchPriority,
|
||||
loading = "lazy",
|
||||
}: {
|
||||
template: Pick<AgentPsdTemplate, "name" | "previewUrl" | "renderBaseUrl">;
|
||||
alt?: string;
|
||||
className?: string;
|
||||
width?: number;
|
||||
quality?: number;
|
||||
preferRenderBase?: boolean;
|
||||
decoding?: "async" | "auto" | "sync";
|
||||
fetchPriority?: "auto" | "high" | "low";
|
||||
loading?: "eager" | "lazy";
|
||||
}) {
|
||||
const fallbackUrls = useMemo(
|
||||
() =>
|
||||
buildTemplatePreviewFallbackUrls(template, {
|
||||
width,
|
||||
quality,
|
||||
preferRenderBase,
|
||||
}),
|
||||
[preferRenderBase, quality, template.previewUrl, template.renderBaseUrl, width]
|
||||
);
|
||||
const fallbackUrlKey = fallbackUrls.join("\n");
|
||||
const [activeUrlIndex, setActiveUrlIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveUrlIndex(0);
|
||||
}, [fallbackUrlKey]);
|
||||
|
||||
const src = fallbackUrls[activeUrlIndex] || "";
|
||||
if (!src) {
|
||||
return (
|
||||
<div
|
||||
aria-label={alt || template.name}
|
||||
className={className}
|
||||
role="img"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const intrinsicSize = Math.max(1, Math.round(width || 1));
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noNoninteractiveElementInteractions: the image swaps to the next remote fallback when a thumbnail URL fails.
|
||||
// biome-ignore lint/performance/noImgElement: these dynamic PSD previews already use the app image proxy and need browser-level error fallback.
|
||||
<img
|
||||
alt={alt || template.name}
|
||||
className={className}
|
||||
decoding={decoding}
|
||||
fetchPriority={fetchPriority}
|
||||
height={intrinsicSize}
|
||||
loading={loading}
|
||||
onError={() =>
|
||||
setActiveUrlIndex((current) =>
|
||||
current + 1 < fallbackUrls.length ? current + 1 : current
|
||||
)
|
||||
}
|
||||
src={src}
|
||||
width={intrinsicSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getScrollAreaViewportElement(root: HTMLDivElement | null) {
|
||||
return root?.querySelector(
|
||||
"[data-radix-scroll-area-viewport]"
|
||||
@ -521,14 +626,11 @@ function renderTemplateSelectContent(
|
||||
: "flex h-[46px] w-[46px] shrink-0 overflow-hidden rounded-lg border border-white/10 bg-white/5"
|
||||
}
|
||||
>
|
||||
<img
|
||||
alt={template.name}
|
||||
<TemplatePreviewImage
|
||||
className="h-full w-full object-cover"
|
||||
decoding="async"
|
||||
loading="lazy"
|
||||
src={buildTemplatePreviewThumbUrl(template.previewUrl, {
|
||||
width: compact ? 96 : 92,
|
||||
})}
|
||||
template={template}
|
||||
width={compact ? 96 : 92}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
@ -580,15 +682,12 @@ function renderTemplatePickerCardContent(
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-3.5">
|
||||
<div className="flex h-14 w-14 shrink-0 overflow-hidden rounded-xl border border-white/10 bg-white/5 shadow-inner">
|
||||
<img
|
||||
alt={template.name}
|
||||
<TemplatePreviewImage
|
||||
className="h-full w-full object-cover"
|
||||
decoding="async"
|
||||
fetchPriority={eager ? "high" : "low"}
|
||||
loading={eager ? "eager" : "lazy"}
|
||||
src={buildTemplatePreviewThumbUrl(template.previewUrl, {
|
||||
width: 112,
|
||||
})}
|
||||
template={template}
|
||||
width={112}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
@ -2876,17 +2975,12 @@ export function AgentPsdTemplateEditorDialog({
|
||||
aspectRatio: `${Math.max(1, selectedTemplate.width)} / ${Math.max(1, selectedTemplate.height)}`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={selectedTemplate.name}
|
||||
<TemplatePreviewImage
|
||||
className="h-full w-full object-contain"
|
||||
decoding="async"
|
||||
src={buildTemplatePreviewThumbUrl(
|
||||
selectedTemplate.previewUrl,
|
||||
{
|
||||
width: 440,
|
||||
quality: 78,
|
||||
}
|
||||
)}
|
||||
loading="eager"
|
||||
quality={78}
|
||||
template={selectedTemplate}
|
||||
width={440}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -2966,18 +3060,14 @@ export function AgentPsdTemplateEditorDialog({
|
||||
>
|
||||
{shouldShowCompositeFallback &&
|
||||
orderedVisiblePreviewLayers.length > 0 ? (
|
||||
<img
|
||||
<TemplatePreviewImage
|
||||
alt={`${selectedTemplate.name} 编辑底图`}
|
||||
className="absolute inset-0 h-full w-full object-cover"
|
||||
decoding="async"
|
||||
src={buildTemplatePreviewThumbUrl(
|
||||
selectedTemplate.renderBaseUrl ||
|
||||
selectedTemplate.previewUrl,
|
||||
{
|
||||
width: 1280,
|
||||
quality: 80,
|
||||
}
|
||||
)}
|
||||
loading="eager"
|
||||
preferRenderBase
|
||||
quality={80}
|
||||
template={selectedTemplate}
|
||||
width={1280}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user