fix: guard psd template dialog runtime errors
This commit is contained in:
parent
89dfc39284
commit
49b54c2387
@ -412,11 +412,20 @@ export function AgentPsdTemplateEditorDialog({
|
||||
? normalizeAgentPsdImagePlacement(effectiveImagePlacements[selectedImageLayer.id])
|
||||
: getDefaultAgentPsdImagePlacement();
|
||||
const fontFaceCss = useMemo(() => buildPreviewFontFaces(selectedTemplate), [selectedTemplate]);
|
||||
const selectedTemplateSelectValue = selectedTemplate?.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
if (templates.length > 0 && !templates.some((template) => template.id === selectedTemplateId)) {
|
||||
if (templates.length === 0) {
|
||||
if (selectedTemplateId) {
|
||||
setSelectedTemplateId("");
|
||||
}
|
||||
setSelectedLayerId("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!templates.some((template) => template.id === selectedTemplateId)) {
|
||||
setSelectedTemplateId(templates[0].id);
|
||||
}
|
||||
}, [open, selectedTemplateId, templates]);
|
||||
@ -436,6 +445,9 @@ export function AgentPsdTemplateEditorDialog({
|
||||
setImageSizes({});
|
||||
return;
|
||||
}
|
||||
if (typeof window === "undefined" || typeof window.Image !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
for (const layer of editableImageLayers) {
|
||||
@ -496,10 +508,44 @@ export function AgentPsdTemplateEditorDialog({
|
||||
};
|
||||
|
||||
updateScale();
|
||||
const observer = new ResizeObserver(updateScale);
|
||||
observer.observe(element);
|
||||
let animationFrameId: number | null = null;
|
||||
const scheduleScaleUpdate = () => {
|
||||
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
||||
if (animationFrameId !== null) {
|
||||
window.cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
animationFrameId = window.requestAnimationFrame(() => {
|
||||
animationFrameId = null;
|
||||
updateScale();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateScale();
|
||||
};
|
||||
|
||||
if (typeof ResizeObserver !== "undefined") {
|
||||
const observer = new ResizeObserver(scheduleScaleUpdate);
|
||||
observer.observe(element);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (animationFrameId !== null && typeof window !== "undefined") {
|
||||
window.cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("resize", scheduleScaleUpdate);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("resize", scheduleScaleUpdate);
|
||||
if (animationFrameId !== null) {
|
||||
window.cancelAnimationFrame(animationFrameId);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [selectedTemplate]);
|
||||
|
||||
@ -513,11 +559,23 @@ export function AgentPsdTemplateEditorDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const focusTextarea = () => {
|
||||
textarea.focus();
|
||||
const valueLength = textarea.value.length;
|
||||
textarea.setSelectionRange(valueLength, valueLength);
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
||||
const frameId = window.requestAnimationFrame(focusTextarea);
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frameId);
|
||||
};
|
||||
}
|
||||
|
||||
const timeoutId = window.setTimeout(focusTextarea, 0);
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, [selectedTextLayer?.id]);
|
||||
|
||||
async function handleSlotUpload(slotId: string, event: ChangeEvent<HTMLInputElement>) {
|
||||
@ -635,21 +693,6 @@ export function AgentPsdTemplateEditorDialog({
|
||||
window.addEventListener("pointercancel", handlePointerUp);
|
||||
}
|
||||
|
||||
function handleImageWheel(
|
||||
layer: AgentPsdTemplateImageLayerSlot,
|
||||
event: ReactPointerEvent<HTMLDivElement>
|
||||
) {
|
||||
if (!String(effectiveImageAssignments[layer.id] || "").trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const delta = event.deltaY < 0 ? 0.08 : -0.08;
|
||||
nudgeImageScale(layer.id, delta);
|
||||
}
|
||||
|
||||
async function handleRender() {
|
||||
if (!selectedTemplate) {
|
||||
toast.error("请先选择 PSD 模板");
|
||||
@ -715,7 +758,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>选择模板</Label>
|
||||
<Select value={selectedTemplateId} onValueChange={setSelectedTemplateId}>
|
||||
<Select value={selectedTemplateSelectValue} onValueChange={setSelectedTemplateId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={isLoading ? "加载模板中..." : "请选择模板"} />
|
||||
</SelectTrigger>
|
||||
@ -937,7 +980,6 @@ export function AgentPsdTemplateEditorDialog({
|
||||
tabIndex={0}
|
||||
onClick={() => setSelectedLayerId(layer.id)}
|
||||
onPointerDown={(event) => handleImageDragStart(layer, event)}
|
||||
onWheel={(event) => handleImageWheel(layer, event)}
|
||||
className="absolute overflow-hidden rounded-lg transition-all"
|
||||
style={{
|
||||
...frameStyle,
|
||||
@ -952,50 +994,9 @@ export function AgentPsdTemplateEditorDialog({
|
||||
<div className="absolute left-2 top-2 rounded-full bg-black/70 px-2 py-1 text-[11px] text-white">
|
||||
{layer.layerName}
|
||||
</div>
|
||||
<div className="absolute right-2 top-2 flex items-center gap-1 rounded-full bg-black/70 p-1 text-white shadow-lg">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1 transition hover:bg-white/10"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
nudgeImageScale(layer.id, -0.1);
|
||||
}}
|
||||
aria-label="缩小图片"
|
||||
title="缩小"
|
||||
>
|
||||
<ZoomOut className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1 transition hover:bg-white/10"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
nudgeImageScale(layer.id, 0.1);
|
||||
}}
|
||||
aria-label="放大图片"
|
||||
title="放大"
|
||||
>
|
||||
<ZoomIn className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1 transition hover:bg-white/10"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
resetImagePlacement(layer.id);
|
||||
}}
|
||||
aria-label="重置图片位置和缩放"
|
||||
title="重置"
|
||||
>
|
||||
<RefreshCcw className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute bottom-2 right-2 flex items-center gap-1 rounded-full bg-orange-500/85 px-2 py-1 text-[11px] text-white">
|
||||
<Move className="h-3 w-3" />
|
||||
拖动或滚轮缩放
|
||||
拖动调整
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -1025,6 +1026,56 @@ export function AgentPsdTemplateEditorDialog({
|
||||
);
|
||||
})}
|
||||
|
||||
{orderedEditableImageLayers.map((layer) => {
|
||||
const isSelected = selectedLayerId === layer.id;
|
||||
const hasAssignedImage = Boolean(
|
||||
String(effectiveImageAssignments[layer.id] || "").trim()
|
||||
);
|
||||
if (!isSelected || !hasAssignedImage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${layer.id}-controls`}
|
||||
className="absolute flex items-center gap-1 rounded-full bg-black/72 p-1 text-white shadow-lg"
|
||||
style={{
|
||||
left: layer.x + layer.width - 112,
|
||||
top: Math.max(8, layer.y - 40),
|
||||
zIndex: 25,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1.5 transition hover:bg-white/10"
|
||||
onClick={() => nudgeImageScale(layer.id, -0.1)}
|
||||
aria-label="缩小图片"
|
||||
title="缩小"
|
||||
>
|
||||
<ZoomOut className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1.5 transition hover:bg-white/10"
|
||||
onClick={() => nudgeImageScale(layer.id, 0.1)}
|
||||
aria-label="放大图片"
|
||||
title="放大"
|
||||
>
|
||||
<ZoomIn className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full p-1.5 transition hover:bg-white/10"
|
||||
onClick={() => resetImagePlacement(layer.id)}
|
||||
aria-label="重置图片位置和缩放"
|
||||
title="重置"
|
||||
>
|
||||
<RefreshCcw className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{orderedEditableTextLayers.map((layer) => {
|
||||
const isSelected = selectedLayerId === layer.id;
|
||||
const frameStyle = getLayerFrameStyle(layer, isSelected);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user