49 lines
3.1 KiB
TypeScript
49 lines
3.1 KiB
TypeScript
"use client";
|
|
import { CanvasImage } from "./canvas-image";
|
|
import { managedImageSource } from "@/lib/canvas-v2";
|
|
|
|
import { useRef } from "react";
|
|
import { Handle, Position, useViewport, type NodeProps } from "@xyflow/react";
|
|
import { FileText, Image as ImageIcon, Type } from "lucide-react";
|
|
import { elementCapabilities } from "@/lib/canvas-elements";
|
|
import { nodeDirections, type CanvasNode } from "@/lib/canvas";
|
|
import { NodeTitle } from "./node-title";
|
|
import { CanvasTextEditor } from "./canvas-text-editor";
|
|
import {useScopedNodeEditing} from "./node-editing";
|
|
import styles from "./canvas.module.css";
|
|
import { TransformableNode } from "./transformable-node";
|
|
export { NodeEditing } from "./transformable-node";
|
|
|
|
const positions = { top: Position.Top, right: Position.Right, bottom: Position.Bottom, left: Position.Left };
|
|
const labels = { top: "上方", right: "右侧", bottom: "下方", left: "左侧" };
|
|
|
|
export function MaterialNode(props: NodeProps<CanvasNode>) {
|
|
const { id, data, selected } = props;
|
|
const editing = useScopedNodeEditing(id,data.__headlessTarget);
|
|
const { zoom } = useViewport();
|
|
const preview = (props.width || 260) * zoom * (typeof window === "undefined" ? 1 : window.devicePixelRatio || 1) <= 1024;
|
|
const pointerStart = useRef({ x: 0, y: 0 });
|
|
const Icon = data.kind === "image" ? ImageIcon : data.kind === "script" ? FileText : Type;
|
|
return (
|
|
<TransformableNode {...props} className={`${styles.node} ${selected ? styles.selectedNode : ""}`}>
|
|
<header className={styles.nodeHeading}><Icon size={13} /><NodeTitle id={id} renderedTarget={data.__headlessTarget} title={data.title} selected={selected} /></header>
|
|
<Handle type="target" position={Position.Left} className={styles.legacyHandle} isConnectable={false} />
|
|
{data.kind === "image" ? <CanvasImage id={id} className={styles.image} src={managedImageSource(data.src, preview)} title={data.title} /> : (
|
|
<CanvasTextEditor id={id} data={data} selected={selected} />
|
|
)}
|
|
<Handle type="source" position={Position.Right} className={styles.legacyHandle} isConnectable={false} />
|
|
{nodeDirections.map((direction) => <Handle key={direction} id={direction} type="source" position={positions[direction]}
|
|
className={styles.centerHandle} isConnectableStart={selected}
|
|
aria-hidden={!selected} aria-label={`从${labels[direction]}连接节点`} role="button" tabIndex={selected ? 0 : -1}
|
|
onPointerDown={(event) => { pointerStart.current = { x: event.clientX, y: event.clientY }; }}
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
if (selected && elementCapabilities(data.kind).branchable && Math.hypot(event.clientX - pointerStart.current.x, event.clientY - pointerStart.current.y) < 5)
|
|
editing.branch(id, direction, { x: event.clientX, y: event.clientY });
|
|
}}
|
|
onKeyDown={(event) => { if (selected && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); event.stopPropagation(); const rect = event.currentTarget.getBoundingClientRect(); editing.branch(id, direction, { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }); } }}>
|
|
</Handle>)}
|
|
</TransformableNode>
|
|
);
|
|
}
|