Ver código fonte

Add sort by favorites option to Browse and Playlist pages

- Add 'favorites' to SortOption type in lib/types.ts
- BrowsePage: Add favorites sorting (favorites first, then by name)
- PlaylistsPage: Add favorites state, load from Favorites playlist
- PlaylistsPage: Add favorites sorting to pattern picker modal
- Both pages now show Favorites as first sort option in dropdown

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 2 semanas atrás
pai
commit
71a043c763

+ 1 - 1
frontend/src/lib/types.ts

@@ -20,7 +20,7 @@ export interface Playlist {
   files: string[]
 }
 
-export type SortOption = 'name' | 'date' | 'size'
+export type SortOption = 'name' | 'date' | 'size' | 'favorites'
 export type PreExecution = 'none' | 'adaptive' | 'clear_from_in' | 'clear_from_out' | 'clear_sideway'
 export type RunMode = 'single' | 'indefinite'
 

+ 12 - 2
frontend/src/pages/BrowsePage.tsx

@@ -46,7 +46,7 @@ interface PreviewData {
 // Coordinates come as [theta, rho] tuples from the backend
 type Coordinate = [number, number]
 
-type SortOption = 'name' | 'date' | 'size'
+type SortOption = 'name' | 'date' | 'size' | 'favorites'
 type PreExecution = 'none' | 'adaptive' | 'clear_from_in' | 'clear_from_out' | 'clear_sideway'
 
 const preExecutionOptions: { value: PreExecution; label: string }[] = [
@@ -369,6 +369,15 @@ export function BrowsePage() {
         case 'size':
           comparison = a.coordinates_count - b.coordinates_count
           break
+        case 'favorites': {
+          const aFav = favorites.has(a.path) ? 1 : 0
+          const bFav = favorites.has(b.path) ? 1 : 0
+          comparison = bFav - aFav // Favorites first
+          if (comparison === 0) {
+            comparison = a.name.localeCompare(b.name) // Then by name
+          }
+          break
+        }
         default:
           return 0
       }
@@ -376,7 +385,7 @@ export function BrowsePage() {
     })
 
     return result
-  }, [patterns, selectedCategory, searchQuery, sortBy, sortAsc])
+  }, [patterns, selectedCategory, searchQuery, sortBy, sortAsc, favorites])
 
   // Batched preview loading - collects requests and fetches in batches
   const requestPreview = useCallback((path: string) => {
@@ -894,6 +903,7 @@ export function BrowsePage() {
               <SelectValue placeholder="Sort" />
             </SelectTrigger>
             <SelectContent>
+              <SelectItem value="favorites">Favorites</SelectItem>
               <SelectItem value="name">Name</SelectItem>
               <SelectItem value="date">Modified</SelectItem>
               <SelectItem value="size">Size</SelectItem>

+ 26 - 1
frontend/src/pages/PlaylistsPage.tsx

@@ -50,6 +50,9 @@ export function PlaylistsPage() {
   const [sortBy, setSortBy] = useState<SortOption>('name')
   const [sortAsc, setSortAsc] = useState(true)
 
+  // Favorites state (loaded from "Favorites" playlist)
+  const [favorites, setFavorites] = useState<Set<string>>(new Set())
+
   // Create/Rename playlist modal
   const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
   const [isRenameModalOpen, setIsRenameModalOpen] = useState(false)
@@ -156,6 +159,7 @@ export function PlaylistsPage() {
     initPreviewCacheDB().catch(() => {})
     fetchPlaylists()
     fetchAllPatterns()
+    loadFavorites()
 
     // Cleanup on unmount: abort in-flight requests and clear pending queue
     return () => {
@@ -173,6 +177,7 @@ export function PlaylistsPage() {
   useOnBackendConnected(() => {
     fetchPlaylists()
     fetchAllPatterns()
+    loadFavorites()
   })
 
   const fetchPlaylists = async () => {
@@ -214,6 +219,16 @@ export function PlaylistsPage() {
     }
   }
 
+  // Load favorites from "Favorites" playlist
+  const loadFavorites = async () => {
+    try {
+      const playlist = await apiClient.get<{ files?: string[] }>('/get_playlist?name=Favorites')
+      setFavorites(new Set(playlist.files || []))
+    } catch {
+      // Favorites playlist doesn't exist yet - that's OK
+    }
+  }
+
   // Preview loading functions (similar to BrowsePage)
   const loadPreviewsForPaths = async (paths: string[]) => {
     const cachedPreviews = await getPreviewsFromCache(paths)
@@ -454,12 +469,21 @@ export function PlaylistsPage() {
         case 'size':
           cmp = a.coordinates_count - b.coordinates_count
           break
+        case 'favorites': {
+          const aFav = favorites.has(a.path) ? 1 : 0
+          const bFav = favorites.has(b.path) ? 1 : 0
+          cmp = bFav - aFav // Favorites first
+          if (cmp === 0) {
+            cmp = a.name.localeCompare(b.name) // Then by name
+          }
+          break
+        }
       }
       return sortAsc ? cmp : -cmp
     })
 
     return filtered
-  }, [allPatterns, searchQuery, selectedCategory, sortBy, sortAsc])
+  }, [allPatterns, searchQuery, selectedCategory, sortBy, sortAsc, favorites])
 
   // Get pattern name from path
   const getPatternName = (path: string) => {
@@ -895,6 +919,7 @@ export function PlaylistsPage() {
                   <SelectValue />
                 </SelectTrigger>
                 <SelectContent>
+                  <SelectItem value="favorites">Favorites</SelectItem>
                   <SelectItem value="name">Name</SelectItem>
                   <SelectItem value="date">Modified</SelectItem>
                   <SelectItem value="size">Size</SelectItem>