/ 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  });