From ca40f5cb13dcf7acc2a31bdae4449e5652a572fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2026 17:58:58 +0100 Subject: [PATCH] feat: enhance canvas and layout components with new features and improvements - Added remote image patterns to the Next.js configuration for enhanced image handling. - Updated TypeScript configuration to exclude the 'implement' directory. - Refactored layout component to fetch initial authentication token and pass it to Providers. - Replaced CanvasToolbar with CanvasSidebar for improved UI layout and functionality. - Enhanced Canvas component with new drag-and-drop file upload capabilities and batch node movement. - Updated various node components to support new status handling and improved user interactions. - Added debounced saving for note and prompt nodes to optimize performance. --- app/(app)/canvas/[canvasId]/page.tsx | 10 +- app/dashboard/page.tsx | 4 +- app/layout.tsx | 7 +- components/canvas/canvas-sidebar.tsx | 72 ++++++- components/canvas/canvas.tsx | 182 ++++++++++++---- components/canvas/node-types.ts | 25 ++- components/canvas/nodes/ai-image-node.tsx | 86 ++++---- components/canvas/nodes/base-node-wrapper.tsx | 37 ++-- components/canvas/nodes/compare-node.tsx | 50 +++-- components/canvas/nodes/frame-node.tsx | 72 +++++-- components/canvas/nodes/group-node.tsx | 65 +++++- components/canvas/nodes/image-node.tsx | 195 ++++++++++++++++-- components/canvas/nodes/note-node.tsx | 75 ++++++- components/canvas/nodes/prompt-node.tsx | 99 +++++++-- components/canvas/nodes/text-node.tsx | 83 +++++++- convex/_generated/api.d.ts | 2 + convex/helpers.ts | 4 + convex/nodes.ts | 44 +++- convex/storage.ts | 10 + hooks/use-debounced-callback.ts | 37 ++++ implement/README.md | 103 +++++++++ implement/image-node.tsx | 187 +++++++++++++++++ implement/nodes-list-patch.ts | 37 ++++ implement/storage.ts | 14 ++ lib/canvas-utils.ts | 53 +++-- next.config.ts | 15 +- tsconfig.json | 2 +- 27 files changed, 1363 insertions(+), 207 deletions(-) create mode 100644 convex/storage.ts create mode 100644 hooks/use-debounced-callback.ts create mode 100644 implement/README.md create mode 100644 implement/image-node.tsx create mode 100644 implement/nodes-list-patch.ts create mode 100644 implement/storage.ts diff --git a/app/(app)/canvas/[canvasId]/page.tsx b/app/(app)/canvas/[canvasId]/page.tsx index b987d52..1864148 100644 --- a/app/(app)/canvas/[canvasId]/page.tsx +++ b/app/(app)/canvas/[canvasId]/page.tsx @@ -1,7 +1,7 @@ import { notFound, redirect } from "next/navigation"; import Canvas from "@/components/canvas/canvas"; -import CanvasToolbar from "@/components/canvas/canvas-toolbar"; +import CanvasSidebar from "@/components/canvas/canvas-sidebar"; import { api } from "@/convex/_generated/api"; import type { Id } from "@/convex/_generated/dataModel"; import { fetchAuthQuery, isAuthenticated } from "@/lib/auth-server"; @@ -48,9 +48,11 @@ export default async function CanvasPage({ } return ( -
- - +
+ +
+ +
); } diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 03d907c..daa49a4 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -340,7 +340,7 @@ export default function DashboardPage() { + )}
- )} + + {isUploading ? ( +
+
+
+ Wird hochgeladen... +
+
+ ) : data.url ? ( +
+ {data.filename +
+ ) : ( +
+ πŸ“ + Klicken oder hierhin ziehen + PNG, JPG, WebP +
+ )} + + {data.filename && data.url && ( +

+ {data.filename} +

+ )} +
+ + + ); diff --git a/components/canvas/nodes/note-node.tsx b/components/canvas/nodes/note-node.tsx index 952fc46..f4e1006 100644 --- a/components/canvas/nodes/note-node.tsx +++ b/components/canvas/nodes/note-node.tsx @@ -1,20 +1,83 @@ "use client"; -import { type Node, type NodeProps } from "@xyflow/react"; - +import { useState, useCallback, useEffect } from "react"; +import { type NodeProps, type Node } from "@xyflow/react"; +import { useMutation } from "convex/react"; +import { api } from "@/convex/_generated/api"; +import type { Id } from "@/convex/_generated/dataModel"; +import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; import BaseNodeWrapper from "./base-node-wrapper"; -export type NoteNodeData = { +type NoteNodeData = { content?: string; + _status?: string; + _statusMessage?: string; }; export type NoteNode = Node; -export default function NoteNode({ data, selected }: NodeProps) { +export default function NoteNode({ id, data, selected }: NodeProps) { + const updateData = useMutation(api.nodes.updateData); + const [content, setContent] = useState(data.content ?? ""); + const [isEditing, setIsEditing] = useState(false); + + useEffect(() => { + if (!isEditing) { + setContent(data.content ?? ""); + } + }, [data.content, isEditing]); + + const saveContent = useDebouncedCallback( + (newContent: string) => { + updateData({ + nodeId: id as Id<"nodes">, + data: { + ...data, + content: newContent, + _status: undefined, + _statusMessage: undefined, + }, + }); + }, + 500, + ); + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const newContent = e.target.value; + setContent(newContent); + saveContent(newContent); + }, + [saveContent], + ); + return ( -
Notiz
-

{data.content || "Leere Notiz"}

+
+ πŸ“Œ Notiz +
+ {isEditing ? ( +