feat: implement createNodeWithEdgeToTarget mutation for enhanced node connections

- Added a new mutation to create nodes connected to existing nodes, allowing for more dynamic interactions on the canvas.
- Updated the CanvasPlacementContext to include the new mutation, improving the workflow for node creation and edge management.
- Enhanced optimistic updates for immediate UI feedback during node and edge creation processes.
- Refactored related components to support the new connection method, streamlining user interactions.
This commit is contained in:
Matthias
2026-03-28 17:50:45 +01:00
parent b3a1ed54db
commit 9694c50195
6 changed files with 552 additions and 85 deletions

View File

@@ -2,18 +2,9 @@
import { useEffect, useRef, useState } from "react";
import { useTheme } from "next-themes";
import {
Frame,
GitCompare,
Image,
Moon,
Sparkles,
StickyNote,
Sun,
Type,
type LucideIcon,
} from "lucide-react";
import { Moon, Sun } from "lucide-react";
import { CanvasNodeTemplatePicker } from "@/components/canvas/canvas-node-template-picker";
import { useCanvasPlacement } from "@/components/canvas/canvas-placement-context";
import { useCenteredFlowNodePosition } from "@/hooks/use-centered-flow-node-position";
import {
@@ -26,30 +17,7 @@ import {
CommandList,
CommandSeparator,
} from "@/components/ui/command";
import {
CANVAS_NODE_TEMPLATES,
type CanvasNodeTemplate,
} from "@/lib/canvas-node-templates";
const NODE_ICONS: Record<CanvasNodeTemplate["type"], LucideIcon> = {
image: Image,
text: Type,
prompt: Sparkles,
note: StickyNote,
frame: Frame,
compare: GitCompare,
};
const NODE_SEARCH_KEYWORDS: Partial<
Record<CanvasNodeTemplate["type"], string[]>
> = {
image: ["image", "photo", "foto"],
text: ["text", "typo"],
prompt: ["prompt", "ai", "generate"],
note: ["note", "sticky", "notiz"],
frame: ["frame", "artboard"],
compare: ["compare", "before", "after"],
};
import type { CanvasNodeTemplate } from "@/lib/canvas-node-templates";
export function CanvasCommandPalette() {
const [open, setOpen] = useState(false);
@@ -69,21 +37,16 @@ export function CanvasCommandPalette() {
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
const handleAddNode = (
type: CanvasNodeTemplate["type"],
data: CanvasNodeTemplate["defaultData"],
width: number,
height: number,
) => {
const handleAddNode = (template: CanvasNodeTemplate) => {
const stagger = (nodeCountRef.current % 8) * 24;
nodeCountRef.current += 1;
setOpen(false);
void createNodeWithIntersection({
type,
position: getCenteredPosition(width, height, stagger),
width,
height,
data,
type: template.type,
position: getCenteredPosition(template.width, template.height, stagger),
width: template.width,
height: template.height,
data: template.defaultData,
clientRequestId: crypto.randomUUID(),
}).catch((error) => {
console.error("[CanvasCommandPalette] createNode failed", error);
@@ -101,28 +64,7 @@ export function CanvasCommandPalette() {
<CommandInput placeholder="Suchen …" />
<CommandList>
<CommandEmpty>Keine Treffer.</CommandEmpty>
<CommandGroup heading="Knoten">
{CANVAS_NODE_TEMPLATES.map((template) => {
const Icon = NODE_ICONS[template.type];
return (
<CommandItem
key={template.type}
keywords={NODE_SEARCH_KEYWORDS[template.type] ?? []}
onSelect={() =>
handleAddNode(
template.type,
template.defaultData,
template.width,
template.height,
)
}
>
<Icon className="size-4" />
{template.label}
</CommandItem>
);
})}
</CommandGroup>
<CanvasNodeTemplatePicker onPick={handleAddNode} />
<CommandSeparator />
<CommandGroup heading="Erscheinungsbild">
<CommandItem