[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.

Fix back button

Kasper (Mar 5, 2026, 1:38 PM +0100) b54fcc35 46e1c816

+19 -38
+19 -38
mobile/src/routes/Library.svelte
··· 1 1 <script lang="ts"> 2 2 import type { Snippet } from 'svelte' 3 + import { goto } from '$app/navigation' 4 + import { page } from '$app/state' 3 5 import type { Track, TrackList, Playlist, Folder, Special, LibraryTauri } from '../../bindings' 6 + import { resolve } from '$app/paths' 4 7 5 8 type sort_key_type = 'name' | 'artist' | 'dateAdded' | 'playCount'; 6 9 type sort_dir_type = 'asc' | 'desc'; 7 10 type active_filter_type = { kind: 'all' } | { kind: 'liked' } | { kind: 'genre'; value: string }; 8 - type breadcrumb_entry = { id: string; name: string }; 9 11 type view_type = 10 - | { kind: 'browser'; folder_id: string; breadcrumb: breadcrumb_entry[] } 11 - | { kind: 'tracks'; playlist_id: string; breadcrumb: breadcrumb_entry[] }; 12 + | { kind: 'browser'; folder_id: string } 13 + | { kind: 'tracks'; playlist_id: string }; 12 14 13 15 const { 14 16 library, ··· 18 20 open_button: Snippet<[]>, 19 21 }>(); 20 22 let error = $state(''); 21 - let view = $state<view_type>({ kind: 'browser', folder_id:'root', breadcrumb: [] }); 22 23 let search_query = $state(''); 23 24 let sort_key = $state<sort_key_type>('name'); 24 25 let sort_dir = $state<sort_dir_type>('asc'); 25 26 let active_filter = $state<active_filter_type>({ kind: 'all' }); 27 + 28 + // ── View derived from URL search params ──────────────────────────────────── 29 + const view = $derived<view_type>( 30 + page.url.searchParams.get('view') === 'tracks' 31 + ? { kind: 'tracks', playlist_id: page.url.searchParams.get('id') ?? 'root' } 32 + : { kind: 'browser', folder_id: page.url.searchParams.get('id') ?? 'root' } 33 + ); 26 34 27 35 // ── Helpers ──────────────────────────────────────────────────────────────── 28 36 ··· 68 76 69 77 // ── Navigation ───────────────────────────────────────────────────────────── 70 78 71 - // We can only navigate into folders or playlists from a browser view, 72 - // so prev.kind is always 'browser' here — no conditional needed. 73 79 function open_folder(id: string) { 74 - if (view.kind !== 'browser') return; 75 - const new_crumb: breadcrumb_entry = { id: view.folder_id, name: node_name(view.folder_id) }; 76 - view = { 77 - kind: 'browser', 78 - folder_id: id, 79 - breadcrumb: [...view.breadcrumb, new_crumb], 80 - }; 80 + // eslint-disable-next-line svelte/no-navigation-without-resolve 81 + goto(resolve('/') + `?view=browser&id=${id}`); 81 82 } 82 83 83 84 function open_playlist(id: string) { 84 - if (view.kind !== 'browser') return; 85 - const new_crumb: breadcrumb_entry = { id: view.folder_id, name: node_name(view.folder_id) }; 86 - view = { 87 - kind: 'tracks', 88 - playlist_id: id, 89 - breadcrumb: [...view.breadcrumb, new_crumb], 90 - }; 91 85 search_query = ''; 92 86 active_filter = { kind: 'all' }; 93 - } 94 - 95 - function go_back() { 96 - const crumb = view.breadcrumb; 97 - if (crumb.length === 0) return; 98 - const parent = crumb[crumb.length - 1]; 99 - const new_crumb = crumb.slice(0, -1); 100 - const tl = get_tracklist(parent.id); 101 - if (tl?.type === 'folder' || tl?.type === 'special') { 102 - view = { kind: 'browser', folder_id: parent.id, breadcrumb: new_crumb }; 103 - } else if (tl?.type === 'playlist') { 104 - view = { kind: 'tracks', playlist_id: parent.id, breadcrumb: new_crumb }; 105 - } 87 + // eslint-disable-next-line svelte/no-navigation-without-resolve 88 + goto(resolve('/') + `?view=tracks&id=${id}`); 106 89 } 107 90 108 91 // ── Derived / async data ────────────────────────────────────────────────── ··· 184 167 185 168 <!-- Header --> 186 169 <header class="flex items-center gap-2 px-4 py-3 border-b border-neutral-800 shrink-0"> 187 - {#if view.breadcrumb.length > 0} 170 + {#if page.url.searchParams.has('id')} 188 171 <button 189 172 type="button" 190 - onclick={go_back} 173 + onclick={() => history.back()} 191 174 class="shrink-0 flex items-center justify-center w-8 h-8 text-xl rounded-lg text-neutral-400 hover:bg-neutral-800 hover:text-neutral-100 transition-colors" 192 175 aria-label="Back" 193 176 >‹</button> ··· 199 182 {:else if view.kind === 'tracks'} 200 183 <p class="font-semibold text-neutral-100 truncate leading-tight">{node_name(view.playlist_id)}</p> 201 184 {/if} 202 - {#if view.breadcrumb.length > 0} 203 - <p class="text-xs text-neutral-600 truncate">{view.breadcrumb.map(b => b.name).join(' › ')}</p> 204 - {/if} 185 + 205 186 </div> 206 187 207 188 {@render open_button()}