fix: stabilize psd text preview positioning
This commit is contained in:
parent
9b745e5591
commit
d450bec1d7
@ -821,31 +821,47 @@ function getTextLayerRenderFrame(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextLayerContentStyle(
|
function renderTextLayerPreviewContent(
|
||||||
layer: AgentPsdTemplateTextLayerSlot,
|
layer: AgentPsdTemplateTextLayerSlot,
|
||||||
value: string,
|
value: string,
|
||||||
color: string
|
color: string
|
||||||
): CSSProperties {
|
) {
|
||||||
const resolvedLayout = getTextLayerRenderFrame(layer, value);
|
const resolvedText = getResolvedTextPreview(layer, value);
|
||||||
|
const resolvedLayout = getTextLayerRenderFrame(layer, resolvedText);
|
||||||
|
|
||||||
return {
|
return (
|
||||||
width: resolvedLayout.width,
|
<div
|
||||||
height: resolvedLayout.height,
|
className="relative overflow-visible"
|
||||||
fontFamily: buildTextLayerFontFamily(layer),
|
style={{
|
||||||
fontSize: resolvedLayout.fontSize,
|
width: resolvedLayout.width,
|
||||||
lineHeight: layer.lineHeight,
|
height: resolvedLayout.height,
|
||||||
letterSpacing: `${layer.letterSpacing}px`,
|
}}
|
||||||
color,
|
>
|
||||||
fontWeight: layer.fontWeight,
|
{resolvedLayout.lines.map((line, index) => (
|
||||||
textAlign: layer.textAlign,
|
<div
|
||||||
paddingLeft: layer.paddingX,
|
className="absolute overflow-visible whitespace-pre"
|
||||||
paddingRight: layer.paddingX,
|
key={`${layer.id}-preview-line-${index + 1}`}
|
||||||
paddingTop: layer.paddingY,
|
style={{
|
||||||
paddingBottom: layer.paddingY,
|
top: layer.paddingY + index * resolvedLayout.lineHeightPx,
|
||||||
whiteSpace: "pre",
|
width: resolvedLayout.width,
|
||||||
wordBreak: "keep-all",
|
paddingLeft: layer.paddingX,
|
||||||
overflowWrap: "normal",
|
paddingRight: layer.paddingX,
|
||||||
};
|
fontFamily: buildTextLayerFontFamily(layer),
|
||||||
|
fontSize: resolvedLayout.fontSize,
|
||||||
|
lineHeight: `${resolvedLayout.fontSize}px`,
|
||||||
|
letterSpacing: `${layer.letterSpacing}px`,
|
||||||
|
color,
|
||||||
|
fontWeight: layer.fontWeight,
|
||||||
|
textAlign: layer.textAlign,
|
||||||
|
wordBreak: "keep-all",
|
||||||
|
overflowWrap: "normal",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{line || "\u00a0"}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAssignedImageMaskStyle(
|
function getAssignedImageMaskStyle(
|
||||||
@ -916,24 +932,6 @@ function hasCustomizedTextLayerAppearance(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInlineTextEditorStyle(
|
|
||||||
layer: AgentPsdTemplateTextLayerSlot,
|
|
||||||
preserveSourceAppearance: boolean,
|
|
||||||
value: string,
|
|
||||||
color: string
|
|
||||||
): CSSProperties {
|
|
||||||
const baseStyle = getTextLayerContentStyle(layer, value, color);
|
|
||||||
return {
|
|
||||||
...baseStyle,
|
|
||||||
background: "transparent",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
overflow: "visible",
|
|
||||||
color: preserveSourceAppearance ? "transparent" : color,
|
|
||||||
caretColor: color,
|
|
||||||
WebkitTextFillColor: preserveSourceAppearance ? "transparent" : color,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextLayerNameBadgeClassName() {
|
function getTextLayerNameBadgeClassName() {
|
||||||
return "pointer-events-none absolute left-0 top-0 -translate-y-[calc(100%+6px)] rounded-full bg-black/70 px-2 py-1 text-[11px] whitespace-nowrap text-white shadow-lg backdrop-blur-md";
|
return "pointer-events-none absolute left-0 top-0 -translate-y-[calc(100%+6px)] rounded-full bg-black/70 px-2 py-1 text-[11px] whitespace-nowrap text-white shadow-lg backdrop-blur-md";
|
||||||
}
|
}
|
||||||
@ -1059,7 +1057,6 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
|
|
||||||
const previewViewportRef = useRef<HTMLDivElement | null>(null);
|
const previewViewportRef = useRef<HTMLDivElement | null>(null);
|
||||||
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const inlineTextEditorRef = useRef<HTMLTextAreaElement | null>(null);
|
|
||||||
const previousSelectedTemplateRef = useRef<AgentPsdTemplate | null>(null);
|
const previousSelectedTemplateRef = useRef<AgentPsdTemplate | null>(null);
|
||||||
const imageDragStateRef = useRef<{
|
const imageDragStateRef = useRef<{
|
||||||
layerId: string;
|
layerId: string;
|
||||||
@ -1735,45 +1732,6 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
};
|
};
|
||||||
}, [selectedTemplate]);
|
}, [selectedTemplate]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!selectedTextLayer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const textarea = inlineTextEditorRef.current;
|
|
||||||
if (!textarea) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const focusTextarea = () => {
|
|
||||||
textarea.focus();
|
|
||||||
const valueLength = textarea.value.length;
|
|
||||||
try {
|
|
||||||
textarea.setSelectionRange(valueLength, valueLength);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
"[AgentPsdTemplateEditorDialog] setSelectionRange failed:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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(
|
async function handleSlotUpload(
|
||||||
slotId: string,
|
slotId: string,
|
||||||
event: ChangeEvent<HTMLInputElement>
|
event: ChangeEvent<HTMLInputElement>
|
||||||
@ -2490,16 +2448,11 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
height: textFrame.height,
|
height: textFrame.height,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{renderTextLayerPreviewContent(
|
||||||
className="whitespace-pre"
|
layer,
|
||||||
style={getTextLayerContentStyle(
|
value || layer.placeholder || layer.layerName,
|
||||||
layer,
|
resolvedTextColor
|
||||||
value || layer.placeholder || layer.layerName,
|
)}
|
||||||
resolvedTextColor
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{value || layer.placeholder || layer.layerName}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -2673,43 +2626,31 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
src={layer.sourceUrl}
|
src={layer.sourceUrl}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
{preserveSourceAppearance ? null : (
|
||||||
className="absolute"
|
<div
|
||||||
style={
|
className="absolute"
|
||||||
preserveSourceAppearance
|
style={{
|
||||||
? { inset: 0 }
|
left: editedTextFrame.offsetX,
|
||||||
: {
|
top: editedTextFrame.offsetY,
|
||||||
left: editedTextFrame.offsetX,
|
width: editedTextFrame.width,
|
||||||
top: editedTextFrame.offsetY,
|
height: editedTextFrame.height,
|
||||||
width: editedTextFrame.width,
|
}}
|
||||||
height: editedTextFrame.height,
|
>
|
||||||
}
|
{renderTextLayerPreviewContent(
|
||||||
}
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
className="resize-none border-none bg-transparent outline-none"
|
|
||||||
onChange={(event) =>
|
|
||||||
setTextAssignments((current) => ({
|
|
||||||
...current,
|
|
||||||
[layer.id]: event.target.value,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
ref={inlineTextEditorRef}
|
|
||||||
style={getInlineTextEditorStyle(
|
|
||||||
layer,
|
layer,
|
||||||
preserveSourceAppearance,
|
|
||||||
currentValue,
|
currentValue,
|
||||||
resolvedTextColor
|
resolvedTextColor
|
||||||
)}
|
)}
|
||||||
value={currentValue}
|
</div>
|
||||||
wrap="off"
|
)}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={getTextLayerNameBadgeClassName()}
|
className={getTextLayerNameBadgeClassName()}
|
||||||
>
|
>
|
||||||
{layer.layerName}
|
{layer.layerName}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={getTextLayerHintBadgeClassName()}>
|
||||||
|
请在右侧面板编辑
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user