feat: enhance canvas components with improved sidebar and toolbar functionality

- Updated CanvasSidebar to accept canvasId as a prop, enabling dynamic content based on the current canvas.
- Refactored CanvasToolbar to implement a dropdown menu for adding nodes, improving usability and organization.
- Introduced new node types and updated existing ones in the node template picker for better categorization and searchability.
- Enhanced AssetNode to utilize context for asset browser interactions, streamlining asset management on the canvas.
- Improved overall layout and styling for better user experience across canvas components.
This commit is contained in:
Matthias
2026-03-28 22:35:44 +01:00
parent e41d3c03b0
commit 4e55460792
14 changed files with 1104 additions and 115 deletions

View File

@@ -1,6 +1,14 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { createPortal } from "react-dom";
import { useAction, useMutation } from "convex/react";
import { X, Search, Loader2, AlertCircle } from "lucide-react";
@@ -35,6 +43,25 @@ export interface AssetBrowserSessionState {
totalPages: number;
}
/** Canvas-weit: bleibt beim Wechsel optimistic_… → echte Node-ID erhalten (sonst remount = Panel zu). */
export type AssetBrowserTargetApi = {
targetNodeId: string | null;
openForNode: (nodeId: string) => void;
close: () => void;
};
export const AssetBrowserTargetContext = createContext<AssetBrowserTargetApi | null>(
null,
);
export function useAssetBrowserTarget(): AssetBrowserTargetApi {
const v = useContext(AssetBrowserTargetContext);
if (!v) {
throw new Error("useAssetBrowserTarget must be used within AssetBrowserTargetContext.Provider");
}
return v;
}
interface Props {
nodeId: string;
canvasId: string;
@@ -50,7 +77,6 @@ export function AssetBrowserPanel({
initialState,
onStateChange,
}: Props) {
const [isMounted, setIsMounted] = useState(false);
const [term, setTerm] = useState(initialState?.term ?? "");
const [debouncedTerm, setDebouncedTerm] = useState(initialState?.term ?? "");
const [assetType, setAssetType] = useState<AssetType>(initialState?.assetType ?? "photo");
@@ -69,11 +95,6 @@ export function AssetBrowserPanel({
const scrollAreaRef = useRef<HTMLDivElement | null>(null);
const isSelecting = selectingAssetKey !== null;
useEffect(() => {
setIsMounted(true);
return () => setIsMounted(false);
}, []);
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedTerm(term);
@@ -429,9 +450,5 @@ export function AssetBrowserPanel({
],
);
if (!isMounted) {
return null;
}
return createPortal(modal, document.body);
}