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.
This commit is contained in:
Matthias
2026-03-25 17:58:58 +01:00
parent d1834c5694
commit ca40f5cb13
27 changed files with 1363 additions and 207 deletions

View File

@@ -1,28 +1,72 @@
"use client";
import { type Node, type NodeProps } from "@xyflow/react";
import { useState, useCallback } 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 BaseNodeWrapper from "./base-node-wrapper";
export type FrameNodeData = {
type FrameNodeData = {
label?: string;
exportWidth?: number;
exportHeight?: number;
resolution?: string;
_status?: string;
_statusMessage?: string;
};
export type FrameNode = Node<FrameNodeData, "frame">;
export default function FrameNode({ data, selected }: NodeProps<FrameNode>) {
const resolution =
data.exportWidth && data.exportHeight
? `${data.exportWidth}x${data.exportHeight}`
: undefined;
export default function FrameNode({ id, data, selected }: NodeProps<FrameNode>) {
const updateData = useMutation(api.nodes.updateData);
const [editingLabel, setEditingLabel] = useState<string | null>(null);
const displayLabel = data.label ?? "Frame";
const isEditing = editingLabel !== null;
const handleDoubleClick = useCallback(() => {
setEditingLabel(displayLabel);
}, [displayLabel]);
const handleBlur = useCallback(() => {
if (editingLabel !== null && editingLabel !== data.label) {
updateData({
nodeId: id as Id<"nodes">,
data: {
...data,
label: editingLabel,
_status: undefined,
_statusMessage: undefined,
},
});
}
setEditingLabel(null);
}, [editingLabel, data, id, updateData]);
return (
<BaseNodeWrapper selected={selected} className="min-h-[200px] min-w-[300px] border-blue-500/30 p-3">
<div className="text-xs font-medium text-blue-500">
{data.label || "Frame"} {resolution ? `(${resolution})` : ""}
</div>
<BaseNodeWrapper
selected={selected}
className="min-w-[300px] min-h-[200px] p-3 border-blue-500/30"
>
{isEditing ? (
<input
value={editingLabel}
onChange={(e) => setEditingLabel(e.target.value)}
onBlur={handleBlur}
onKeyDown={(e) => e.key === "Enter" && handleBlur()}
autoFocus
className="nodrag text-xs font-medium text-blue-500 bg-transparent border-0 outline-none w-full"
/>
) : (
<div
onDoubleClick={handleDoubleClick}
className="text-xs font-medium text-blue-500 cursor-text"
>
🖥 {displayLabel}{" "}
{data.resolution && (
<span className="text-muted-foreground">({data.resolution})</span>
)}
</div>
)}
</BaseNodeWrapper>
);
}