[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Mobile fixes

Kasper (Mar 10, 2026, 2:40 AM +0100) 779e48e0 4386be94

+9 -29
+2 -1
CHANGELOG.md
··· 5 5 - Add "Show in Playlist" context menu item 6 6 - Remember queue on restart 7 7 - Keep scroll position when opening play history 8 - - Android: Add track streaming search links for Spotify and YouTube Music 8 + - Mobile: Add track streaming search links for Spotify and YouTube Music 9 + - Mobile: Fast playlist loading 9 10 10 11 ## 0.21.0 - 2026 Map 6 11 12 - Add a basic Android app. It only lets you browse a Ferrum Library.json file. No playback or syncing yet.
-7
mobile/README.md
··· 1 - # Tauri + SvelteKit + TypeScript 2 - 3 - This template should help get you started developing with Tauri, SvelteKit and TypeScript in Vite. 4 - 5 - ## Recommended IDE Setup 6 - 7 - [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
+7 -21
mobile/src/routes/Library.svelte
··· 8 8 9 9 type sort_key_type = 'name' | 'artist' | 'dateAdded' | 'playCount' 10 10 type sort_dir_type = 'asc' | 'desc' 11 - type active_filter_type = { kind: 'all' } | { kind: 'liked' } | { kind: 'genre'; value: string } 11 + type active_filter_type = { kind: 'all' } | { kind: 'genre'; value: string } 12 12 type view_type = { kind: 'browser'; folder_id: string } | { kind: 'tracks'; playlist_id: string } 13 13 type streaming_service_type = 'spotify' | 'youtube-music' 14 14 ··· 19 19 }>() 20 20 let error = $state('') 21 21 let search_query = $state('') 22 - let sort_key = $state<sort_key_type>('name') 23 - let sort_dir = $state<sort_dir_type>('asc') 22 + let sort_key = $state<sort_key_type>('dateAdded') 23 + let sort_dir = $state<sort_dir_type>('desc') 24 24 let active_filter = $state<active_filter_type>({ kind: 'all' }) 25 25 26 26 // ── View derived from URL search params ──────────────────────────────────── ··· 128 128 const filtered_tracks = $derived( 129 129 playlist_tracks 130 130 .filter((t) => { 131 - if (active_filter.kind === 'liked') return t.liked === true 132 131 if (active_filter.kind === 'genre') return t.genre === active_filter.value 133 132 return true 134 133 }) ··· 333 332 </p> 334 333 <p class="mt-0.5 text-xs text-neutral-500"> 335 334 {tl.tracks.length} 336 - {tl.tracks.length === 1 ? 'track' : 'tracks'}{tl.liked ? ' · ♥' : ''} 335 + {tl.tracks.length === 1 ? 'track' : 'tracks'} 337 336 </p> 338 337 </div> 339 338 <span class="text-lg text-neutral-400 select-none dark:text-neutral-700">›</span> ··· 425 424 > 426 425 All 427 426 </button> 428 - <button 429 - type="button" 430 - onclick={() => (active_filter = { kind: 'liked' })} 431 - class="shrink-0 rounded-full border px-2.5 py-1 text-xs transition-colors {active_filter.kind === 432 - 'liked' 433 - ? 'border-rose-300 bg-rose-100 font-semibold text-rose-700 dark:border-rose-700 dark:bg-rose-900 dark:text-rose-200' 434 - : 'border-neutral-300 text-neutral-500 hover:text-neutral-600 dark:border-neutral-700 dark:hover:text-neutral-300'}" 435 - > 436 - ♥ Liked 437 - </button> 438 427 {#each genres as genre} 439 428 <button 440 429 type="button" ··· 456 445 {#each filtered_tracks as track} 457 446 <li> 458 447 <button 459 - rel="noopener noreferrer" 460 - class="focus-visible active flex items-center justify-between gap-3 px-4 py-3 focus-visible:bg-neutral-100" 448 + type="button" 449 + class="focus-visible active flex w-full items-center justify-between gap-3 px-4 py-3 focus-visible:bg-neutral-100" 461 450 onclick={() => open_track(track)} 462 451 > 463 - <div> 452 + <div class="grow text-left"> 464 453 <p class="truncate font-medium text-neutral-900 dark:text-neutral-100"> 465 454 {track.name} 466 455 </p> ··· 492 481 <span>{format_duration(track.duration)}</span> 493 482 {#if track.playCount} 494 483 <span>{track.playCount} plays</span> 495 - {/if} 496 - {#if track.liked} 497 - <span class="text-rose-500">♥</span> 498 484 {/if} 499 485 </div> 500 486 </button>