/ DreamSongLegacyCode / linearFlowCanvas.js
linearFlowCanvas.js
1 const mainContainer = document.body; // Use the body as the main container 2 mainContainer.style.display = 'flex'; 3 mainContainer.style.flexDirection = 'column'; 4 mainContainer.style.alignItems = 'center'; // Center the content horizontally 5 6 const canvasContainer = document.createElement('div'); 7 canvasContainer.id = 'canvas-container'; 8 mainContainer.appendChild(canvasContainer); 9 10 // Function to fetch the directory listing and get the parent directory 11 async function fetchParentDirectory() { 12 try { 13 const response = await fetch('directory-listing.json'); 14 const directoryListing = await response.json(); 15 return directoryListing.parent_directory; 16 } catch (error) { 17 console.error('Error fetching directory listing:', error); 18 } 19 } 20 21 // Function to fetch the DreamSong.canvas file and parse it 22 async function fetchCanvasData(parentDirectory) { 23 try { 24 const response = await fetch('DreamSong.canvas'); 25 const canvasData = await response.json(); 26 const sortedNodes = sortNodesByEdges(canvasData); 27 renderLinearFlow(sortedNodes, parentDirectory); 28 } catch (error) { 29 console.error('Error fetching canvas data:', error); 30 } 31 } 32 33 // Function to get the relative path of the file, excluding the canvas parent folder 34 function getFilePath(filePath, parentDirectory) { 35 if (filePath.startsWith(parentDirectory + '/')) { 36 return filePath.substring(parentDirectory.length + 1); // Remove the parent folder from the path 37 } 38 return filePath; // Return the original path if no parent folder match 39 } 40 41 // Function to sort nodes based on edges and handle non-directional links 42 function sortNodesByEdges(canvasData) { 43 const nodes = canvasData.nodes; 44 const edges = canvasData.edges; 45 46 // Create a map for quick lookup of nodes by ID 47 const nodeMap = new Map(); 48 nodes.forEach(node => nodeMap.set(node.id, node)); 49 50 // Separate unidirectional and non-directional edges 51 const unidirectionalEdges = edges.filter(edge => !('toEnd' in edge)); 52 const nonDirectionalEdges = edges.filter(edge => 'toEnd' in edge); 53 54 console.log('Unidirectional Edges:', unidirectionalEdges); 55 console.log('Non-Directional Edges:', nonDirectionalEdges); 56 57 // Create an adjacency list from unidirectional edges 58 const adjList = new Map(); 59 unidirectionalEdges.forEach(edge => { 60 if (!adjList.has(edge.fromNode)) { 61 adjList.set(edge.fromNode, []); 62 } 63 adjList.get(edge.fromNode).push(edge.toNode); 64 }); 65 66 // Find nodes that are part of the unidirectional flow 67 const unidirectionalNodes = new Set(); 68 unidirectionalEdges.forEach(edge => { 69 unidirectionalNodes.add(edge.fromNode); 70 unidirectionalNodes.add(edge.toNode); 71 }); 72 73 // Perform a topological sort only on the unidirectional nodes 74 const sortedNodes = []; 75 const visited = new Set(); 76 77 function dfs(nodeId) { 78 if (visited.has(nodeId) || !unidirectionalNodes.has(nodeId)) return; 79 visited.add(nodeId); 80 81 const neighbors = adjList.get(nodeId) || []; 82 neighbors.forEach(neighborId => dfs(neighborId)); 83 84 sortedNodes.unshift(nodeMap.get(nodeId)); // Add node to the sorted list 85 } 86 87 // Find the starting nodes (nodes with no incoming edges) 88 const incomingEdges = new Set(unidirectionalEdges.map(edge => edge.toNode)); 89 nodes.forEach(node => { 90 if (unidirectionalNodes.has(node.id) && !incomingEdges.has(node.id)) { 91 dfs(node.id); 92 } 93 }); 94 95 // Add remaining unidirectional nodes that were not part of any paths 96 unidirectionalNodes.forEach(nodeId => { 97 if (!visited.has(nodeId)) { 98 sortedNodes.push(nodeMap.get(nodeId)); 99 } 100 }); 101 102 console.log('Sorted Unidirectional Nodes:', sortedNodes); 103 104 // Handle non-directional edges to combine nodes in the same container 105 nonDirectionalEdges.forEach(edge => { 106 const node1 = nodeMap.get(edge.fromNode); 107 const node2 = nodeMap.get(edge.toNode); 108 109 if (node1 && node2) { 110 sortedNodes.forEach((node, index) => { 111 if (Array.isArray(node)) { 112 const [mediaNode, textNode] = node; 113 if (mediaNode.id === node1.id || mediaNode.id === node2.id) { 114 sortedNodes[index] = [mediaNode, textNode, node1.id === mediaNode.id ? node2 : node1]; 115 visited.add(node1.id); 116 visited.add(node2.id); 117 } 118 } else if (node.id === node1.id || node.id === node2.id) { 119 const combinedNode = node.id === node1.id ? node2 : node1; 120 sortedNodes[index] = [node, combinedNode]; 121 visited.add(node.id); 122 visited.add(combinedNode.id); 123 } 124 }); 125 } 126 }); 127 128 // Add remaining nodes that were not part of any paths 129 nodes.forEach(node => { 130 if (!visited.has(node.id) && !unidirectionalNodes.has(node.id)) { 131 sortedNodes.push(node); 132 } 133 }); 134 135 console.log('Final Sorted Nodes:', sortedNodes); 136 return sortedNodes; 137 } 138 139 // Function to render the canvas data in a linear top-to-bottom flow with flip-flop pattern for combined elements 140 function renderLinearFlow(nodes, parentDirectory) { 141 canvasContainer.innerHTML = ''; // Clear existing content 142 let flipFlop = true; 143 144 nodes.forEach(node => { 145 if (Array.isArray(node)) { 146 const combinedDiv = document.createElement('div'); 147 combinedDiv.className = 'container'; // Add container class 148 combinedDiv.style.flexDirection = flipFlop ? 'row' : 'row-reverse'; 149 150 node.forEach(subNode => { 151 if (subNode.type === 'file') { 152 const mediaElement = createMediaElement(subNode.file, parentDirectory); 153 combinedDiv.appendChild(mediaElement); 154 } else if (subNode.type === 'text') { 155 const textElement = document.createElement('div'); 156 textElement.className = 'text'; // Add text class 157 textElement.innerHTML = convertMarkdownToHTML(subNode.text); // Convert markdown to HTML 158 combinedDiv.appendChild(textElement); 159 } 160 }); 161 162 canvasContainer.appendChild(combinedDiv); 163 flipFlop = !flipFlop; // Alternate the flip-flop pattern 164 } else { 165 if (node.type === 'file') { 166 const mediaElement = createMediaElement(node.file, parentDirectory); 167 canvasContainer.appendChild(mediaElement); 168 } else if (node.type === 'text') { 169 const textElement = document.createElement('div'); 170 textElement.className = 'text'; // Add text class 171 textElement.innerHTML = convertMarkdownToHTML(node.text); // Convert markdown to HTML 172 const textContainer = document.createElement('div'); 173 textContainer.className = 'text-container'; 174 textContainer.appendChild(textElement); 175 canvasContainer.appendChild(textContainer); 176 } 177 } 178 }); 179 } 180 181 // Function to create media elements (image or video) 182 function createMediaElement(filePath, parentDirectory) { 183 const videoExtensions = ['mp4', 'mov', 'webm', 'ogg']; // List of supported video extensions 184 const fileExtension = filePath.split('.').pop().toLowerCase(); 185 const relativePath = getFilePath(filePath, parentDirectory); 186 187 if (videoExtensions.includes(fileExtension)) { 188 const videoElement = document.createElement('video'); 189 videoElement.className = 'media'; // Add media class 190 videoElement.src = relativePath; 191 videoElement.autoplay = true; 192 videoElement.loop = true; 193 videoElement.muted = true; // Video is muted by default when autoplay 194 videoElement.style.display = 'block'; // Ensure the video is displayed as a block element 195 196 // Event listener to maintain current frame on pause 197 videoElement.addEventListener('pause', () => { 198 videoElement.style.objectFit = 'cover'; // Maintain the current frame 199 }); 200 201 // Event listener to show controls on hover 202 videoElement.addEventListener('mouseover', () => { 203 videoElement.controls = true; 204 }); 205 206 // Event listener to hide controls when not hovering 207 videoElement.addEventListener('mouseout', () => { 208 videoElement.controls = false; 209 }); 210 211 return videoElement; 212 } else { 213 const imgElement = document.createElement('img'); 214 imgElement.className = 'media'; // Add media class 215 imgElement.src = relativePath; 216 return imgElement; 217 } 218 } 219 220 // Function to convert markdown to HTML 221 function convertMarkdownToHTML(markdown) { 222 if (!markdown) return ''; // Return an empty string if markdown is undefined 223 224 return markdown 225 // Convert headers 226 .replace(/^###### (.*$)/gim, '<h6>$1</h6>') 227 .replace(/^##### (.*$)/gim, '<h5>$1</h5>') 228 .replace(/^#### (.*$)/gim, '<h4>$1</h4>') 229 .replace(/^### (.*$)/gim, '<h3>$1</h3>') 230 .replace(/^## (.*$)/gim, '<h2>$1</h2>') 231 .replace(/^# (.*$)/gim, '<h1>$1</h1>') 232 // Convert bold text 233 .replace(/\*\*(.*)\*\*/gim, '<b>$1</b>') 234 // Convert bullet points 235 .replace(/^\* (.*$)/gim, '<ul><li>$1</li></ul>') 236 // Convert new lines to <br> 237 .replace(/\n/g, '<br>') 238 // Merge consecutive <ul> tags 239 .replace(/<\/ul><ul>/gim, ''); 240 } 241 242 // Convert camel case to title case 243 function camelToTitleCase(camelCase) { 244 const result = camelCase.replace(/([A-Z])/g, ' $1'); 245 return result.charAt(0).toUpperCase() + result.slice(1); 246 } 247 248 // Function to initialize the header and title 249 async function initializeHeader() { 250 const parentDirectory = await fetchParentDirectory(); 251 if (parentDirectory) { 252 const title = camelToTitleCase(parentDirectory); 253 document.title = title; 254 255 // Create a new header container 256 const headerContainer = document.createElement('div'); 257 headerContainer.id = 'header-container'; 258 headerContainer.style.textAlign = 'center'; // Center the content 259 headerContainer.style.marginBottom = '200px'; // Add margin to the bottom 260 headerContainer.style.width = '100%'; // Ensure the container takes full width 261 262 // Create and append the header 263 const header = document.createElement('h1'); 264 header.textContent = title; 265 header.style.color = 'white'; // Set the header text color to white 266 header.style.fontSize = '4em'; // Set the header font size 267 headerContainer.appendChild(header); 268 269 // Create and append the image 270 const img = document.createElement('img'); 271 img.src = `${parentDirectory}.png`; 272 img.style.maxWidth = '50%'; // Ensure the image does not exceed the container width 273 img.style.display = 'block'; // Ensure the image is displayed as a block element 274 img.style.margin = '0 auto'; // Center the image 275 headerContainer.appendChild(img); 276 277 // Insert the header container before the canvas container 278 mainContainer.insertBefore(headerContainer, canvasContainer); 279 280 return parentDirectory; 281 } else { 282 console.error('Parent directory not found in directory listing'); 283 } 284 } 285 286 // Initialize the rendering process 287 initializeHeader().then(parentDirectory => { 288 if (parentDirectory) { 289 fetchCanvasData(parentDirectory); 290 } 291 });