From 4b4d784768a93f4df1f73bb2881ec488e8194540 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 28 Mar 2026 11:02:19 +0100 Subject: [PATCH] feat: enhance canvas styling and edge handling with glow effects - Added custom CSS variables for edge stroke and connection line styles in light and dark modes to improve visual consistency. - Updated edge rendering logic to utilize new styles, enhancing the appearance of temporary edges during interactions. - Introduced a new utility function for applying glow effects based on source node types, improving the visual feedback of connections. - Refactored edge mapping to support glow effects, enhancing user experience during node interactions. --- app/globals.css | 28 +++++++++++++++++++-- components/canvas/canvas.tsx | 21 +++++++++++++--- lib/canvas-utils.ts | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/app/globals.css b/app/globals.css index 5a7f3ed..573fb0e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -145,6 +145,23 @@ } @layer components { + /* Kanten: doppelte Standard-Dicke, feste Kontrastfarben je Light/Dark (gleiches Schema wie colorMode) */ + .react-flow { + --xy-edge-stroke: rgba(44, 62, 80, 1); + --xy-edge-stroke-width: 2; + --xy-edge-stroke-selected: rgba(44, 62, 80, 1); + --xy-connectionline-stroke: rgba(44, 62, 80, 1); + --xy-connectionline-stroke-width: 1; + } + + .react-flow.dark { + --xy-edge-stroke: rgba(189, 195, 199, 1); + --xy-edge-stroke-width: 2; + --xy-edge-stroke-selected: rgba(189, 195, 199, 1); + --xy-connectionline-stroke: rgba(189, 195, 199, 1); + --xy-connectionline-stroke-width: 1; + } + /* Verbindungs-Punkte über Node-Inhalt (XYFlow setzt kein z-index) */ .react-flow__handle { z-index: 50; @@ -156,9 +173,16 @@ } .react-flow__edge.temp .react-flow__edge-path { - stroke: hsl(var(--foreground) / 0.3); - stroke-width: 2.5; + stroke-width: 5; stroke-dasharray: 8 6; stroke-linecap: round; } + + .react-flow:not(.dark) .react-flow__edge.temp .react-flow__edge-path { + stroke: rgba(44, 62, 80, 0.35); + } + + .react-flow.dark .react-flow__edge.temp .react-flow__edge-path { + stroke: rgba(189, 195, 199, 0.35); + } } diff --git a/components/canvas/canvas.tsx b/components/canvas/canvas.tsx index 36833e8..dff4847 100644 --- a/components/canvas/canvas.tsx +++ b/components/canvas/canvas.tsx @@ -34,6 +34,7 @@ import { convexNodeDocWithMergedStorageUrl, convexNodeToRF, convexEdgeToRF, + convexEdgeToRFWithSourceGlow, NODE_DEFAULTS, NODE_HANDLE_MAP, resolveMediaAspectRatio, @@ -133,7 +134,7 @@ const DEFAULT_EDGE_OPTIONS: DefaultEdgeOptions = { }; const EDGE_INTERSECTION_HIGHLIGHT_STYLE: NonNullable = { - stroke: "hsl(var(--foreground))", + stroke: "var(--xy-edge-stroke)", strokeWidth: 2, }; @@ -622,10 +623,23 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { if (!convexEdges) return; setEdges((prev) => { const tempEdges = prev.filter((e) => e.className === "temp"); - const mapped = convexEdges.map(convexEdgeToRF); + const sourceTypeByNodeId = + convexNodes !== undefined + ? new Map(convexNodes.map((n) => [n._id, n.type])) + : undefined; + const glowMode = resolvedTheme === "dark" ? "dark" : "light"; + const mapped = convexEdges.map((edge) => + sourceTypeByNodeId + ? convexEdgeToRFWithSourceGlow( + edge, + sourceTypeByNodeId.get(edge.sourceNodeId), + glowMode, + ) + : convexEdgeToRF(edge), + ); return [...mapped, ...tempEdges]; }); - }, [convexEdges]); + }, [convexEdges, convexNodes, resolvedTheme]); useEffect(() => { if (isDragging.current) return; @@ -1278,6 +1292,7 @@ function CanvasInner({ canvasId }: CanvasInnerProps) { ): RFEdge { }; } +/** + * Akzentfarben der Handles je Node-Typ (s. jeweilige Node-Komponente). + * Für einen dezenten Glow entlang der Kante (drop-shadow am Pfad). + */ +const SOURCE_NODE_GLOW_RGB: Record = { + prompt: [139, 92, 246], + "ai-image": [139, 92, 246], + image: [13, 148, 136], + text: [13, 148, 136], + note: [13, 148, 136], + asset: [13, 148, 136], + group: [100, 116, 139], + frame: [249, 115, 22], + compare: [100, 116, 139], +}; + +export type EdgeGlowColorMode = "light" | "dark"; + +function sourceGlowFilterForNodeType( + type: string | undefined, + colorMode: EdgeGlowColorMode, +): string | undefined { + if (!type) return undefined; + const rgb = SOURCE_NODE_GLOW_RGB[type]; + if (!rgb) return undefined; + const [r, g, b] = rgb; + if (colorMode === "dark") { + /* Zwei kleine Schatten statt gestapelter großer Blur — weniger GPU-Last beim Pan/Zoom */ + return `drop-shadow(0 0 4px rgba(${r},${g},${b},0.72)) drop-shadow(0 0 9px rgba(${r},${g},${b},0.38))`; + } + return `drop-shadow(0 0 3px rgba(${r},${g},${b},0.42)) drop-shadow(0 0 7px rgba(${r},${g},${b},0.2))`; +} + +/** Wie convexEdgeToRF, setzt zusätzlich filter am Pfad nach Quell-Node-Typ. */ +export function convexEdgeToRFWithSourceGlow( + edge: Doc<"edges">, + sourceNodeType: string | undefined, + colorMode: EdgeGlowColorMode = "light", +): RFEdge { + const base = convexEdgeToRF(edge); + const filter = sourceGlowFilterForNodeType(sourceNodeType, colorMode); + if (!filter) return base; + return { + ...base, + style: { ...(base.style ?? {}), filter }, + }; +} + /** * Handle-IDs pro Node-Typ für Proximity Connect. * `undefined` = default handle (kein explizites `id`-Attribut auf dem Handle).