- {categories.map((category) => (
-
-
- {category}
-
-
- {nodeTemplates
- .filter((template) => template.category === category)
- .map((template) => (
-
+ {NODE_CATEGORIES_ORDERED.map((categoryId) => {
+ const entries = byCategory.get(categoryId) ?? [];
+ if (entries.length === 0) return null;
+ const { label } = NODE_CATEGORY_META[categoryId];
+ return (
+
+
+ {label}
+
+
+ {entries.map((entry) => (
+
))}
+
-
- ))}
+ );
+ })}
+
+
);
}
diff --git a/components/canvas/canvas-toolbar.tsx b/components/canvas/canvas-toolbar.tsx
index f8b5626..0f08858 100644
--- a/components/canvas/canvas-toolbar.tsx
+++ b/components/canvas/canvas-toolbar.tsx
@@ -1,68 +1,187 @@
"use client";
import { useRef } from "react";
+import {
+ Hand,
+ MessageSquare,
+ MousePointer2,
+ Plus,
+ Redo2,
+ Scissors,
+ Undo2,
+} from "lucide-react";
import { CreditDisplay } from "@/components/canvas/credit-display";
import { ExportButton } from "@/components/canvas/export-button";
import { useCanvasPlacement } from "@/components/canvas/canvas-placement-context";
import { useCenteredFlowNodePosition } from "@/hooks/use-centered-flow-node-position";
+import { Button } from "@/components/ui/button";
import {
- CANVAS_NODE_TEMPLATES,
- type CanvasNodeTemplate,
-} from "@/lib/canvas-node-templates";
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import {
+ NODE_CATEGORY_META,
+ NODE_CATEGORIES_ORDERED,
+ catalogEntriesByCategory,
+ getTemplateForCatalogType,
+ isNodePaletteEnabled,
+ type NodeCategoryId,
+} from "@/lib/canvas-node-catalog";
+import type { CanvasNodeTemplate } from "@/lib/canvas-node-templates";
+
+export type CanvasNavTool = "select" | "hand" | "scissor" | "comment";
interface CanvasToolbarProps {
canvasName?: string;
+ activeTool: CanvasNavTool;
+ onToolChange: (tool: CanvasNavTool) => void;
}
export default function CanvasToolbar({
canvasName,
+ activeTool,
+ onToolChange,
}: CanvasToolbarProps) {
const { createNodeWithIntersection } = useCanvasPlacement();
const getCenteredPosition = useCenteredFlowNodePosition();
const nodeCountRef = useRef(0);
- const handleAddNode = async (
- type: CanvasNodeTemplate["type"],
- data: CanvasNodeTemplate["defaultData"],
- width: number,
- height: number,
- ) => {
+ const handleAddNode = async (template: CanvasNodeTemplate) => {
const stagger = (nodeCountRef.current % 8) * 24;
nodeCountRef.current += 1;
await 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(),
});
};
+ const byCategory = catalogEntriesByCategory();
+
+ const toolBtn = (tool: CanvasNavTool, icon: React.ReactNode, label: string) => (
+
+ );
+
return (
-
- {CANVAS_NODE_TEMPLATES.map((template) => (
-