Skip to content

Commit 48224b9

Browse files
committed
feat: Implement symmetrical parallel edge distribution with detailed path and label data, and add comprehensive unit tests.
1 parent 40d306e commit 48224b9

3 files changed

Lines changed: 349 additions & 188 deletions

File tree

src/NodeCanvas.jsx

Lines changed: 112 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,9 +1854,12 @@ function NodeCanvas() {
18541854
const initializeFromFolder = async () => {
18551855
try {
18561856
console.log('[NodeCanvas] Checking for stored folder on startup...');
1857+
console.log('[NodeCanvas Debug] Session Param:', new URLSearchParams(window.location.search).get('session'));
18571858

18581859
// Validate stored folder
1859-
const { valid, folderHandle } = await folderPersistence.validateStoredFolder();
1860+
const validationResult = await folderPersistence.validateStoredFolder();
1861+
const { valid, folderHandle } = validationResult;
1862+
console.log('[NodeCanvas Debug] Folder Validation Result:', validationResult);
18601863

18611864
if (!valid || !folderHandle) {
18621865
console.log('[NodeCanvas] No valid stored folder found');
@@ -1944,7 +1947,27 @@ function NodeCanvas() {
19441947
!!universeLoadingError
19451948
);
19461949

1950+
// Debug logging for onboarding state
1951+
if (typeof window !== 'undefined') {
1952+
const params = new URLSearchParams(window.location.search);
1953+
console.log('[NodeCanvas Debug] Onboarding State Check:', {
1954+
url: window.location.href,
1955+
search: window.location.search,
1956+
sessionParam: params.get('session'),
1957+
storageKey: getStorageKey('redstring-alpha-welcome-seen'),
1958+
rawValue: localStorage.getItem(getStorageKey('redstring-alpha-welcome-seen')),
1959+
hasCompletedOnboarding,
1960+
suppressForGitFlow,
1961+
isUniverseLoading,
1962+
hasUniverseFile,
1963+
isUniverseLoaded,
1964+
universeLoadingError,
1965+
shouldShowOnboarding
1966+
});
1967+
}
1968+
19471969
if (shouldShowOnboarding && !showOnboardingModal) {
1970+
console.log('[NodeCanvas Debug] Showing onboarding modal!');
19481971
setShowOnboardingModal(true);
19491972
}
19501973
}, [isUniverseLoading, hasUniverseFile, isUniverseLoaded, universeLoadingError, showOnboardingModal]);
@@ -10581,6 +10604,12 @@ function NodeCanvas() {
1058110604
manhattanSourceSide = sSide;
1058210605
manhattanDestSide = dSide;
1058310606
}
10607+
10608+
// Calculate parallel edge path using centralized utility
10609+
const curveInfo = edgeCurveInfo.get(edge.id);
10610+
const parallelPath = calculateParallelEdgePath(startX, startY, endX, endY, curveInfo);
10611+
const useCurve = parallelPath.type === 'curve';
10612+
1058410613
return (
1058510614
<g key={`edge-above-${edge.id}-${idx}`}>
1058610615
{/* Main edge line - always same thickness */}
@@ -10602,54 +10631,32 @@ function NodeCanvas() {
1060210631
}}
1060310632
strokeLinecap="round"
1060410633
/>
10605-
) : (() => {
10606-
// Glow effect also needs curve for multi-edge pairs
10607-
const curveInfo = edgeCurveInfo.get(edge.id);
10608-
if (curveInfo && curveInfo.totalInPair > 1) {
10609-
const { pairIndex, totalInPair } = curveInfo;
10610-
const curveSpacing = 100;
10611-
// Alternating direction logic matching utils
10612-
const direction = pairIndex % 2 === 0 ? 1 : -1;
10613-
const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
10614-
const perpOffset = direction * offsetMagnitude;
10615-
const edgeDx = endX - startX;
10616-
const edgeDy = endY - startY;
10617-
const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
10618-
const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
10619-
const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
10620-
const curveMidX = (startX + endX) / 2;
10621-
const curveMidY = (startY + endY) / 2;
10622-
const ctrlX = curveMidX + perpX * perpOffset;
10623-
const ctrlY = curveMidY + perpY * perpOffset;
10624-
return (
10625-
<path
10626-
d={`M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`}
10627-
fill="none"
10628-
stroke={edgeColor}
10629-
strokeWidth="12"
10630-
opacity={isSelected ? "0.3" : "0.2"}
10631-
style={{
10632-
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
10633-
}}
10634-
strokeLinecap="round"
10635-
/>
10636-
);
10637-
}
10638-
return (
10639-
<line
10640-
x1={startX}
10641-
y1={startY}
10642-
x2={endX}
10643-
y2={endY}
10644-
stroke={edgeColor}
10645-
strokeWidth="12"
10646-
opacity={isSelected ? "0.3" : "0.2"}
10647-
style={{
10648-
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
10649-
}}
10650-
/>
10651-
);
10652-
})()
10634+
) : useCurve ? (
10635+
<path
10636+
d={parallelPath.path}
10637+
fill="none"
10638+
stroke={edgeColor}
10639+
strokeWidth="12"
10640+
opacity={isSelected ? "0.3" : "0.2"}
10641+
style={{
10642+
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
10643+
}}
10644+
strokeLinecap="round"
10645+
/>
10646+
) : (
10647+
<line
10648+
x1={startX}
10649+
y1={startY}
10650+
x2={endX}
10651+
y2={endY}
10652+
stroke={edgeColor}
10653+
strokeWidth="12"
10654+
opacity={isSelected ? "0.3" : "0.2"}
10655+
style={{
10656+
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
10657+
}}
10658+
/>
10659+
)
1065310660
)}
1065410661

1065510662
{(enableAutoRouting && (routingStyle === 'manhattan' || routingStyle === 'clean')) ? (
@@ -10673,54 +10680,26 @@ function NodeCanvas() {
1067310680
strokeLinecap="round"
1067410681
/>
1067510682
</>
10676-
) : (() => {
10677-
// Check if this edge needs curve offset (multiple edges between same nodes)
10678-
const curveInfo = edgeCurveInfo.get(edge.id);
10679-
if (curveInfo && curveInfo.totalInPair > 1) {
10680-
// Calculate curve offset for parallel edges
10681-
const { pairIndex, totalInPair } = curveInfo;
10682-
const curveSpacing = 100; // Exaggerated curves
10683-
const direction = pairIndex % 2 === 0 ? 1 : -1;
10684-
const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
10685-
const perpOffset = direction * offsetMagnitude;
10686-
10687-
// Calculate perpendicular direction
10688-
const edgeDx = endX - startX;
10689-
const edgeDy = endY - startY;
10690-
const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
10691-
const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
10692-
const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
10693-
10694-
// Control point at midpoint, offset perpendicular to edge
10695-
const curveMidX = (startX + endX) / 2;
10696-
const curveMidY = (startY + endY) / 2;
10697-
const ctrlX = curveMidX + perpX * perpOffset;
10698-
const ctrlY = curveMidY + perpY * perpOffset;
10699-
10700-
return (
10701-
<path
10702-
d={`M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`}
10703-
fill="none"
10704-
stroke={edgeColor}
10705-
strokeWidth={showConnectionNames ? "16" : "6"}
10706-
style={{ transition: 'stroke 0.2s ease' }}
10707-
strokeLinecap="round"
10708-
/>
10709-
);
10710-
}
10711-
// Single edge - render as straight line
10712-
return (
10713-
<line
10714-
x1={startX}
10715-
y1={startY}
10716-
x2={endX}
10717-
y2={endY}
10718-
stroke={edgeColor}
10719-
strokeWidth={showConnectionNames ? "16" : "6"}
10720-
style={{ transition: 'stroke 0.2s ease' }}
10721-
/>
10722-
);
10723-
})()}
10683+
) : useCurve ? (
10684+
<path
10685+
d={parallelPath.path}
10686+
fill="none"
10687+
stroke={edgeColor}
10688+
strokeWidth={showConnectionNames ? "16" : "6"}
10689+
style={{ transition: 'stroke 0.2s ease' }}
10690+
strokeLinecap="round"
10691+
/>
10692+
) : (
10693+
<line
10694+
x1={startX}
10695+
y1={startY}
10696+
x2={endX}
10697+
y2={endY}
10698+
stroke={edgeColor}
10699+
strokeWidth={showConnectionNames ? "16" : "6"}
10700+
style={{ transition: 'stroke 0.2s ease' }}
10701+
/>
10702+
)}
1072410703

1072510704
{/* Connection name text - only show when enabled */}
1072610705
{showConnectionNames && (() => {
@@ -10740,31 +10719,10 @@ function NodeCanvas() {
1074010719
angle = 90;
1074110720
}
1074210721
} else {
10743-
// Curve apex logic for labels
10744-
const curveInfo = edgeCurveInfo.get(edge.id);
10745-
if (curveInfo && curveInfo.totalInPair > 1) {
10746-
const { pairIndex } = curveInfo;
10747-
const curveSpacing = 100;
10748-
const direction = pairIndex % 2 === 0 ? 1 : -1;
10749-
const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
10750-
const perpOffset = direction * offsetMagnitude;
10751-
const edgeDx = endX - startX;
10752-
const edgeDy = endY - startY;
10753-
const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
10754-
const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
10755-
const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
10756-
const curveMidX = (startX + endX) / 2;
10757-
const curveMidY = (startY + endY) / 2;
10758-
const ctrlX = curveMidX + perpX * perpOffset;
10759-
const ctrlY = curveMidY + perpY * perpOffset;
10760-
midX = 0.25 * startX + 0.5 * ctrlX + 0.25 * endX;
10761-
midY = 0.25 * startY + 0.5 * ctrlY + 0.25 * endY;
10762-
angle = Math.atan2(edgeDy, edgeDx) * (180 / Math.PI);
10763-
} else {
10764-
midX = (x1 + x2) / 2;
10765-
midY = (y1 + y2) / 2;
10766-
angle = Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
10767-
}
10722+
// Use utility-calculated apex for curves, midpoint for lines
10723+
midX = parallelPath.apexX;
10724+
midY = parallelPath.apexY;
10725+
angle = parallelPath.labelAngle;
1076810726
}
1076910727

1077010728
// Determine connection name to display
@@ -11736,6 +11694,12 @@ function NodeCanvas() {
1173611694
manhattanSourceSide = sSide;
1173711695
manhattanDestSide = dSide;
1173811696
}
11697+
11698+
// Calculate parallel edge path using centralized utility
11699+
const curveInfo = edgeCurveInfo.get(edge.id);
11700+
const parallelPath = calculateParallelEdgePath(startX, startY, endX, endY, curveInfo);
11701+
const useCurve = parallelPath.type === 'curve';
11702+
1173911703
return (
1174011704
<g key={`edge-above-${edge.id}-${idx}`}>
1174111705
{/* Main edge line - always same thickness */}
@@ -11757,54 +11721,32 @@ function NodeCanvas() {
1175711721
}}
1175811722
strokeLinecap="round"
1175911723
/>
11760-
) : (() => {
11761-
// Glow effect also needs curve for multi-edge pairs
11762-
const curveInfo = edgeCurveInfo.get(edge.id);
11763-
if (curveInfo && curveInfo.totalInPair > 1) {
11764-
const { pairIndex, totalInPair } = curveInfo;
11765-
const curveSpacing = 100;
11766-
// Alternating direction logic matching utils
11767-
const direction = pairIndex % 2 === 0 ? 1 : -1;
11768-
const offsetMagnitude = Math.floor((pairIndex + 1) / 2) * curveSpacing;
11769-
const perpOffset = direction * offsetMagnitude;
11770-
const edgeDx = endX - startX;
11771-
const edgeDy = endY - startY;
11772-
const edgeLen = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
11773-
const perpX = edgeLen > 0 ? -edgeDy / edgeLen : 0;
11774-
const perpY = edgeLen > 0 ? edgeDx / edgeLen : 0;
11775-
const curveMidX = (startX + endX) / 2;
11776-
const curveMidY = (startY + endY) / 2;
11777-
const ctrlX = curveMidX + perpX * perpOffset;
11778-
const ctrlY = curveMidY + perpY * perpOffset;
11779-
return (
11780-
<path
11781-
d={`M ${startX} ${startY} Q ${ctrlX} ${ctrlY} ${endX} ${endY}`}
11782-
fill="none"
11783-
stroke={edgeColor}
11784-
strokeWidth="12"
11785-
opacity={isSelected ? "0.3" : "0.2"}
11786-
style={{
11787-
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
11788-
}}
11789-
strokeLinecap="round"
11790-
/>
11791-
);
11792-
}
11793-
return (
11794-
<line
11795-
x1={startX}
11796-
y1={startY}
11797-
x2={endX}
11798-
y2={endY}
11799-
stroke={edgeColor}
11800-
strokeWidth="12"
11801-
opacity={isSelected ? "0.3" : "0.2"}
11802-
style={{
11803-
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
11804-
}}
11805-
/>
11806-
);
11807-
})()
11724+
) : useCurve ? (
11725+
<path
11726+
d={parallelPath.path}
11727+
fill="none"
11728+
stroke={edgeColor}
11729+
strokeWidth="12"
11730+
opacity={isSelected ? "0.3" : "0.2"}
11731+
style={{
11732+
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
11733+
}}
11734+
strokeLinecap="round"
11735+
/>
11736+
) : (
11737+
<line
11738+
x1={startX}
11739+
y1={startY}
11740+
x2={endX}
11741+
y2={endY}
11742+
stroke={edgeColor}
11743+
strokeWidth="12"
11744+
opacity={isSelected ? "0.3" : "0.2"}
11745+
style={{
11746+
filter: `blur(3px) drop-shadow(0 0 8px ${edgeColor})`
11747+
}}
11748+
/>
11749+
)
1180811750
)}
1180911751

1181011752
{(enableAutoRouting && (routingStyle === 'manhattan' || routingStyle === 'clean')) ? (

0 commit comments

Comments
 (0)