From 81ef5bac901ef813129fd916f8f3416a3d82c6c0 Mon Sep 17 00:00:00 2001 From: Markury Date: Wed, 1 Oct 2025 22:19:10 -0400 Subject: [PATCH] feat(wip): search --- README.md | 9 +- src/lib/ToolBar.svelte | 4 +- src/lib/components/SearchResultsTable.svelte | 94 +++++++++++ src/lib/library/search.ts | 67 ++++++++ src/lib/services/deezer/search.ts | 71 ++++++++ src/lib/types/search.ts | 24 +++ src/routes/+page.svelte | 2 +- src/routes/search/+page.svelte | 156 ++++++++++++++++++ src/routes/search/results/+page.svelte | 162 +++++++++++++++++++ 9 files changed, 583 insertions(+), 6 deletions(-) create mode 100644 src/lib/components/SearchResultsTable.svelte create mode 100644 src/lib/library/search.ts create mode 100644 src/lib/services/deezer/search.ts create mode 100644 src/lib/types/search.ts create mode 100644 src/routes/search/+page.svelte create mode 100644 src/routes/search/results/+page.svelte diff --git a/README.md b/README.md index cbc77b9..f043a3e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ -# shark +# Shark! Desktop music management application written in Typescript. +## Inspiration + +Inspired by [this post on X](https://x.com/tehmondspartan/status/1972011472265118148), and the functionality of Deemix, written from the ground up for Tauri's webwiew without the Node.js dependency. Instead it has much more specific dependencies :) + + ## Tech Stack - [Tauri 2](https://v2.tauri.app/start/) (desktop runtime) @@ -45,8 +50,6 @@ bun run tauri build bun run check ``` -## How It Works - ### Architecture ``` diff --git a/src/lib/ToolBar.svelte b/src/lib/ToolBar.svelte index f7ba870..4b4575c 100644 --- a/src/lib/ToolBar.svelte +++ b/src/lib/ToolBar.svelte @@ -69,10 +69,10 @@ Now Playing - + Settings diff --git a/src/lib/components/SearchResultsTable.svelte b/src/lib/components/SearchResultsTable.svelte new file mode 100644 index 0000000..ce7cd9d --- /dev/null +++ b/src/lib/components/SearchResultsTable.svelte @@ -0,0 +1,94 @@ + + +
+ + + + + + + + + + + {#each results as result, i} + handleRowClick(i)} + ondblclick={() => handleRowDoubleClick(result, i)} + > + + + + + + {/each} + +
TrackArtistDurationAlbum
{result.title}{result.artist}{formatDuration(result.duration)}{result.album}
+
+ + diff --git a/src/lib/library/search.ts b/src/lib/library/search.ts new file mode 100644 index 0000000..36faf72 --- /dev/null +++ b/src/lib/library/search.ts @@ -0,0 +1,67 @@ +import { initDatabase, type DbAlbum } from './database'; +import { loadAlbumTracks } from './album'; +import type { SearchResult } from '$lib/types/search'; + +/** + * Search local library for tracks matching the query + * Searches album titles, artist names, and then loads tracks from matching albums + */ +export async function searchLocalTracks(query: string): Promise { + if (!query.trim()) { + return []; + } + + const database = await initDatabase(); + const searchTerm = `%${query.trim()}%`; + + // Search albums by title or artist name + const albums = await database.select( + `SELECT * FROM albums + WHERE title LIKE $1 COLLATE NOCASE + OR artist_name LIKE $1 COLLATE NOCASE + ORDER BY artist_name COLLATE NOCASE, title COLLATE NOCASE + LIMIT 50`, + [searchTerm] + ); + + if (albums.length === 0) { + return []; + } + + // Load tracks from all matching albums + const results: SearchResult[] = []; + + for (const album of albums) { + try { + const tracks = await loadAlbumTracks(album.path); + + for (const track of tracks) { + // Filter tracks by query in track title or artist + const trackTitle = track.metadata.title || track.filename; + const trackArtist = track.metadata.artist || album.artist_name; + + if ( + trackTitle.toLowerCase().includes(query.toLowerCase()) || + trackArtist.toLowerCase().includes(query.toLowerCase()) || + album.title.toLowerCase().includes(query.toLowerCase()) + ) { + results.push({ + title: trackTitle, + artist: trackArtist, + album: album.title, + duration: track.metadata.duration, + source: 'local', + albumPath: album.path, + artistName: album.artist_name, + albumTitle: album.title + }); + } + } + } catch (error) { + console.error(`Error loading tracks from album ${album.path}:`, error); + // Continue with next album + } + } + + return results; +} diff --git a/src/lib/services/deezer/search.ts b/src/lib/services/deezer/search.ts new file mode 100644 index 0000000..235e912 --- /dev/null +++ b/src/lib/services/deezer/search.ts @@ -0,0 +1,71 @@ +import type { SearchResult } from '$lib/types/search'; + +/** + * Search Deezer for tracks matching the query + * TODO: Implement actual Deezer search API + * For now, returns mock data + */ +export async function searchDeezerTracks(query: string): Promise { + if (!query.trim()) { + return []; + } + + // Simulate API delay + await new Promise(resolve => setTimeout(resolve, 300)); + + // Mock data - replace with actual Deezer API implementation + const mockResults: SearchResult[] = [ + { + title: 'One More Time', + artist: 'Daft Punk', + album: 'Discovery', + duration: 320, + source: 'online', + trackId: '3135556', + coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg' + }, + { + title: 'Get Lucky', + artist: 'Daft Punk', + album: 'Random Access Memories', + duration: 369, + source: 'online', + trackId: '67238732', + coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/b0b1e82769d1a1e452fd3f2f95d3bb0f/250x250-000000-80-0-0.jpg' + }, + { + title: 'Around the World', + artist: 'Daft Punk', + album: 'Homework', + duration: 429, + source: 'online', + trackId: '3135553', + coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/250x250-000000-80-0-0.jpg' + }, + { + title: 'Harder, Better, Faster, Stronger', + artist: 'Daft Punk', + album: 'Discovery', + duration: 224, + source: 'online', + trackId: '3135554', + coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg' + }, + { + title: 'Digital Love', + artist: 'Daft Punk', + album: 'Discovery', + duration: 301, + source: 'online', + trackId: '3135555', + coverArtUrl: 'https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/250x250-000000-80-0-0.jpg' + } + ]; + + // Filter mock results by query + return mockResults.filter(result => + result.title.toLowerCase().includes(query.toLowerCase()) || + result.artist.toLowerCase().includes(query.toLowerCase()) || + result.album.toLowerCase().includes(query.toLowerCase()) + ); +} diff --git a/src/lib/types/search.ts b/src/lib/types/search.ts new file mode 100644 index 0000000..3ac02a1 --- /dev/null +++ b/src/lib/types/search.ts @@ -0,0 +1,24 @@ +/** + * Search result representing a track from either local DB or online service + */ +export interface SearchResult { + // Common fields + title: string; + artist: string; + album: string; + duration?: number; // in seconds + + // Source-specific fields + source: 'local' | 'online'; + + // Local-specific: path info for navigation + albumPath?: string; + artistName?: string; + albumTitle?: string; + + // Online-specific: track ID for downloading + trackId?: string; + coverArtUrl?: string; +} + +export type SearchType = 'local' | 'online'; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e5dca62..1d16f16 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -5,7 +5,7 @@
cat

Welcome to Shark!

-

Your music library manager and player.

+

Your music library manager.

{#if !$settings.musicFolder}
diff --git a/src/routes/search/+page.svelte b/src/routes/search/+page.svelte new file mode 100644 index 0000000..4c147df --- /dev/null +++ b/src/routes/search/+page.svelte @@ -0,0 +1,156 @@ + + +
+
+ + + + +
+ + +
+ + {#if isSearching} +

Searching...

+ {/if} + + {#if error} +

{error}

+ {/if} +
+
+ + diff --git a/src/routes/search/results/+page.svelte b/src/routes/search/results/+page.svelte new file mode 100644 index 0000000..dd5707b --- /dev/null +++ b/src/routes/search/results/+page.svelte @@ -0,0 +1,162 @@ + + +
+ {#if loading} +

Searching...

+ {:else if error} +
+ {:else} +
+

+ {results.length} Search Result{results.length !== 1 ? 's' : ''} for "{query}" +

+ + Edit +
+ +
+ +
+ {/if} +
+ +