From 30ee21f087e9f1622fe3402a85ed4c394acbeeca Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Mon, 15 Sep 2025 18:06:11 +0200 Subject: [PATCH] revert graph.js refactor --- static/js/graph.js | 169 +++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/static/js/graph.js b/static/js/graph.js index d3b1d19..d775a00 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -1290,114 +1290,115 @@ class GraphManager { } /** - * Enhanced hide operation using true root detection - * @param {string} nodeId - The ID of the node to start hiding from + * Hide a node and recursively hide any neighbors that become disconnected. + * @param {string} nodeId - The ID of the node to start hiding from. */ hideNodeAndOrphans(nodeId) { const historyData = { nodeIds: [] }; - - // Step 1: Hide the target node - this.nodes.update({ id: nodeId, hidden: true }); - historyData.nodeIds.push(nodeId); - - // Step 2: Recompute what should remain visible using true root detection - const remainingVisible = this.computeReachableFromRoots(); - - // Step 3: Hide any nodes that are no longer reachable - const allNodes = this.nodes.get(); - allNodes.forEach(node => { - if (!node.hidden && !remainingVisible.has(node.id) && node.id !== nodeId) { - this.nodes.update({ id: node.id, hidden: true }); - historyData.nodeIds.push(node.id); + const queue = [nodeId]; + const visited = new Set([nodeId]); + + while (queue.length > 0) { + const currentId = queue.shift(); + const node = this.nodes.get(currentId); + if (!node || node.hidden) continue; + + // 1. Hide the current node and add to history + this.nodes.update({ id: currentId, hidden: true }); + historyData.nodeIds.push(currentId); + + // 2. Check its neighbors + const neighbors = this.network.getConnectedNodes(currentId); + for (const neighborId of neighbors) { + if (visited.has(neighborId)) continue; + + const connectedEdges = this.network.getConnectedEdges(neighborId); + let hasVisibleEdge = false; + // 3. See if the neighbor still has any visible connections + for (const edgeId of connectedEdges) { + const edge = this.edges.get(edgeId); + const sourceNode = this.nodes.get(edge.from); + const targetNode = this.nodes.get(edge.to); + if ((sourceNode && !sourceNode.hidden) && (targetNode && !targetNode.hidden)) { + hasVisibleEdge = true; + break; + } + } + + // 4. If no visible connections, add to queue to be hidden + if (!hasVisibleEdge) { + queue.push(neighborId); + visited.add(neighborId); + } } - }); + } if (historyData.nodeIds.length > 0) { this.addToHistory('hide', historyData); - console.log(`Hidden ${historyData.nodeIds.length} nodes using true root detection`); } } /** - * Enhanced delete operation using true root detection - * @param {string} nodeId - The ID of the node to start deleting from + * Delete a node and recursively delete any neighbors that become disconnected. + * @param {string} nodeId - The ID of the node to start deleting from. */ async deleteNodeAndOrphans(nodeId) { + const deletionQueue = [nodeId]; + const processedForDeletion = new Set([nodeId]); const historyData = { nodes: [], edges: [] }; let operationFailed = false; - - // Step 1: Store current state and delete target node - const targetNode = this.nodes.get(nodeId); - const connectedEdgeIds = this.network.getConnectedEdges(nodeId); - const connectedEdges = this.edges.get(connectedEdgeIds); - - if (targetNode) { - historyData.nodes.push(targetNode); - historyData.edges.push(...connectedEdges); - } - - try { - // Delete from backend first - const response = await fetch(`/api/graph/node/${nodeId}`, { method: 'DELETE' }); - const result = await response.json(); + + while (deletionQueue.length > 0) { + const currentId = deletionQueue.shift(); + const node = this.nodes.get(currentId); + if (!node) continue; + + const neighbors = this.network.getConnectedNodes(currentId); + const connectedEdgeIds = this.network.getConnectedEdges(currentId); + const edges = this.edges.get(connectedEdgeIds); - if (!result.success) { - throw new Error(result.error || 'Failed to delete node from backend'); - } - - // Remove from frontend - this.nodes.remove({ id: nodeId }); - console.log(`Node ${nodeId} deleted from backend and frontend`); - - // Step 2: Identify orphaned nodes using true root detection - const reachableNodes = this.computeReachableFromRoots(); - const allRemainingNodes = this.nodes.get().filter(node => node.id !== nodeId); - - // Step 3: Delete orphaned nodes - const orphanedNodes = allRemainingNodes.filter(node => !reachableNodes.has(node.id)); - - for (const orphanNode of orphanedNodes) { - try { - const orphanEdgeIds = this.network.getConnectedEdges(orphanNode.id); - const orphanEdges = this.edges.get(orphanEdgeIds); - - // Store for history - historyData.nodes.push(orphanNode); - historyData.edges.push(...orphanEdges); - - // Delete from backend - const orphanResponse = await fetch(`/api/graph/node/${orphanNode.id}`, { method: 'DELETE' }); - const orphanResult = await orphanResponse.json(); - - if (!orphanResult.success) { - console.warn(`Failed to delete orphaned node ${orphanNode.id}:`, orphanResult.error); - // Continue with other orphans rather than failing entirely - } else { - // Remove from frontend - this.nodes.remove({ id: orphanNode.id }); - console.log(`Orphaned node ${orphanNode.id} deleted`); - } - } catch (error) { - console.error(`Error deleting orphaned node ${orphanNode.id}:`, error); - // Continue with other orphans + // Store state for potential revert + historyData.nodes.push(node); + historyData.edges.push(...edges); + + try { + const response = await fetch(`/api/graph/node/${currentId}`, { method: 'DELETE' }); + const result = await response.json(); + + if (!result.success) { + console.error(`Failed to delete node ${currentId} from backend:`, result.error); + operationFailed = true; + break; } + + console.log(`Node ${currentId} deleted from backend.`); + this.nodes.remove({ id: currentId }); // Remove from view + + // Check if former neighbors are now orphans + neighbors.forEach(neighborId => { + if (!processedForDeletion.has(neighborId) && this.nodes.get(neighborId)) { + if (this.network.getConnectedEdges(neighborId).length === 0) { + deletionQueue.push(neighborId); + processedForDeletion.add(neighborId); + } + } + }); + + } catch (error) { + console.error('Error during node deletion API call:', error); + operationFailed = true; + break; } - - console.log(`Deleted ${orphanedNodes.length + 1} nodes using true root detection`); - - } catch (error) { - console.error('Error during node deletion:', error); - operationFailed = true; } - - // Add to history only if primary deletion succeeded + + // Add to history only if the entire operation was successful if (!operationFailed && historyData.nodes.length > 0) { // Ensure edges in history are unique historyData.edges = Array.from(new Map(historyData.edges.map(e => [e.id, e])).values()); this.addToHistory('delete', historyData); } else if (operationFailed) { - console.log("Reverting UI changes due to failed delete operation"); - // Restore the UI to original state + console.log("Reverting UI changes due to failed delete operation."); + // If any part of the chain failed, restore the UI to its original state this.nodes.add(historyData.nodes); this.edges.add(historyData.edges); }