@@ -3,8 +3,13 @@ import { useCallback } from 'react';
33/**
44 * Audio stream port - RCA jack style with green color
55 * Visual: Circle with center dot (◉) with metallic 3D effect
6+ *
7+ * Nodes can provide custom port rendering via renderStreamPort function.
8+ * If customRender is provided, it receives { index, isOutput, x, y } and should return SVG elements.
9+ * The custom render is placed inside the port group, so coordinates are relative to port position.
610 */
7- export function AudioPort ( { x, y, isOutput, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave } ) {
11+ export function AudioPort ( { x, y, isOutput, index, customRender, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave } ) {
12+ const hasCustomRender = ! ! customRender ;
813 // Touch handlers that simulate mouse events
914 const handleTouchStart = useCallback ( ( e ) => {
1015 if ( e . touches . length !== 1 ) return ;
@@ -21,19 +26,21 @@ export function AudioPort({ x, y, isOutput, onMouseDown, onMouseUp, onMouseEnter
2126 onMouseUp ?. ( fakeEvent ) ;
2227 } , [ onMouseUp ] ) ;
2328
24- // Offset to center the port visually - symmetric distance from node body
25- // Output: start at node edge (x), extend outward. Input: start at x-12, extend into node
26- const offsetX = isOutput ? x : x - 12 ;
27- const gradientId = `audio-port-grad-${ isOutput ? 'out' : 'in' } -${ x } -${ y } ` ;
29+ // Offset to center the port visually - overlap node body slightly for visual cohesion
30+ // Port is 16px wide, center at 8px. We want ~4px overlap with node edge on both sides.
31+ // Output (right side): x is node right edge, port center should be at x + 4
32+ // Input (left side): x is 0 (node left edge), port center should be at x - 4
33+ const offsetX = isOutput ? x - 4 : x - 12 ;
34+ const gradientId = `audio-port-grad-${ isOutput ? 'out' : 'in' } -${ x } -${ y } -${ hasCustomRender ? 'custom' : 'std' } ` ;
2835
2936 return (
3037 < g transform = { `translate(${ offsetX } , ${ y - 8 } )` } >
3138 < defs >
32- { /* Metallic green gradient for outer ring */ }
39+ { /* Metallic green gradient for outer ring - lighter when custom render for better contrast */ }
3340 < radialGradient id = { gradientId } cx = "30%" cy = "30%" r = "70%" >
34- < stop offset = "0%" stopColor = "# 7dda7d" />
35- < stop offset = "50%" stopColor = "# 3daa3d" />
36- < stop offset = "100%" stopColor = "# 1d7a1d" />
41+ < stop offset = "0%" stopColor = { hasCustomRender ? "#e8f8e8" : "# 7dda7d"} />
42+ < stop offset = "50%" stopColor = { hasCustomRender ? "#b8e8b8" : "# 3daa3d"} />
43+ < stop offset = "100%" stopColor = { hasCustomRender ? "#78c878" : "# 1d7a1d"} />
3744 </ radialGradient >
3845 </ defs >
3946 { /* Larger invisible touch target */ }
@@ -61,7 +68,7 @@ export function AudioPort({ x, y, isOutput, onMouseDown, onMouseUp, onMouseEnter
6168 cy = { 8 }
6269 r = { 7 }
6370 fill = { `url(#${ gradientId } )` }
64- stroke = "# 1a6a1a"
71+ stroke = { hasCustomRender ? "#4a9a4a" : "# 1a6a1a"}
6572 strokeWidth = { 1 }
6673 onMouseDown = { onMouseDown }
6774 onMouseUp = { onMouseUp }
@@ -71,23 +78,27 @@ export function AudioPort({ x, y, isOutput, onMouseDown, onMouseUp, onMouseEnter
7178 onTouchEnd = { handleTouchEnd }
7279 style = { { cursor : 'crosshair' } }
7380 />
74- { /* Inner ring / hole */ }
75- < circle
76- cx = { 8 }
77- cy = { 8 }
78- r = { 4 }
79- fill = "#0a3a0a"
80- pointerEvents = "none"
81- />
82- { /* Center pin - metallic */ }
83- < circle
84- className = "audio-port-inner"
85- cx = { 8 }
86- cy = { 8 }
87- r = { 2 }
88- fill = "#c0c0c0"
89- pointerEvents = "none"
90- />
81+ { /* Inner ring / hole - skip if custom render provides its own content */ }
82+ { ! hasCustomRender && (
83+ < circle
84+ cx = { 8 }
85+ cy = { 8 }
86+ r = { 4 }
87+ fill = "#0a3a0a"
88+ pointerEvents = "none"
89+ />
90+ ) }
91+ { /* Center pin - metallic - skip if custom render */ }
92+ { ! hasCustomRender && (
93+ < circle
94+ className = "audio-port-inner"
95+ cx = { 8 }
96+ cy = { 8 }
97+ r = { 2 }
98+ fill = "#c0c0c0"
99+ pointerEvents = "none"
100+ />
101+ ) }
91102 { /* Highlight */ }
92103 < circle
93104 cx = { 5.5 }
@@ -96,6 +107,8 @@ export function AudioPort({ x, y, isOutput, onMouseDown, onMouseUp, onMouseEnter
96107 fill = "rgba(255,255,255,0.4)"
97108 pointerEvents = "none"
98109 />
110+ { /* Custom rendering from node definition (icons, labels, etc.) */ }
111+ { customRender && customRender ( { index, isOutput, x : 8 , y : 8 } ) }
99112 </ g >
100113 ) ;
101114}
0 commit comments