feat: add interactive React Flow canvas and floating toolbars

This commit is contained in:
2026-11-06 00:52:07 +08:00
parent fec69c6e59
commit d4b45c5051
13 changed files with 778 additions and 36 deletions
+25
View File
@@ -0,0 +1,25 @@
"use client";
import { createContext, useContext } from "react";
import { Handle, Position, type NodeProps } from "@xyflow/react";
import { FileText, Image as ImageIcon, Type } from "lucide-react";
import type { CanvasNode } from "@/lib/canvas";
import styles from "./canvas.module.css";
export const NodeEditing = createContext({ begin: () => {}, edit: (_id: string, _text: string) => {} });
export function MaterialNode({ id, data, selected }: NodeProps<CanvasNode>) {
const editing = useContext(NodeEditing);
const Icon = data.kind === "image" ? ImageIcon : data.kind === "script" ? FileText : Type;
return (
<article className={`${styles.node} ${selected ? styles.selectedNode : ""}`}>
<header className={styles.nodeHeading}><Icon size={13} /><span>{data.title}</span></header>
<Handle type="target" position={Position.Left} />
{data.kind === "image" ? <img className={styles.image} src={data.src} alt={data.title} draggable={false} referrerPolicy="no-referrer" /> : (
<textarea className="nodrag nowheel" aria-label={data.title} placeholder={data.kind === "script" ? "写下你的脚本…" : "留住一个想法…"}
value={data.text} maxLength={100000} onFocus={editing.begin} onChange={(event) => editing.edit(id, event.target.value)} />
)}
<Handle type="source" position={Position.Right} />
</article>
);
}