tuanchris 5 hónapja
szülő
commit
941ab25658
4 módosított fájl, 189 hozzáadás és 7 törlés
  1. 175 7
      static/js/index.js
  2. 12 0
      static/js/playlists.js
  3. 1 0
      templates/index.html
  4. 1 0
      templates/playlists.html

+ 175 - 7
static/js/index.js

@@ -735,30 +735,43 @@ function displayPatternBatch() {
 // Create a pattern card element
 // Create a pattern card element
 function createPatternCard(pattern) {
 function createPatternCard(pattern) {
     const card = document.createElement('div');
     const card = document.createElement('div');
-    card.className = 'pattern-card flex flex-col items-center gap-3 bg-gray-50';
+    card.className = 'pattern-card group relative flex flex-col items-center gap-3 bg-gray-50';
     card.dataset.pattern = pattern;
     card.dataset.pattern = pattern;
     
     
     // Create preview container with proper styling for loading indicator
     // Create preview container with proper styling for loading indicator
     const previewContainer = document.createElement('div');
     const previewContainer = document.createElement('div');
-    previewContainer.className = 'w-32 h-32 rounded-full shadow-md relative pattern-preview group';
+    previewContainer.className = 'w-32 h-32 rounded-full shadow-md relative pattern-preview';
     previewContainer.dataset.pattern = pattern;
     previewContainer.dataset.pattern = pattern;
     
     
     // Add loading indicator
     // Add loading indicator
     previewContainer.innerHTML = '<div class="absolute inset-0 flex items-center justify-center"><div class="bg-slate-200 rounded-full h-8 w-8 flex items-center justify-center"><div class="bg-slate-500 rounded-full h-4 w-4"></div></div></div>';
     previewContainer.innerHTML = '<div class="absolute inset-0 flex items-center justify-center"><div class="bg-slate-200 rounded-full h-8 w-8 flex items-center justify-center"><div class="bg-slate-500 rounded-full h-4 w-4"></div></div></div>';
     
     
-    // Add play button overlay (hidden by default, shown on hover)
+    // Add play button overlay (centered, hidden by default, shown on hover)
     const playOverlay = document.createElement('div');
     const playOverlay = document.createElement('div');
     playOverlay.className = 'absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer';
     playOverlay.className = 'absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-pointer';
     playOverlay.innerHTML = '<div class="bg-white rounded-full p-2 shadow-lg flex items-center justify-center w-10 h-10"><span class="material-icons text-lg text-gray-800">play_arrow</span></div>';
     playOverlay.innerHTML = '<div class="bg-white rounded-full p-2 shadow-lg flex items-center justify-center w-10 h-10"><span class="material-icons text-lg text-gray-800">play_arrow</span></div>';
-    
-    // Add click handler for play button (separate from card click)
+    playOverlay.title = 'Preview pattern';
     playOverlay.addEventListener('click', (e) => {
     playOverlay.addEventListener('click', (e) => {
         e.stopPropagation(); // Prevent card selection
         e.stopPropagation(); // Prevent card selection
         openAnimatedPreview(pattern);
         openAnimatedPreview(pattern);
     });
     });
-    
     previewContainer.appendChild(playOverlay);
     previewContainer.appendChild(playOverlay);
     
     
+    // Add heart favorite button (top-right corner)
+    const heartButton = document.createElement('div');
+    const isAlreadyFavorite = favoritePatterns.has(pattern);
+    const heartOpacity = isAlreadyFavorite ? 'opacity-100' : 'opacity-0 group-hover:opacity-100';
+    heartButton.className = `absolute top-2 right-2 w-7 h-7 cursor-pointer ${heartOpacity} transition-opacity duration-200 z-10 bg-white/90 rounded-full shadow-sm flex items-center justify-center`;
+    const heartIcon = isAlreadyFavorite ? 'favorite' : 'favorite_border';
+    const heartColor = isAlreadyFavorite ? 'text-red-500 hover:text-red-600' : 'text-gray-400 hover:text-red-500';
+    heartButton.innerHTML = `<span class="material-icons text-lg ${heartColor} transition-colors" id="heart-${pattern.replace(/[^a-zA-Z0-9]/g, '_')}">${heartIcon}</span>`;
+    heartButton.title = isAlreadyFavorite ? 'Remove from favorites' : 'Add to favorites';
+    heartButton.addEventListener('click', (e) => {
+        e.stopPropagation(); // Prevent card selection
+        toggleFavorite(pattern);
+    });
+    // Note: Heart button will be added to card, not previewContainer to avoid circular clipping
+    
     // Create pattern name
     // Create pattern name
     const patternName = document.createElement('p');
     const patternName = document.createElement('p');
     patternName.className = 'text-gray-700 text-sm font-medium text-center truncate w-full';
     patternName.className = 'text-gray-700 text-sm font-medium text-center truncate w-full';
@@ -779,6 +792,9 @@ function createPatternCard(pattern) {
     card.appendChild(previewContainer);
     card.appendChild(previewContainer);
     card.appendChild(patternName);
     card.appendChild(patternName);
     
     
+    // Add heart button to card (not previewContainer) to avoid circular clipping
+    card.appendChild(heartButton);
+    
     return card;
     return card;
 }
 }
 
 
@@ -1143,6 +1159,18 @@ function sortPatterns(patterns, sortField, sortDirection) {
                 aVal = a.coordinates_count;
                 aVal = a.coordinates_count;
                 bVal = b.coordinates_count;
                 bVal = b.coordinates_count;
                 break;
                 break;
+            case 'favorite':
+                // Sort by favorite status first, then by name as secondary sort
+                const aIsFavorite = favoritePatterns.has(a.path);
+                const bIsFavorite = favoritePatterns.has(b.path);
+                
+                if (aIsFavorite && !bIsFavorite) return sortDirection === 'asc' ? -1 : 1;
+                if (!aIsFavorite && bIsFavorite) return sortDirection === 'asc' ? 1 : -1;
+                
+                // Both have same favorite status, sort by name as secondary sort
+                aVal = a.name.toLowerCase();
+                bVal = b.name.toLowerCase();
+                break;
             default:
             default:
                 aVal = a.name.toLowerCase();
                 aVal = a.name.toLowerCase();
                 bVal = b.name.toLowerCase();
                 bVal = b.name.toLowerCase();
@@ -1452,7 +1480,8 @@ document.addEventListener('DOMContentLoaded', async () => {
             });
             });
         }
         }
 
 
-        // Load patterns on page load
+        // Load favorites first, then patterns
+        await loadFavorites();
         await loadPatterns();
         await loadPatterns();
         
         
         logMessage('Patterns page initialized successfully', LOG_TYPE.SUCCESS);
         logMessage('Patterns page initialized successfully', LOG_TYPE.SUCCESS);
@@ -2174,4 +2203,143 @@ function closeAnimatedPreview() {
     speedValue.textContent = '1x';
     speedValue.textContent = '1x';
     progressSlider.value = 0;
     progressSlider.value = 0;
     progressValue.textContent = '0%';
     progressValue.textContent = '0%';
+}
+
+// Global set to track favorite patterns
+let favoritePatterns = new Set();
+// Make favoritePatterns available globally for other scripts
+window.favoritePatterns = favoritePatterns;
+
+// Load favorites from server on page load
+async function loadFavorites() {
+    try {
+        const response = await fetch('/get_playlist?name=Favorites');
+        if (response.ok) {
+            const playlist = await response.json();
+            favoritePatterns = new Set(playlist.files);
+            window.favoritePatterns = favoritePatterns; // Keep window reference updated
+            updateAllHeartIcons();
+        }
+    } catch (error) {
+        // Favorites playlist doesn't exist yet - that's OK
+        console.debug('Favorites playlist not found, will create when needed');
+    }
+}
+
+// Toggle favorite status
+async function toggleFavorite(pattern) {
+    const heartIcon = document.getElementById('heart-' + pattern.replace(/[^a-zA-Z0-9]/g, '_'));
+    if (!heartIcon) return;
+    
+    try {
+        if (favoritePatterns.has(pattern)) {
+            // Remove from favorites
+            await removeFromFavorites(pattern);
+            favoritePatterns.delete(pattern);
+            heartIcon.textContent = 'favorite_border';
+            heartIcon.className = 'material-icons text-lg text-gray-400 hover:text-red-500 transition-colors';
+            // Make heart only visible on hover when not favorited
+            heartIcon.parentElement.className = heartIcon.parentElement.className.replace('opacity-100', 'opacity-0 group-hover:opacity-100');
+            showStatusMessage('Removed from favorites', 'success');
+        } else {
+            // Add to favorites
+            await addToFavorites(pattern);
+            favoritePatterns.add(pattern);
+            heartIcon.textContent = 'favorite';
+            heartIcon.className = 'material-icons text-lg text-red-500 hover:text-red-600 transition-colors';
+            // Make heart permanently visible when favorited
+            heartIcon.parentElement.className = heartIcon.parentElement.className.replace('opacity-0 group-hover:opacity-100', 'opacity-100');
+            showStatusMessage('Added to favorites', 'success');
+        }
+    } catch (error) {
+        console.error('Error toggling favorite:', error);
+        showStatusMessage('Failed to update favorites', 'error');
+    }
+}
+
+// Add pattern to favorites playlist
+async function addToFavorites(pattern) {
+    try {
+        // First, check if Favorites playlist exists
+        const checkResponse = await fetch('/get_playlist?name=Favorites');
+        
+        if (checkResponse.ok) {
+            // Playlist exists, add to it
+            const response = await fetch('/add_to_playlist', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    playlist_name: 'Favorites',
+                    pattern: pattern
+                })
+            });
+            
+            if (!response.ok) {
+                throw new Error('Failed to add to favorites playlist');
+            }
+        } else {
+            // Playlist doesn't exist, create it with this pattern
+            const response = await fetch('/create_playlist', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    playlist_name: 'Favorites',
+                    files: [pattern]
+                })
+            });
+            
+            if (!response.ok) {
+                throw new Error('Failed to create favorites playlist');
+            }
+        }
+    } catch (error) {
+        throw new Error(`Failed to add to favorites: ${error.message}`);
+    }
+}
+
+// Remove pattern from favorites playlist
+async function removeFromFavorites(pattern) {
+    try {
+        // Get current favorites playlist
+        const getResponse = await fetch('/get_playlist?name=Favorites');
+        if (!getResponse.ok) return; // No favorites playlist
+        
+        const currentFavorites = await getResponse.json();
+        const updatedFavorites = currentFavorites.files.filter(p => p !== pattern);
+        
+        // Update the playlist
+        const updateResponse = await fetch('/modify_playlist', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+            },
+            body: JSON.stringify({
+                playlist_name: 'Favorites',
+                files: updatedFavorites
+            })
+        });
+        
+        if (!updateResponse.ok) {
+            throw new Error('Failed to update favorites playlist');
+        }
+    } catch (error) {
+        throw new Error(`Failed to remove from favorites: ${error.message}`);
+    }
+}
+
+// Update all heart icons based on current favorites
+function updateAllHeartIcons() {
+    favoritePatterns.forEach(pattern => {
+        const heartIcon = document.getElementById('heart-' + pattern.replace(/[^a-zA-Z0-9]/g, '_'));
+        if (heartIcon) {
+            heartIcon.textContent = 'favorite';
+            heartIcon.className = 'material-icons text-lg text-red-500 hover:text-red-600 transition-colors';
+            // Make heart permanently visible when favorited
+            heartIcon.parentElement.className = heartIcon.parentElement.className.replace('opacity-0 group-hover:opacity-100', 'opacity-100');
+        }
+    });
 } 
 } 

+ 12 - 0
static/js/playlists.js

@@ -788,6 +788,18 @@ function sortPatterns(patterns, sortField, sortDirection) {
                 aVal = a.coordinates_count;
                 aVal = a.coordinates_count;
                 bVal = b.coordinates_count;
                 bVal = b.coordinates_count;
                 break;
                 break;
+            case 'favorite':
+                // Check if patterns are in favorites (access global favoritePatterns)
+                const aIsFavorite = window.favoritePatterns ? window.favoritePatterns.has(a.path) : false;
+                const bIsFavorite = window.favoritePatterns ? window.favoritePatterns.has(b.path) : false;
+                
+                if (aIsFavorite && !bIsFavorite) return sortDirection === 'asc' ? -1 : 1;
+                if (!aIsFavorite && bIsFavorite) return sortDirection === 'asc' ? 1 : -1;
+                
+                // Both have same favorite status, sort by name as secondary sort
+                aVal = a.name.toLowerCase();
+                bVal = b.name.toLowerCase();
+                break;
             default:
             default:
                 aVal = a.name.toLowerCase();
                 aVal = a.name.toLowerCase();
                 bVal = b.name.toLowerCase();
                 bVal = b.name.toLowerCase();

+ 1 - 0
templates/index.html

@@ -207,6 +207,7 @@
                             <option value="name">Name</option>
                             <option value="name">Name</option>
                             <option value="date">Date Modified</option>
                             <option value="date">Date Modified</option>
                             <option value="coordinates">Coordinates</option>
                             <option value="coordinates">Coordinates</option>
+                            <option value="favorite">Favorite</option>
                         </select>
                         </select>
                         <button id="browseSortDirectionBtn" class="p-1 rounded hover:bg-gray-200 text-gray-500 opacity-50 cursor-not-allowed" title="Loading..." disabled>
                         <button id="browseSortDirectionBtn" class="p-1 rounded hover:bg-gray-200 text-gray-500 opacity-50 cursor-not-allowed" title="Loading..." disabled>
                             <span class="material-icons text-sm" id="browseSortDirectionIcon">arrow_upward</span>
                             <span class="material-icons text-sm" id="browseSortDirectionIcon">arrow_upward</span>

+ 1 - 0
templates/playlists.html

@@ -317,6 +317,7 @@ html:not(.dark) #availablePatternsGrid .text-xs {
             <option value="name">Name</option>
             <option value="name">Name</option>
             <option value="date">Date Modified</option>
             <option value="date">Date Modified</option>
             <option value="coordinates">Coordinates</option>
             <option value="coordinates">Coordinates</option>
+            <option value="favorite">Favorite</option>
           </select>
           </select>
           <button id="sortDirectionBtn" class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400" title="Toggle sort direction">
           <button id="sortDirectionBtn" class="p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400" title="Toggle sort direction">
             <span class="material-icons text-sm" id="sortDirectionIcon">arrow_upward</span>
             <span class="material-icons text-sm" id="sortDirectionIcon">arrow_upward</span>