Add pure text resource nodes
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-19 09:50:49 +08:00
parent 3a4604286f
commit 8a3f0bea15

View File

@ -379,8 +379,8 @@ const PRESET_VIEWPORT = { x: 144, y: 100, zoom: 0.77 };
const PRESET_WORKFLOW_CENTER = { x: 1024, y: 512 };
const CANVAS_EXTENT = 20000;
const OFFICIAL_NODE_GAP = 100;
const OFFICIAL_TEXT_RESOURCE_HEIGHT = 232;
const OFFICIAL_TEXT_RESOURCE_WIDTH = 350;
const OFFICIAL_TEXT_RESOURCE_HEIGHT = 330;
const OFFICIAL_TEXT_RESOURCE_WIDTH = 560;
const OFFICIAL_IMAGE_TOOLBAR_SCALE = 0.7;
const OFFICIAL_COMPOSER_SCALE = 0.546;
const OFFICIAL_ZOOM_DOCK_SCALE = 0.65;
@ -1420,13 +1420,16 @@ function inferLegacyTargetModuleKind(node: CloneNode): AddModuleKind | null {
return null;
}
function normalizeTextResourceNode(node: CloneNode): CloneNode {
function normalizeTextResourceNode(node: CloneNode, fallbackIndex: number): CloneNode {
const shouldUseOfficialSize =
Boolean(node.draftValues?.manualEditing) || /^(文本|文本生成|文本节点)/.test(String(node.title || ""));
const title = node.title && !/^(文本|文本生成|文本节点)(\s*\d+)?$/.test(node.title)
? node.title
: `文本节点 ${extractTrailingNumber(node.title) || fallbackIndex}`;
return {
...node,
kind: "script",
title: node.title && !/文本生成|文本节点/.test(node.title) ? node.title : "文本",
title,
subtitle: "文本资源",
inputType: "text",
width: shouldUseOfficialSize ? OFFICIAL_TEXT_RESOURCE_WIDTH : node.width > 260 ? node.width : OFFICIAL_TEXT_RESOURCE_WIDTH,
@ -1488,7 +1491,7 @@ function normalizeTargetModuleNode(node: CloneNode, kind: AddModuleKind, fallbac
function normalizeRestoredNode(node: CloneNode, fallbackIndex: number): CloneNode {
const action = getDraftString(node, "action");
if (action === "TEXT_RESOURCE" && !getPresetMode(node)) {
return normalizeTextResourceNode(node);
return normalizeTextResourceNode(node, fallbackIndex);
}
const kind = getTargetModuleKind(node) || inferLegacyTargetModuleKind(node);
@ -2080,15 +2083,14 @@ export function LiblibVideoCanvasReplica({ canvasId, isLoading = false }: Liblib
switch (actionLabel) {
case "自己编写内容": {
draftUpdates = {
action: "TEXT_GENERATE",
content: Array.isArray(anchor.draftValues?.content) ? anchor.draftValues?.content : [],
manualEditing: true,
manualTextComposer: true,
nodeType: "text",
};
selectedId = anchor.id;
toast.success("已打开文本编写对话框");
const textNode = createTextResourceNode({
content: "",
title: `文本节点 ${nodes.length + nextNodes.length + 1}`,
x: rightX,
y: centerY(OFFICIAL_TEXT_RESOURCE_HEIGHT),
});
connectFromAnchor(textNode);
toast.success("已创建纯文本节点");
break;
}
case "文生视频": {
@ -6362,6 +6364,14 @@ function CloneCanvasNode({
);
}
if (getDraftString(node, "action") === "TEXT_RESOURCE") {
return (
<CanvasNodeShell node={node} onContextMenu={onContextMenu} onOpenConnectionMenu={onOpenConnectionMenu} onPointerDown={onPointerDown} selected={selected}>
<PureTextResourceCanvasNode node={node} onTextResourceChange={onTextResourceChange} selected={selected} />
</CanvasNodeShell>
);
}
if (node.kind === "media") {
return (
<CanvasNodeShell node={node} onContextMenu={onContextMenu} onOpenConnectionMenu={onOpenConnectionMenu} onPointerDown={onPointerDown} selected={selected}>
@ -6924,6 +6934,60 @@ function PresetMediaCanvasNode({ node, selected }: { node: CloneNode; selected:
);
}
function PureTextResourceCanvasNode({
node,
onTextResourceChange,
selected,
}: {
node: CloneNode;
onTextResourceChange: (content: string) => void;
selected: boolean;
}) {
const contentValues = node.draftValues?.content;
const textContent =
Array.isArray(contentValues) && typeof contentValues[0] === "string"
? contentValues[0]
: "";
return (
<div className="h-full w-full">
<div className="mb-2 flex h-6 items-center gap-1 text-[20px] font-semibold text-white/50">
<FileText className="h-5 w-5" />
<span>{node.title}</span>
</div>
<div
className={cn(
"relative h-[calc(100%-32px)] overflow-hidden rounded-[28px] border-[3px] border-white/54 bg-[#111] text-white shadow-[0_18px_60px_rgba(0,0,0,0.36)]",
selected && "border-white/70"
)}
>
<textarea
className={cn(
"absolute left-[54px] right-[54px] top-[70px] z-10 h-[96px] resize-none bg-transparent text-center text-[22px] leading-8 text-white/72 outline-none placeholder:text-white/48",
textContent && "top-[44px] h-[calc(100%-88px)] text-left text-[18px] leading-8"
)}
onChange={(event) => onTextResourceChange(event.target.value)}
onPointerDown={(event) => event.stopPropagation()}
onWheel={(event) => event.stopPropagation()}
placeholder="请编写内容,开始你的创作。"
value={textContent}
/>
{!textContent ? (
<div className="pointer-events-none absolute left-1/2 top-1/2 flex w-[166px] -translate-x-1/2 -translate-y-[12%] flex-col gap-[16px]">
{[166, 166, 166, 98].map((width, index) => (
<span className="h-[12px] rounded-full bg-white/22" key={`${node.id}-text-line-${index}`} style={{ width }} />
))}
</div>
) : null}
<span className="pointer-events-none absolute bottom-7 right-7 h-5 w-5">
<span className="absolute bottom-0 right-0 h-[3px] w-7 origin-bottom-right -rotate-45 rounded-full bg-white/28" />
<span className="absolute bottom-1 right-0 h-[3px] w-[18px] origin-bottom-right -rotate-45 rounded-full bg-white/24" />
</span>
</div>
</div>
);
}
function PresetScriptCanvasNode({
node,
onTextResourceChange,