refactor: Update KnowledgeGraph and BaseLayout #4
			
				
			
		
		
		
	|  | @ -2,9 +2,6 @@ | |||
| // src/components/KnowledgeGraph.astro | ||||
| // Interactive visualization of content connections using Cytoscape.js | ||||
| 
 | ||||
| // Assuming Cytoscape is loaded via CDN in BaseLayout or globally | ||||
| // If not, you might need: import cytoscape from 'cytoscape'; | ||||
| 
 | ||||
| export interface GraphNode { | ||||
|   id: string; | ||||
|   label: string; | ||||
|  | @ -34,7 +31,7 @@ const { graphData, height = "60vh" } = Astro.props; | |||
| // Generate colors based on categories for nodes | ||||
| const uniqueCategories = [...new Set(graphData.nodes.map(node => node.category || 'Uncategorized'))]; | ||||
| const categoryColors = {}; | ||||
| const predefinedColors = { /* Colors from previous step */ | ||||
| const predefinedColors = {  | ||||
|   'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61', | ||||
|   'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981', | ||||
|   'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1', | ||||
|  | @ -67,8 +64,11 @@ graphData.nodes.forEach(node => { | |||
| }); | ||||
| --- | ||||
| 
 | ||||
| <!-- Include Cytoscape via CDN with is:inline to ensure it loads before the script runs --> | ||||
| <script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script> | ||||
| 
 | ||||
| <div class="graph-wrapper" style={`--graph-height: ${height};`}> | ||||
|   {/* Loading Animation */} | ||||
|   <!-- Loading Animation --> | ||||
|   <div id="graph-loading" class="graph-loading"> | ||||
|     <div class="loading-spinner"> | ||||
|       <div class="spinner-ring"></div> | ||||
|  | @ -78,10 +78,10 @@ graphData.nodes.forEach(node => { | |||
|     <div class="loading-text">Initializing Knowledge Graph...</div> | ||||
|   </div> | ||||
| 
 | ||||
|   {/* Cytoscape Container */} | ||||
|   <!-- Cytoscape Container --> | ||||
|   <div id="knowledge-graph" class="graph-container"></div> | ||||
| 
 | ||||
|   {/* Node Details Panel */} | ||||
|   <!-- Node Details Panel --> | ||||
|   <div id="node-details" class="node-details"> | ||||
|     <div class="node-details-header"> | ||||
|       <h3 id="node-title" class="node-title">Node Title</h3> | ||||
|  | @ -96,19 +96,19 @@ graphData.nodes.forEach(node => { | |||
|     <div id="node-tags" class="node-tags"> | ||||
|       <span class="tags-label">Tags:</span> | ||||
|       <div class="tags-container"> | ||||
|         {/* Tags populated by JS */} | ||||
|         <!-- Tags populated by JS --> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="node-connections" class="node-connections"> | ||||
|       <span class="connections-label">Connections:</span> | ||||
|       <ul class="connections-list"> | ||||
|         {/* Connections populated by JS */} | ||||
|         <!-- Connections populated by JS --> | ||||
|       </ul> | ||||
|     </div> | ||||
|     <a href="#" id="node-link" class="node-link" target="_blank" rel="noopener noreferrer">Read Article</a> | ||||
|   </div> | ||||
| 
 | ||||
|   {/* Graph Controls */} | ||||
|   <!-- Graph Controls --> | ||||
|   <div class="graph-controls"> | ||||
|      <div class="graph-filters"> | ||||
|        <button class="graph-filter active" data-filter="all" style="--filter-color: var(--accent-primary);">All Topics</button> | ||||
|  | @ -129,7 +129,7 @@ graphData.nodes.forEach(node => { | |||
|      </div> | ||||
|   </div> | ||||
| 
 | ||||
|   {/* Graph Legend */} | ||||
|   <!-- Graph Legend --> | ||||
|   <details class="graph-legend"> | ||||
|     <summary class="legend-title">Legend</summary> | ||||
|     <div class="legend-items"> | ||||
|  | @ -141,18 +141,14 @@ graphData.nodes.forEach(node => { | |||
|       ))} | ||||
|     </div> | ||||
|   </details> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| {/* Include Cytoscape via CDN - Ensure this is loaded, perhaps in BaseLayout */} | ||||
| {/* <script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js"></script> */} | ||||
| 
 | ||||
| <script define:vars={{ graphData, categoryColors, nodeSizes }}> | ||||
|   // Initialize the graph when the DOM is ready | ||||
|   function initializeGraph() { | ||||
|     // Check if Cytoscape is loaded | ||||
|     if (typeof cytoscape === 'undefined') { | ||||
|       console.error("Cytoscape library not loaded. Make sure it's included (e.g., via CDN in BaseLayout)."); | ||||
|       console.error("Cytoscape library not loaded. Make sure it's included via the script tag."); | ||||
|       const loadingEl = document.getElementById('graph-loading'); | ||||
|       if(loadingEl) loadingEl.innerHTML = "<p>Error: Cytoscape library not loaded.</p>"; | ||||
|       return;  | ||||
|  | @ -202,7 +198,7 @@ graphData.nodes.forEach(node => { | |||
|     const cy = cytoscape({ | ||||
|       container: graphContainer, | ||||
|       elements: elements, | ||||
|       style: [ /* Styles from your snippet */ | ||||
|       style: [  | ||||
|          { selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'width': 'data(size)', 'height': 'data(size)', 'font-size': '10px', 'color': '#E2E8F0', 'text-valign': 'bottom', 'text-halign': 'center', 'text-margin-y': '7px', 'text-background-opacity': 0.7, 'text-background-color': '#0F1219', 'text-background-padding': '3px', 'text-background-shape': 'roundrectangle', 'text-max-width': '120px', 'text-wrap': 'ellipsis', 'text-overflow-wrap': 'anywhere', 'border-width': '2px', 'border-color': '#0F1219', 'border-opacity': 0.8, 'z-index': 10, 'text-outline-width': 1, 'text-outline-color': '#000', 'text-outline-opacity': 0.5 } }, | ||||
|          { selector: 'edge', style: { 'width': 'mapData(weight, 1, 10, 1, 4)', 'line-color': 'rgba(226, 232, 240, 0.2)', 'curve-style': 'bezier', 'opacity': 0.6, 'z-index': 1 } }, | ||||
|          { selector: '.highlighted', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '3px', 'color': '#FFFFFF', 'text-background-opacity': 0.9, 'opacity': 1, 'z-index': 20 } }, | ||||
|  | @ -227,7 +223,7 @@ graphData.nodes.forEach(node => { | |||
|         clearTimeout(hoverTimeout);  | ||||
|         node.addClass('highlighted'); | ||||
|         node.connectedEdges().addClass('highlighted'); | ||||
|         graphContainer.style.cursor = 'pointer'; // Use graphContainer | ||||
|         graphContainer.style.cursor = 'pointer';  | ||||
|     }); | ||||
| 
 | ||||
|     cy.on('mouseout', 'node', function(e) { | ||||
|  | @ -238,7 +234,7 @@ graphData.nodes.forEach(node => { | |||
|                 node.connectedEdges().removeClass('highlighted'); | ||||
|             } | ||||
|         }, 100); | ||||
|          graphContainer.style.cursor = 'default'; // Use graphContainer | ||||
|         graphContainer.style.cursor = 'default';  | ||||
|     }); | ||||
| 
 | ||||
|     cy.on('tap', 'node', function(e) { | ||||
|  | @ -282,7 +278,7 @@ graphData.nodes.forEach(node => { | |||
|                 evt.preventDefault(); | ||||
|                 cy.$(':selected').unselect();  | ||||
|                 const targetNode = cy.getElementById(connectedData.id); | ||||
|                 if (targetNode) { // Check if node exists | ||||
|                 if (targetNode) { | ||||
|                     targetNode.select();  | ||||
|                     cy.animate({ center: { eles: targetNode }, zoom: cy.zoom() }, { duration: 300 });  | ||||
|                     targetNode.trigger('tap');  | ||||
|  | @ -345,7 +341,7 @@ graphData.nodes.forEach(node => { | |||
|       item.addEventListener('click', () => { | ||||
|         const category = item.dataset.category; | ||||
|         const filterButton = document.querySelector(`.graph-filter[data-filter="${category}"]`); | ||||
|         filterButton?.click();  | ||||
|         if (filterButton) filterButton.click();  | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|  | @ -355,10 +351,11 @@ graphData.nodes.forEach(node => { | |||
|     document.getElementById('reset-graph')?.addEventListener('click', () => { | ||||
|         cy.fit(null, 30);  | ||||
|         cy.elements().removeClass('faded highlighted filtered'); | ||||
|         document.querySelector('.graph-filter[data-filter="all"]')?.click();  | ||||
|         const allFilterButton = document.querySelector('.graph-filter[data-filter="all"]'); | ||||
|         if (allFilterButton) allFilterButton.click();  | ||||
|     }); | ||||
| 
 | ||||
|     // Add mouse wheel zoom controls (already present in original script) | ||||
|     // Add mouse wheel zoom controls | ||||
|     cy.on('zoom', function() { | ||||
|       if (cy.zoom() > 1.5) { | ||||
|         cy.style().selector('node').style({ 'text-max-width': '150px', 'font-size': '12px' }).update(); | ||||
|  | @ -436,4 +433,3 @@ graphData.nodes.forEach(node => { | |||
|     .graph-legend[open] { transform: translateX(0); }  | ||||
|     .graph-controls { bottom: 10px; } | ||||
|   } | ||||
| </style> | ||||
|  | @ -26,7 +26,7 @@ const { | |||
|     <!-- OpenGraph/Social Media Meta Tags --> | ||||
|     <meta property="og:title" content={title} /> | ||||
|     <meta property="og:description" content={description} /> | ||||
|     <meta property="og:image" content={Astro.site ? new URL(image, Astro.site).href : image} /> {/* Use absolute URL */} | ||||
|     <meta property="og:image" content={Astro.site ? new URL(image, Astro.site).href : image} /> <!-- Use absolute URL --> | ||||
|     <meta property="og:url" content={Astro.url} /> | ||||
|     <meta property="og:type" content="website" /> | ||||
|      | ||||
|  | @ -34,7 +34,7 @@ const { | |||
|     <meta name="twitter:card" content="summary_large_image"> | ||||
|     <meta name="twitter:title" content={title}> | ||||
|     <meta name="twitter:description" content={description}> | ||||
|     <meta name="twitter:image" content={Astro.site ? new URL(image, Astro.site).href : image}> {/* Use absolute URL */} | ||||
|     <meta name="twitter:image" content={Astro.site ? new URL(image, Astro.site).href : image}> <!-- Use absolute URL --> | ||||
|      | ||||
|     <!-- Fonts --> | ||||
|     <link rel="preconnect" href="https://fonts.googleapis.com"> | ||||
|  | @ -44,6 +44,9 @@ const { | |||
|     <!-- Favicon --> | ||||
|     <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> | ||||
|      | ||||
|     <!-- Cytoscape Library for Knowledge Graph --> | ||||
|     <script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script> | ||||
|      | ||||
|     <!-- Schema.org markup for Google --> | ||||
|     <script type="application/ld+json"> | ||||
|       { | ||||
|  | @ -295,10 +298,6 @@ const { | |||
|         border-top: 1px solid var(--border-primary); | ||||
|         margin-top: 4rem; /* Add space above footer */ | ||||
|       } | ||||
|        | ||||
|       /* Add other global styles from your external file if needed */ | ||||
|       /* Or remove conflicting styles from the external file */ | ||||
| 
 | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|  | @ -312,11 +311,10 @@ const { | |||
|       <div class="floating-shape shape-3"></div> | ||||
|     </div> | ||||
|      | ||||
|     {/* Use slots for Header and Footer */} | ||||
|     <slot name="header" /> | ||||
|      | ||||
|     <main> | ||||
|       <slot /> {/* Default slot for page content */} | ||||
|       <slot /> <!-- Default slot for page content --> | ||||
|     </main> | ||||
|      | ||||
|     <slot name="footer" /> | ||||
|  | @ -345,10 +343,6 @@ const { | |||
|         } else { | ||||
|             console.warn("Element with class 'neural-nodes' not found."); | ||||
|         } | ||||
|          | ||||
|         // Terminal typing effect (if needed globally, otherwise keep in component) | ||||
|         // const typingElements = document.querySelectorAll('.terminal-typing'); | ||||
|         // typingElements.forEach(typingElement => { ... }); | ||||
|       }); | ||||
|     </script> | ||||
|   </body> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue