From f66615229ac8d808e10289418a6ca6fac7b77647 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 13 Jan 2025 22:36:01 +0100 Subject: [PATCH 01/18] enhance(menu): homogenize and improve --- src/lib/albums/AlbumMenu.svelte | 106 +++++++++++++++++---------- src/lib/data/LibraryEnrichers.ts | 118 ++++++++++++++++--------------- src/lib/library/TrackMenu.svelte | 74 +++++++++---------- src/lib/menu/MenuOption.svelte | 10 ++- src/lib/queue/TrackMenu.svelte | 106 ++++++++++++++------------- 5 files changed, 225 insertions(+), 189 deletions(-) diff --git a/src/lib/albums/AlbumMenu.svelte b/src/lib/albums/AlbumMenu.svelte index 301f8fc0..dd3fef6e 100644 --- a/src/lib/albums/AlbumMenu.svelte +++ b/src/lib/albums/AlbumMenu.svelte @@ -11,10 +11,14 @@ import Menu from "../menu/Menu.svelte"; import MenuDivider from "../menu/MenuDivider.svelte"; import MenuOption from "../menu/MenuOption.svelte"; - import { fetchAlbumArt } from "../data/LibraryEnrichers"; + import { + enrichArtistCountry, + fetchAlbumArt, + } from "../data/LibraryEnrichers"; import { rescanAlbumArtwork } from "../../data/LibraryUtils"; import type { Album, ToImport } from "../../App"; import { invoke } from "@tauri-apps/api/core"; + import { writable } from "svelte/store"; export let position = { x: 0, y: 0 }; export let showMenu = false; @@ -103,6 +107,15 @@ } // Enrichers + let isFetchingOriginCountry = writable(false); + + async function fetchingOriginCountry() { + await enrichArtistCountry( + $rightClickedTracks[0], + isFetchingOriginCountry, + ); + } + let isFetchingArtwork = false; let artworkResult: { success?: string; error?: string }; let artworkResultForAlbum: String; @@ -147,56 +160,71 @@ $rightClickedAlbum.title} by {$rightClickedAlbum.artist}" /> - {#if $rightClickedAlbum} + + + {#if $rightClickedTracks[0].artist} - - - - {#if artworkResult && artworkResultForAlbum === $rightClickedAlbum.id} - - {/if} - + {/if} + + {#if artworkResult && artworkResultForAlbum === $rightClickedAlbum.id} + {/if} + + + {#if $rightClickedTracks[0].artist} + - - - - {/if} + + + + + {/if} diff --git a/src/lib/data/LibraryEnrichers.ts b/src/lib/data/LibraryEnrichers.ts index eabbd7ec..ee5fb31f 100644 --- a/src/lib/data/LibraryEnrichers.ts +++ b/src/lib/data/LibraryEnrichers.ts @@ -7,7 +7,7 @@ import { convertFileSrc } from "@tauri-apps/api/core"; import md5 from "md5"; import { addOriginCountryStatus, userSettings } from "../../data/store"; import { path } from "@tauri-apps/api"; -import { get } from "svelte/store"; +import { get, type Writable } from "svelte/store"; import { fetch } from "@tauri-apps/plugin-http"; export async function findCountryByArtist(artistName) { @@ -18,7 +18,7 @@ export async function findCountryByArtist(artistName) { try { const wdk = WBK({ instance: "https://www.wikidata.org", - sparqlEndpoint: "https://query.wikidata.org/sparql" + sparqlEndpoint: "https://query.wikidata.org/sparql", }); let url = wdk.searchEntities({ search: artistName }); @@ -29,7 +29,7 @@ export async function findCountryByArtist(artistName) { if (!firstItem) return null; url = wdk.getEntities({ - ids: [firstItem.id] + ids: [firstItem.id], }); const { entities } = await fetch(url).then((res) => res.json()); console.log(entities); @@ -59,12 +59,12 @@ export async function findCountryByArtist(artistName) { export async function fetchAlbumArt( album: Album = null, - song: Song = null + song: Song = null, ): Promise<{ success?: string; error?: string }> { if (!album) { const albumPath = song.path.replace(`/${song.file}`, ""); album = await db.albums.get( - md5(`${albumPath} - ${song.album}`.toLowerCase()) + md5(`${albumPath} - ${song.album}`.toLowerCase()), ); } @@ -97,18 +97,18 @@ export async function fetchAlbumArt( } return { - error: "No artwork found!" + error: "No artwork found!", }; } async function fetchAlbumArtWithWikipedia( - album: Album + album: Album, ): Promise<{ success?: string; error?: string }> { try { const ecArtist = encodeURIComponent(album.artist); const dbpediaResult = await ( await fetch( - `https://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=PREFIX+rdf%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0D%0APREFIX+dbpedia2%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fproperty%2F%3E%0D%0APREFIX+owl%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2F%3E%0D%0APREFIX+rdfs%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E%0D%0ASELECT+DISTINCT+%3Fname%2C+%3FcoverArtVar+WHERE+%7B%0D%0A%09%3Fsubject+dbpedia2%3Aname+%3Fname+.%0D%0A%09%3Fsubject+rdfs%3Alabel+%3Flabel+.%0D%0A%09%7B+%3Fsubject+dbpedia2%3Aartist+%3Fartist+%7D+UNION+%7B+%3Fsubject+owl%3Aartist+%3Fartist+%7D%0D%0A%09%7B+%3Fartist+rdfs%3Alabel+%22${ecArtist}%22%40en+%7D+UNION+%7B+%3Fartist+dbpedia2%3Aname+%22${ecArtist}%22%40en+%7D%0D%0A%09%3Fsubject+rdf%3Atype+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2FAlbum%3E+.%0D%0A%09%3Fsubject+dbpedia2%3Acover+%3FcoverArtVar+.%0D%0A%7D%0D%0ALimit+30%0D%0A&format=application%2Fsparql-results%2Bjson&timeout=10000&signal_void=on&signal_unconnected=on` + `https://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=PREFIX+rdf%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0D%0APREFIX+dbpedia2%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fproperty%2F%3E%0D%0APREFIX+owl%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2F%3E%0D%0APREFIX+rdfs%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E%0D%0ASELECT+DISTINCT+%3Fname%2C+%3FcoverArtVar+WHERE+%7B%0D%0A%09%3Fsubject+dbpedia2%3Aname+%3Fname+.%0D%0A%09%3Fsubject+rdfs%3Alabel+%3Flabel+.%0D%0A%09%7B+%3Fsubject+dbpedia2%3Aartist+%3Fartist+%7D+UNION+%7B+%3Fsubject+owl%3Aartist+%3Fartist+%7D%0D%0A%09%7B+%3Fartist+rdfs%3Alabel+%22${ecArtist}%22%40en+%7D+UNION+%7B+%3Fartist+dbpedia2%3Aname+%22${ecArtist}%22%40en+%7D%0D%0A%09%3Fsubject+rdf%3Atype+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2FAlbum%3E+.%0D%0A%09%3Fsubject+dbpedia2%3Acover+%3FcoverArtVar+.%0D%0A%7D%0D%0ALimit+30%0D%0A&format=application%2Fsparql-results%2Bjson&timeout=10000&signal_void=on&signal_unconnected=on`, ) ).json(); console.log("dbpedia", dbpediaResult); @@ -130,9 +130,9 @@ async function fetchAlbumArtWithWikipedia( `https://en.wikipedia.org/w/api.php?format=json&&origin=*&action=query&prop=imageinfo&iiprop=url|size&titles=File:${coverArtFile}`, { headers: { - Accept: "application/json" - } - } + Accept: "application/json", + }, + }, ) ).json(); let imageUrl; @@ -149,39 +149,39 @@ async function fetchAlbumArtWithWikipedia( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } console.log("got art", wikiResult); return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithGenius( album: Album, - geniusApiKey: string + geniusApiKey: string, ): Promise<{ success?: string; error?: string }> { try { const query = encodeURIComponent( - `${album.displayTitle} - ${album.artist}` + `${album.displayTitle} - ${album.artist}`, ); const result = await fetch(`https://api.genius.com/search?q=${query}`, { method: "GET", headers: { Accept: "application/json", - Authorization: `Bearer ${geniusApiKey}` - } + Authorization: `Bearer ${geniusApiKey}`, + }, }); if (!result.ok) { @@ -197,9 +197,9 @@ async function fetchAlbumArtWithGenius( h?.result.header_image_url && !h.result.header_image_url.includes("default_cover_image") && toComparableString(h.result.title).includes( - toComparableString(album.displayTitle) + toComparableString(album.displayTitle), ) && - artist === toComparableString(h?.result?.artist_names) + artist === toComparableString(h?.result?.artist_names), ); if (hit) { @@ -209,26 +209,26 @@ async function fetchAlbumArtWithGenius( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithDiscogs( album: Album, - discogsApiKey: string + discogsApiKey: string, ): Promise<{ success?: string; error?: string }> { try { const ecRelease = encodeURIComponent(album.displayTitle); @@ -241,9 +241,9 @@ async function fetchAlbumArtWithDiscogs( Accept: "application/json", Authorization: `Discogs token=${discogsApiKey}`, "User-Agent": - "Musicat +https://github.com/basharovV/musicat" - } - } + "Musicat +https://github.com/basharovV/musicat", + }, + }, ); if (!result.ok) { @@ -257,7 +257,7 @@ async function fetchAlbumArtWithDiscogs( const hit = data.results.find( (h) => h.type === "release" && - toComparableString(h.title).startsWith(artist) + toComparableString(h.title).startsWith(artist), ); if (hit) { @@ -266,25 +266,25 @@ async function fetchAlbumArtWithDiscogs( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithMusicBrainz( - album: Album + album: Album, ): Promise<{ success?: string; error?: string }> { try { const ecRelease = encodeURIComponent(album.displayTitle); @@ -295,9 +295,9 @@ async function fetchAlbumArtWithMusicBrainz( { method: "GET", headers: { - Accept: "application/json" - } - } + Accept: "application/json", + }, + }, ); if (!result.ok) { throw new Error("MusicBrainz API: " + JSON.stringify(result)); @@ -319,8 +319,8 @@ async function fetchAlbumArtWithMusicBrainz( `https://coverartarchive.org/release/${release.id}/front`, { method: "GET", - maxRedirections: 0 - } + maxRedirections: 0, + }, ); if (result.status === 307) { @@ -332,18 +332,18 @@ async function fetchAlbumArtWithMusicBrainz( if (imageUrl && (await fetchImage(album, imageUrl, "jpg"))) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } @@ -358,7 +358,7 @@ function toComparableString(str) { async function fetchImage( album: Album, imageUrl?: string, - imageExtension?: string + imageExtension?: string, ): Promise { if (!imageUrl || !imageExtension) { return false; @@ -388,9 +388,9 @@ async function fetchImage( format: format, size: { width: 200, - height: 200 - } - } + height: 200, + }, + }, }); return true; // Double success! Artwork should now be visible in the library @@ -415,30 +415,34 @@ export async function addCountryDataAllSongs() { } else { addOriginCountryStatus.set({ percent: Math.ceil( - ((idx + 1) / allArtists.length) * 100 - ) + ((idx + 1) / allArtists.length) * 100, + ), }); } - } + }, ); }); } -async function enrichArtistCountry(artist) { - const country = await findCountryByArtist(artist); +export async function enrichArtistCountry( + song: Song, + isFetching: Writable, +): Promise { + isFetching.set(true); + const country = await findCountryByArtist(song.artist); console.log("country", country); if (country) { + song.originCountry = country; + // Find all songs with this artist const artistSongs = await db.songs .where("artist") - .equals(artist) + .equals(song.artist) .toArray(); - - db.songs.bulkPut( - artistSongs.map((s) => { - s.originCountry = country; - return s; - }) - ); + artistSongs.forEach((s) => { + s.originCountry = country; + db.songs.update(s.id, s); + }); } + isFetching.set(false); } diff --git a/src/lib/library/TrackMenu.svelte b/src/lib/library/TrackMenu.svelte index ee2b5586..42011819 100644 --- a/src/lib/library/TrackMenu.svelte +++ b/src/lib/library/TrackMenu.svelte @@ -21,7 +21,10 @@ } from "../../data/store"; import LL from "../../i18n/i18n-svelte"; import { dedupe } from "../../utils/ArrayUtils"; - import { findCountryByArtist } from "../data/LibraryEnrichers"; + import { + enrichArtistCountry, + findCountryByArtist, + } from "../data/LibraryEnrichers"; import Menu from "../menu/Menu.svelte"; import MenuDivider from "../menu/MenuDivider.svelte"; import MenuInput from "../menu/MenuInput.svelte"; @@ -29,6 +32,7 @@ import Icon from "../ui/Icon.svelte"; import { deleteFromLibrary } from "../../data/LibraryUtils"; import { liveQuery } from "dexie"; + import { writable } from "svelte/store"; export let pos = { x: 0, y: 0 }; export let showMenu = false; @@ -245,26 +249,10 @@ } // Enrichers - let isFetchingOriginCountry = false; - - async function enrichArtistCountry() { - isFetchingOriginCountry = true; - const country = await findCountryByArtist($rightClickedTrack.artist); - console.log("country", country); - if (country) { - $rightClickedTrack.originCountry = country; - - // Find all songs with this artist - const artistSongs = await db.songs - .where("artist") - .equals($rightClickedTrack.artist) - .toArray(); - artistSongs.forEach((s) => { - s.originCountry = country; - db.songs.update(s.id, s); - }); - } - isFetchingOriginCountry = false; + let isFetchingOriginCountry = writable(false); + + async function fetchingOriginCountry() { + await enrichArtistCountry($rightClickedTrack, isFetchingOriginCountry); } let isReimporting = false; @@ -423,28 +411,27 @@ onEscPressed={closeMenu} small /> - - - - - - - - - - {:else if $rightClickedTracks.length} @@ -525,6 +512,9 @@ /> + {#if $rightClickedTrack} + + {/if} {/if} diff --git a/src/lib/menu/MenuOption.svelte b/src/lib/menu/MenuOption.svelte index 324ccf33..aac8d114 100644 --- a/src/lib/menu/MenuOption.svelte +++ b/src/lib/menu/MenuOption.svelte @@ -48,7 +48,7 @@
{#if text} -

{isConfirming ? confirmText : text}

+

{@html isConfirming ? confirmText : text}

{#if description} {description} @@ -172,6 +172,14 @@ opacity: 0.7; text-align: left; } + + :global(u) { + text-underline-offset: 3px; + } + + :global(i) { + font-size: 12.5px; + } } } diff --git a/src/lib/queue/TrackMenu.svelte b/src/lib/queue/TrackMenu.svelte index 6179faed..1c9f6ad8 100644 --- a/src/lib/queue/TrackMenu.svelte +++ b/src/lib/queue/TrackMenu.svelte @@ -12,9 +12,13 @@ import Menu from "../menu/Menu.svelte"; import MenuDivider from "../menu/MenuDivider.svelte"; import MenuOption from "../menu/MenuOption.svelte"; - import { findCountryByArtist } from "../data/LibraryEnrichers"; + import { + enrichArtistCountry, + findCountryByArtist, + } from "../data/LibraryEnrichers"; import type { Song } from "../../App"; import { findQueueIndexes, updateQueues } from "../../data/storeHelper"; + import { writable } from "svelte/store"; export let pos = { x: 0, y: 0 }; export let showMenu = false; @@ -141,26 +145,10 @@ } // Enrichers - let isFetchingOriginCountry = false; - - async function enrichArtistCountry() { - isFetchingOriginCountry = true; - const country = await findCountryByArtist(song.artist); - console.log("country", country); - if (country) { - song.originCountry = country; - - // Find all songs with this artist - const artistSongs = await db.songs - .where("artist") - .equals(song.artist) - .toArray(); - artistSongs.forEach((s) => { - s.originCountry = country; - db.songs.update(s.id, s); - }); - } - isFetchingOriginCountry = false; + let isFetchingOriginCountry = writable(false); + + async function fetchingOriginCountry() { + await enrichArtistCountry(song, isFetchingOriginCountry); } function unselect() { @@ -175,46 +163,64 @@ isDisabled={true} text={song ? song.title : songs.length + " tracks"} /> - {#if songs.length > 1} - - {/if} - {#if song} - - - + {#if song.artist} + + + + {/if} - - - + {#if song.artist} + + + + + {/if} + {/if} + {#if songs.length > 1} + + {/if} + + + + {#if song} {/if} From 00e48ed8a1c6cc2acf055b0911d71e390efe5518 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 13 Jan 2025 22:38:51 +0100 Subject: [PATCH 02/18] fix(album): reposition menu if in virtual list --- src/lib/library/CanvasLibrary.svelte | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/library/CanvasLibrary.svelte b/src/lib/library/CanvasLibrary.svelte index 2d0da1a6..8456ecf7 100644 --- a/src/lib/library/CanvasLibrary.svelte +++ b/src/lib/library/CanvasLibrary.svelte @@ -1003,8 +1003,18 @@ } else { $rightClickedTrack = song; } + + // reposition menu if in a virtual-list + const list = e.target.closest(".virtual-list-inner"); + if (list) { + var rect = list.getBoundingClientRect(); + + menuPos = { x: e.clientX - rect.left, y: e.clientY - rect.top }; + } else { + menuPos = { x: e.clientX, y: e.clientY }; + } + showTrackMenu = true; - menuPos = { x: e.clientX, y: e.clientY }; } /** From 7b1ee066ba14c86f08aa916276ceddbfb8fa4f95 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 14 Jan 2025 16:57:26 +0100 Subject: [PATCH 03/18] fix: rescanning album's artwork --- src/data/LibraryUtils.ts | 15 ---- src/lib/albums/AlbumMenu.svelte | 59 +++++++++---- src/lib/data/LibraryEnrichers.ts | 138 +++++++++++++++++++------------ 3 files changed, 127 insertions(+), 85 deletions(-) diff --git a/src/data/LibraryUtils.ts b/src/data/LibraryUtils.ts index 99cc9323..031bd841 100644 --- a/src/data/LibraryUtils.ts +++ b/src/data/LibraryUtils.ts @@ -339,21 +339,6 @@ export async function openTauriImportDialog() { } } -export async function rescanAlbumArtwork(album: Album) { - // console.log("adding artwork from song", song, newAlbum); - - const response = await invoke("scan_paths", { - event: { - paths: [album.path], - recursive: false, - process_albums: true, - is_async: false, - }, - }); - - // TODO: Write updated album with updated artwork to DB -} - export async function runScan() { const settings = get(userSettings); diff --git a/src/lib/albums/AlbumMenu.svelte b/src/lib/albums/AlbumMenu.svelte index 301f8fc0..b7805c58 100644 --- a/src/lib/albums/AlbumMenu.svelte +++ b/src/lib/albums/AlbumMenu.svelte @@ -11,8 +11,11 @@ import Menu from "../menu/Menu.svelte"; import MenuDivider from "../menu/MenuDivider.svelte"; import MenuOption from "../menu/MenuOption.svelte"; - import { fetchAlbumArt } from "../data/LibraryEnrichers"; - import { rescanAlbumArtwork } from "../../data/LibraryUtils"; + import { + type EnricherResult, + fetchAlbumArt, + rescanAlbumArtwork, + } from "../data/LibraryEnrichers"; import type { Album, ToImport } from "../../App"; import { invoke } from "@tauri-apps/api/core"; @@ -103,19 +106,32 @@ } // Enrichers - let isFetchingArtwork = false; - let artworkResult: { success?: string; error?: string }; - let artworkResultForAlbum: String; + let fetchArtworkLoading = false; + let fetchArtworkResult: EnricherResult; + let fetchArtworkAlbumId: String; + async function fetchArtwork() { - artworkResult = null; - isFetchingArtwork = true; - artworkResultForAlbum = $rightClickedAlbum.id; - artworkResult = await fetchAlbumArt($rightClickedAlbum); - isFetchingArtwork = false; + fetchArtworkLoading = true; + fetchArtworkResult = null; + fetchArtworkAlbumId = $rightClickedAlbum.id; + + fetchArtworkResult = await fetchAlbumArt($rightClickedAlbum); + + fetchArtworkLoading = false; } + let rescanLocalArtworkLoading = false; + let rescanLocalArtworkResult: EnricherResult; + let rescanLocalArtworkAlbumId: String; + async function rescanLocalArtwork() { - rescanAlbumArtwork($rightClickedAlbum); + rescanLocalArtworkLoading = true; + rescanLocalArtworkResult = null; + rescanLocalArtworkAlbumId = $rightClickedAlbum.id; + + rescanLocalArtworkResult = await rescanAlbumArtwork($rightClickedAlbum); + + rescanLocalArtworkLoading = false; } let isReimporting = false; @@ -163,23 +179,34 @@ - {#if artworkResult && artworkResultForAlbum === $rightClickedAlbum.id} + {#if fetchArtworkResult && fetchArtworkAlbumId === $rightClickedAlbum.id} {/if} + {#if rescanLocalArtworkResult && rescanLocalArtworkAlbumId === $rightClickedAlbum.id} + + {/if} res.json()); console.log(entities); @@ -59,12 +61,12 @@ export async function findCountryByArtist(artistName) { export async function fetchAlbumArt( album: Album = null, - song: Song = null -): Promise<{ success?: string; error?: string }> { + song: Song = null, +): Promise { if (!album) { const albumPath = song.path.replace(`/${song.file}`, ""); album = await db.albums.get( - md5(`${albumPath} - ${song.album}`.toLowerCase()) + md5(`${albumPath} - ${song.album}`.toLowerCase()), ); } @@ -97,18 +99,18 @@ export async function fetchAlbumArt( } return { - error: "No artwork found!" + error: "No artwork found!", }; } async function fetchAlbumArtWithWikipedia( - album: Album -): Promise<{ success?: string; error?: string }> { + album: Album, +): Promise { try { const ecArtist = encodeURIComponent(album.artist); const dbpediaResult = await ( await fetch( - `https://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=PREFIX+rdf%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0D%0APREFIX+dbpedia2%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fproperty%2F%3E%0D%0APREFIX+owl%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2F%3E%0D%0APREFIX+rdfs%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E%0D%0ASELECT+DISTINCT+%3Fname%2C+%3FcoverArtVar+WHERE+%7B%0D%0A%09%3Fsubject+dbpedia2%3Aname+%3Fname+.%0D%0A%09%3Fsubject+rdfs%3Alabel+%3Flabel+.%0D%0A%09%7B+%3Fsubject+dbpedia2%3Aartist+%3Fartist+%7D+UNION+%7B+%3Fsubject+owl%3Aartist+%3Fartist+%7D%0D%0A%09%7B+%3Fartist+rdfs%3Alabel+%22${ecArtist}%22%40en+%7D+UNION+%7B+%3Fartist+dbpedia2%3Aname+%22${ecArtist}%22%40en+%7D%0D%0A%09%3Fsubject+rdf%3Atype+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2FAlbum%3E+.%0D%0A%09%3Fsubject+dbpedia2%3Acover+%3FcoverArtVar+.%0D%0A%7D%0D%0ALimit+30%0D%0A&format=application%2Fsparql-results%2Bjson&timeout=10000&signal_void=on&signal_unconnected=on` + `https://dbpedia.org/sparql?default-graph-uri=http%3A%2F%2Fdbpedia.org&query=PREFIX+rdf%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0D%0APREFIX+dbpedia2%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fproperty%2F%3E%0D%0APREFIX+owl%3A+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2F%3E%0D%0APREFIX+rdfs%3A+%3Chttp%3A%2F%2Fwww.w3.org%2F2000%2F01%2Frdf-schema%23%3E%0D%0ASELECT+DISTINCT+%3Fname%2C+%3FcoverArtVar+WHERE+%7B%0D%0A%09%3Fsubject+dbpedia2%3Aname+%3Fname+.%0D%0A%09%3Fsubject+rdfs%3Alabel+%3Flabel+.%0D%0A%09%7B+%3Fsubject+dbpedia2%3Aartist+%3Fartist+%7D+UNION+%7B+%3Fsubject+owl%3Aartist+%3Fartist+%7D%0D%0A%09%7B+%3Fartist+rdfs%3Alabel+%22${ecArtist}%22%40en+%7D+UNION+%7B+%3Fartist+dbpedia2%3Aname+%22${ecArtist}%22%40en+%7D%0D%0A%09%3Fsubject+rdf%3Atype+%3Chttp%3A%2F%2Fdbpedia.org%2Fontology%2FAlbum%3E+.%0D%0A%09%3Fsubject+dbpedia2%3Acover+%3FcoverArtVar+.%0D%0A%7D%0D%0ALimit+30%0D%0A&format=application%2Fsparql-results%2Bjson&timeout=10000&signal_void=on&signal_unconnected=on`, ) ).json(); console.log("dbpedia", dbpediaResult); @@ -130,9 +132,9 @@ async function fetchAlbumArtWithWikipedia( `https://en.wikipedia.org/w/api.php?format=json&&origin=*&action=query&prop=imageinfo&iiprop=url|size&titles=File:${coverArtFile}`, { headers: { - Accept: "application/json" - } - } + Accept: "application/json", + }, + }, ) ).json(); let imageUrl; @@ -149,39 +151,39 @@ async function fetchAlbumArtWithWikipedia( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } console.log("got art", wikiResult); return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithGenius( album: Album, - geniusApiKey: string -): Promise<{ success?: string; error?: string }> { + geniusApiKey: string, +): Promise { try { const query = encodeURIComponent( - `${album.displayTitle} - ${album.artist}` + `${album.displayTitle} - ${album.artist}`, ); const result = await fetch(`https://api.genius.com/search?q=${query}`, { method: "GET", headers: { Accept: "application/json", - Authorization: `Bearer ${geniusApiKey}` - } + Authorization: `Bearer ${geniusApiKey}`, + }, }); if (!result.ok) { @@ -197,9 +199,9 @@ async function fetchAlbumArtWithGenius( h?.result.header_image_url && !h.result.header_image_url.includes("default_cover_image") && toComparableString(h.result.title).includes( - toComparableString(album.displayTitle) + toComparableString(album.displayTitle), ) && - artist === toComparableString(h?.result?.artist_names) + artist === toComparableString(h?.result?.artist_names), ); if (hit) { @@ -209,27 +211,27 @@ async function fetchAlbumArtWithGenius( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithDiscogs( album: Album, - discogsApiKey: string -): Promise<{ success?: string; error?: string }> { + discogsApiKey: string, +): Promise { try { const ecRelease = encodeURIComponent(album.displayTitle); @@ -241,9 +243,9 @@ async function fetchAlbumArtWithDiscogs( Accept: "application/json", Authorization: `Discogs token=${discogsApiKey}`, "User-Agent": - "Musicat +https://github.com/basharovV/musicat" - } - } + "Musicat +https://github.com/basharovV/musicat", + }, + }, ); if (!result.ok) { @@ -257,7 +259,7 @@ async function fetchAlbumArtWithDiscogs( const hit = data.results.find( (h) => h.type === "release" && - toComparableString(h.title).startsWith(artist) + toComparableString(h.title).startsWith(artist), ); if (hit) { @@ -266,26 +268,26 @@ async function fetchAlbumArtWithDiscogs( if (await fetchImage(album, imageUrl, imageExtension)) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } async function fetchAlbumArtWithMusicBrainz( - album: Album -): Promise<{ success?: string; error?: string }> { + album: Album, +): Promise { try { const ecRelease = encodeURIComponent(album.displayTitle); const ecArtist = encodeURIComponent(album.artist); @@ -295,9 +297,9 @@ async function fetchAlbumArtWithMusicBrainz( { method: "GET", headers: { - Accept: "application/json" - } - } + Accept: "application/json", + }, + }, ); if (!result.ok) { throw new Error("MusicBrainz API: " + JSON.stringify(result)); @@ -319,8 +321,8 @@ async function fetchAlbumArtWithMusicBrainz( `https://coverartarchive.org/release/${release.id}/front`, { method: "GET", - maxRedirections: 0 - } + maxRedirections: 0, + }, ); if (result.status === 307) { @@ -332,18 +334,18 @@ async function fetchAlbumArtWithMusicBrainz( if (imageUrl && (await fetchImage(album, imageUrl, "jpg"))) { return { - success: "Artwork saved!" + success: "Artwork saved!", }; } return { - error: "No artwork found!" + error: "No artwork found!", }; } catch (err) { console.error("Error fetching artwork", err); return { - error: "Error fetching artwork." + err + error: "Error fetching artwork." + err, }; } } @@ -358,7 +360,7 @@ function toComparableString(str) { async function fetchImage( album: Album, imageUrl?: string, - imageExtension?: string + imageExtension?: string, ): Promise { if (!imageUrl || !imageExtension) { return false; @@ -388,9 +390,9 @@ async function fetchImage( format: format, size: { width: 200, - height: 200 - } - } + height: 200, + }, + }, }); return true; // Double success! Artwork should now be visible in the library @@ -415,11 +417,11 @@ export async function addCountryDataAllSongs() { } else { addOriginCountryStatus.set({ percent: Math.ceil( - ((idx + 1) / allArtists.length) * 100 - ) + ((idx + 1) / allArtists.length) * 100, + ), }); } - } + }, ); }); } @@ -438,7 +440,35 @@ async function enrichArtistCountry(artist) { artistSongs.map((s) => { s.originCountry = country; return s; - }) + }), ); } } + +export async function rescanAlbumArtwork( + album: Album, +): Promise { + // console.log("adding artwork from song", album); + const response = await invoke("scan_paths", { + event: { + paths: [album.path], + recursive: false, + process_albums: true, + is_async: false, + }, + }); + + if (response?.albums?.length === 1 && response.albums[0].artwork) { + album.artwork = response.albums[0].artwork; + + await db.albums.put(album, album.id); + + return { + success: "Artwork saved!", + }; + } + + return { + error: "No artwork found!", + }; +} From e27d97c4192099e4c2fcfcfb5ca97d3b698e5f69 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 14 Jan 2025 18:38:37 +0100 Subject: [PATCH 04/18] fix: remove menu --- src/lib/library/CanvasLibrary.svelte | 15 +++ src/lib/library/TrackMenu.svelte | 177 ++++++++++++--------------- src/lib/menu/MenuInput.svelte | 1 + src/lib/menu/MenuOption.svelte | 4 +- src/lib/ui/Input.svelte | 2 + 5 files changed, 101 insertions(+), 98 deletions(-) diff --git a/src/lib/library/CanvasLibrary.svelte b/src/lib/library/CanvasLibrary.svelte index 2d0da1a6..05ebd86e 100644 --- a/src/lib/library/CanvasLibrary.svelte +++ b/src/lib/library/CanvasLibrary.svelte @@ -389,6 +389,9 @@ let ctx: CanvasRenderingContext2D; let dpr; + // if cursor over the library + let isOver = false; + // drag-n-drop let isDraggingOver = false; @@ -1238,6 +1241,16 @@ }); function onKeyDown(event) { + if ( + isOver && + event.keyCode === 65 && + (($os === "macos" && event.metaKey) || event.ctrlKey) + ) { + event.preventDefault(); + + songsHighlighted = [...songs]; + } + if ($arrowFocus !== "library") return; if (event.keyCode === 16) { @@ -1728,10 +1741,12 @@ class:ready bind:this={scrollContainer} on:mouseenter={() => { + isOver = true; isDraggingOver = $selectedPlaylistFile && $draggedSongs?.length > 0; }} on:mouseleave={() => { + isOver = false; isDraggingOver = false; }} > diff --git a/src/lib/library/TrackMenu.svelte b/src/lib/library/TrackMenu.svelte index ee2b5586..de64c271 100644 --- a/src/lib/library/TrackMenu.svelte +++ b/src/lib/library/TrackMenu.svelte @@ -30,9 +30,15 @@ import { deleteFromLibrary } from "../../data/LibraryUtils"; import { liveQuery } from "dexie"; + type RemoveType = "remove" | "remove_from_playlist" | "delete"; + export let pos = { x: 0, y: 0 }; export let showMenu = false; + let explorerName: string; + let isDestructiveConfirmType: RemoveType = null; + let isLoading: RemoveType = null; + let songId; onMount(async () => { const os = await type(); @@ -43,38 +49,31 @@ case "windows": explorerName = "Explorer"; break; - case "Linux": + case "linux": explorerName = "File manager"; break; } }); - let songId; - let isDestructiveConfirmType: "remove" | "remove_from_playlist" | "delete" = - null; - let isConfirmingRemoveFromPlaylist = false; - $: { if ( $rightClickedTracks?.map((s) => s.id).includes(songId) || songId !== $rightClickedTrack?.id ) { isDestructiveConfirmType = null; - isConfirmingRemoveFromPlaylist = false; } } function closeMenu() { showMenu = false; isDestructiveConfirmType = null; - isConfirmingRemoveFromPlaylist = false; } /** * Playlists are M3U files, and in special cases (like the to-delete playlist, a separate table) * @param tracks */ - async function deleteTracksFromPlaylists(tracks: Song[]) { + async function deleteTracksFromPlaylist(tracks: Song[]) { // Delete from playlist if ($selectedPlaylistFile) { // Delete directly from M3U file @@ -102,98 +101,58 @@ }); } - /** - * Removes track from the library - but not from disk! - * i.e from songs DB, playlists - */ - async function removeTrackFromLibrary() { - console.log("[Track menu] Remove from library"); - if (isDestructiveConfirmType !== "remove") { - songId = $rightClickedTrack?.id; - isDestructiveConfirmType = "remove"; - return; - } + function removeSongs( + type: RemoveType, + action: (songs: Song[]) => Promise, + ): () => Promise { + return async () => { + if (isLoading) { + return; + } - let tracksToRemove = []; - if ($rightClickedTracks.length) { - tracksToRemove = $rightClickedTracks; - } else if ($rightClickedTrack) { - tracksToRemove.push($rightClickedTrack); - } + if (isDestructiveConfirmType !== type) { + songId = $rightClickedTrack?.id; + isDestructiveConfirmType = type; + return; + } - closeMenu(); + isLoading = "remove"; + isDestructiveConfirmType = null; - // Delete - await deleteTracksFromPlaylists(tracksToRemove); - await deleteFromLibrary(tracksToRemove); + const tracksToRemove = $rightClickedTracks.length + ? $rightClickedTracks + : [$rightClickedTrack]; - // Reset - if ($rightClickedTracks.length) { + await action(tracksToRemove); + + closeMenu(); + + // Reset $rightClickedTracks = []; - } else if ($rightClickedTrack) { $rightClickedTrack = null; - } - isDestructiveConfirmType = null; + isLoading = null; + }; } - async function removeFromPlaylist() { - if (isDestructiveConfirmType !== "remove_from_playlist") { - isDestructiveConfirmType = "remove_from_playlist"; - songId = $rightClickedTrack?.id; - isConfirmingRemoveFromPlaylist = true; - return; - } - - let tracksToRemove = []; - if ($rightClickedTracks.length) { - tracksToRemove = $rightClickedTracks; - } else if ($rightClickedTrack) { - tracksToRemove.push($rightClickedTrack); - } - - closeMenu(); - await deleteTracksFromPlaylists(tracksToRemove); + /** + * Removes track from the library - but not from disk! + * i.e from songs DB, playlists + */ + async function removeFromLibrary(songs: Song[]) { + await deleteFromLibrary(songs); + } - // Reset - if ($rightClickedTracks.length) { - $rightClickedTracks = []; - } else if ($rightClickedTrack) { - $rightClickedTrack = null; - } - isDestructiveConfirmType = null; + async function removeFromPlaylist(songs: Song[]) { + await deleteTracksFromPlaylist(songs); } /** * Moves the files(s) to the system trash / Recycle bin * Also performs deletion from DB */ - async function deleteFile() { - console.log("delete"); - if (isDestructiveConfirmType !== "delete") { - songId = $rightClickedTrack?.id; - isDestructiveConfirmType = "delete"; - return; - } - - let tracksToRemove = []; - if ($rightClickedTracks.length) { - tracksToRemove = $rightClickedTracks; - } else if ($rightClickedTrack) { - tracksToRemove.push($rightClickedTrack); - } - - closeMenu(); - await deleteTracksFromFileSystem(tracksToRemove); - await deleteTracksFromPlaylists(tracksToRemove); - await deleteFromLibrary(tracksToRemove); - - // Reset - if ($rightClickedTracks.length) { - $rightClickedTracks = []; - } else if ($rightClickedTrack) { - $rightClickedTrack = null; - } - isDestructiveConfirmType = null; + async function removeFromSystem(songs: Song[]) { + await deleteTracksFromFileSystem(songs); + await deleteFromLibrary(songs); } function searchArtistOnYouTube() { @@ -385,6 +344,7 @@ ? "Re-import track" : `Re-import ${$rightClickedTracks.length} tracks`} isLoading={isReimporting} + isDisabled={!!isLoading} /> {#if $rightClickedTrack} @@ -421,6 +381,7 @@ onEnterPressed={addTagToContextItem} placeholder="Add a tag" onEscPressed={closeMenu} + isDisabled={!!isLoading} small /> @@ -434,17 +395,23 @@ : "Origin country" : "Origin country ✅"} description="from Wikipedia" + isDisabled={!!isLoading} /> - + {:else if $rightClickedTracks.length} @@ -483,6 +450,7 @@ autoFocus placeholder="Add a tag" onEscPressed={closeMenu} + isDisabled={!!isLoading} small /> {/if} @@ -491,41 +459,58 @@ {#if $selectedPlaylistFile || $uiView === "to-delete"} {/if} { - removeTrackFromLibrary(); - }} + isDisabled={!!isLoading && isLoading !== "remove"} + onClick={removeSongs("remove", removeFromLibrary)} text={$LL.trackMenu.removeFromLibrary( $rightClickedTracks.length ? $rightClickedTracks.length : 1, )} confirmText="Click again to confirm" + description={isLoading === "remove" + ? "Removing from library..." + : ""} /> { - deleteFile(); - }} - description={$LL.trackMenu.deleteFileHint()} + isDisabled={!!isLoading && isLoading !== "delete"} + onClick={removeSongs("delete", removeFromSystem)} text={$LL.trackMenu.deleteFile( $rightClickedTracks.length ? $rightClickedTracks.length : 1, )} confirmText="Click again to confirm" + description={isLoading === "remove" + ? "Moving to Trash / Recycle bin..." + : $LL.trackMenu.deleteFileHint()} /> - + {/if} diff --git a/src/lib/menu/MenuInput.svelte b/src/lib/menu/MenuInput.svelte index 88959aca..3bfad96e 100644 --- a/src/lib/menu/MenuInput.svelte +++ b/src/lib/menu/MenuInput.svelte @@ -50,6 +50,7 @@ { - onClick && onClick(); + !isDisabled && onClick && onClick(); }} >
@@ -140,7 +140,7 @@ } } &.destructive { - &:hover { + &:hover:not(.disabled) { background: var(--menu-item-destructive-hover-bg); color: var(--menu-item-destructive-hover-text); } diff --git a/src/lib/ui/Input.svelte b/src/lib/ui/Input.svelte index 4deb4561..f8ad81f7 100644 --- a/src/lib/ui/Input.svelte +++ b/src/lib/ui/Input.svelte @@ -15,6 +15,7 @@ export let padding = true; export let small = false; export let alt = false; + export let disabled = false; function onKeyDown(evt) { if (evt.keyCode === 13) { @@ -68,6 +69,7 @@ on:keydown={onKeyDown} spellcheck="false" autocomplete="off" + {disabled} /> {#if autoCompleteValue && autoCompleteValue?.toLowerCase() !== value?.toLowerCase()} From 3cbc4a6d26180300f7c1c1bdf86c55147dabfc2b Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 14 Jan 2025 18:44:23 +0100 Subject: [PATCH 05/18] fix: re-add remove from playlist --- src/lib/library/TrackMenu.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/library/TrackMenu.svelte b/src/lib/library/TrackMenu.svelte index de64c271..c090d540 100644 --- a/src/lib/library/TrackMenu.svelte +++ b/src/lib/library/TrackMenu.svelte @@ -140,6 +140,7 @@ */ async function removeFromLibrary(songs: Song[]) { await deleteFromLibrary(songs); + await deleteTracksFromPlaylist(songs); } async function removeFromPlaylist(songs: Song[]) { @@ -153,6 +154,7 @@ async function removeFromSystem(songs: Song[]) { await deleteTracksFromFileSystem(songs); await deleteFromLibrary(songs); + await deleteTracksFromPlaylist(songs); } function searchArtistOnYouTube() { From 5c32e29717d9b2cc83884d322b5a9d60d8d43118 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 15 Jan 2025 12:49:31 +0100 Subject: [PATCH 06/18] refactor: move menu to ui --- src/lib/albums/AlbumMenu.svelte | 6 +++--- src/lib/library/ColumnPicker.svelte | 6 +++--- src/lib/library/TrackMenu.svelte | 13 +++++-------- src/lib/queue/QueueMenu.svelte | 8 ++++---- src/lib/queue/TrackMenu.svelte | 6 +++--- src/lib/sidebar/Sidebar.svelte | 6 +++--- src/lib/smart-query/SmartQueryBuilder.svelte | 2 +- src/lib/ui/CompressionSelector.svelte | 4 ++-- src/lib/ui/Dropdown.svelte | 4 ++-- src/lib/ui/KeySelector.svelte | 2 +- src/lib/{ => ui}/menu/Menu.svelte | 4 ++-- src/lib/{ => ui}/menu/MenuDivider.svelte | 2 +- src/lib/{ => ui}/menu/MenuInput.svelte | 4 ++-- src/lib/{ => ui}/menu/MenuOption.svelte | 4 ++-- src/lib/views/InternetArchiveView.svelte | 4 ++-- src/lib/your-music/OtherTab.svelte | 4 ++-- src/lib/your-music/Scrapbook.svelte | 6 +++--- src/lib/your-music/SongDetails.svelte | 4 ++-- src/lib/your-music/YourArtists.svelte | 4 ++-- 19 files changed, 45 insertions(+), 48 deletions(-) rename src/lib/{ => ui}/menu/Menu.svelte (98%) rename src/lib/{ => ui}/menu/MenuDivider.svelte (76%) rename src/lib/{ => ui}/menu/MenuInput.svelte (97%) rename src/lib/{ => ui}/menu/MenuOption.svelte (98%) diff --git a/src/lib/albums/AlbumMenu.svelte b/src/lib/albums/AlbumMenu.svelte index c4a751cd..9152b102 100644 --- a/src/lib/albums/AlbumMenu.svelte +++ b/src/lib/albums/AlbumMenu.svelte @@ -8,9 +8,9 @@ rightClickedAlbum, rightClickedTracks, } from "../../data/store"; - import Menu from "../menu/Menu.svelte"; - import MenuDivider from "../menu/MenuDivider.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuDivider from "../ui/menu/MenuDivider.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import { type EnricherResult, enrichArtistCountry, diff --git a/src/lib/library/ColumnPicker.svelte b/src/lib/library/ColumnPicker.svelte index d567e42a..98d606a1 100644 --- a/src/lib/library/ColumnPicker.svelte +++ b/src/lib/library/ColumnPicker.svelte @@ -1,7 +1,7 @@ {#if showMenu} - + - {#if $rightClickedTracks[0].artist} + {#if song.artist} {/if} - {#if fetchArtworkResult && fetchArtworkAlbumId === $rightClickedAlbum.id} - + {#if hasResult("artwork-online")} + {/if} - {#if rescanLocalArtworkResult && rescanLocalArtworkAlbumId === $rightClickedAlbum.id} - + {#if hasResult("artwork-local")} + {/if} - {#if $rightClickedTracks[0].artist} + {#if song.artist} {/if} - + {/if} diff --git a/src/lib/data/LibraryEnrichers.ts b/src/lib/data/LibraryEnrichers.ts index 5ffd3d59..b5c664d1 100644 --- a/src/lib/data/LibraryEnrichers.ts +++ b/src/lib/data/LibraryEnrichers.ts @@ -426,11 +426,26 @@ export async function addCountryDataAllSongs() { }); } -export async function enrichArtistCountry( - song: Song, - isFetching: Writable, -): Promise { - isFetching.set(true); +export async function enrichArtistCountry(artist: string): Promise { + const country = await findCountryByArtist(artist); + console.log("country", country); + if (country) { + // Find all songs with this artist + const artistSongs = await db.songs + .where("artist") + .equals(artist) + .toArray(); + + db.songs.bulkPut( + artistSongs.map((s) => { + s.originCountry = country; + return s; + }), + ); + } +} + +export async function enrichSongCountry(song: Song): Promise { const country = await findCountryByArtist(song.artist); console.log("country", country); if (country) { @@ -449,7 +464,6 @@ export async function enrichArtistCountry( }), ); } - isFetching.set(false); } export async function rescanAlbumArtwork( diff --git a/src/lib/library/CanvasLibrary.svelte b/src/lib/library/CanvasLibrary.svelte index f7475fa2..39f9dcbd 100644 --- a/src/lib/library/CanvasLibrary.svelte +++ b/src/lib/library/CanvasLibrary.svelte @@ -43,7 +43,6 @@ importStatus, isPlaying, isQueueOpen, - isShuffleEnabled, isSidebarOpen, isSmartQueryBuilderOpen, isSmartQuerySaveUiOpen, @@ -65,7 +64,6 @@ smartQueryInitiator, smartQueryResults, uiView, - draggedSource, } from "../../data/store"; import LL from "../../i18n/i18n-svelte"; import { currentThemeObject } from "../../theming/store"; @@ -954,8 +952,7 @@ let rangeStartSongIdx = null; let rangeEndSongIdx = null; let highlightedSongIdx = 0; - let showTrackMenu = false; - let menuPos; + let trackMenu: TrackMenu; let currentSongInView = false; let currentSongScrollIdx = null; @@ -1012,12 +1009,16 @@ if (list) { var rect = list.getBoundingClientRect(); - menuPos = { x: e.clientX - rect.left, y: e.clientY - rect.top }; + trackMenu.open( + songsHighlighted.length > 1 ? songsHighlighted : song, + { x: e.clientX - rect.left, y: e.clientY - rect.top }, + ); } else { - menuPos = { x: e.clientX, y: e.clientY }; + trackMenu.open( + songsHighlighted.length > 1 ? songsHighlighted : song, + { x: e.clientX, y: e.clientY }, + ); } - - showTrackMenu = true; } /** @@ -1242,8 +1243,8 @@ "input")) && document.activeElement.tagName.toLowerCase() !== "textarea" ) { - if (showTrackMenu) { - showTrackMenu = false; + if (trackMenu.isOpen()) { + trackMenu.close(); } else { songsHighlighted = []; } @@ -1706,7 +1707,7 @@ - + { // TODO: Show overflowed tags in menu - menuPos = { - x: e - .detail - .evt - .clientX, - y: e - .detail - .evt - .clientY, - }; - $rightClickedTrack = - song; - showTrackMenu = true; + trackMenu.open( + song, + { + x: e + .detail + .evt + .clientX, + y: e + .detail + .evt + .clientY, + }, + ); }} > import { invoke } from "@tauri-apps/api/core"; import { type } from "@tauri-apps/plugin-os"; - import { open } from "@tauri-apps/plugin-shell"; import { onMount } from "svelte"; import type { Album, Song, ToImport } from "../../App"; import { db } from "../../data/db"; @@ -9,7 +8,6 @@ import { isSmartQueryBuilderOpen, isTagCloudOpen, - isWikiOpen, popupOpen, rightClickedTrack, rightClickedTracks, @@ -17,11 +15,10 @@ selectedTags, toDeletePlaylist, uiView, - wikiArtist, } from "../../data/store"; import LL from "../../i18n/i18n-svelte"; import { dedupe } from "../../utils/ArrayUtils"; - import { enrichArtistCountry } from "../data/LibraryEnrichers"; + import { enrichSongCountry } from "../data/LibraryEnrichers"; import Menu from "../ui/menu/Menu.svelte"; import MenuDivider from "../ui/menu/MenuDivider.svelte"; import MenuInput from "../ui/menu/MenuInput.svelte"; @@ -30,16 +27,18 @@ import { deleteFromLibrary } from "../../data/LibraryUtils"; import { liveQuery } from "dexie"; import { writable } from "svelte/store"; + import { searchArtistOnWikipedia } from "../menu/search"; + import { openInFinder } from "../menu/file"; - type RemoveType = "remove" | "remove_from_playlist" | "delete"; - - export let pos = { x: 0, y: 0 }; - export let showMenu = false; + type ActionType = "country" | "delete" | "remove" | "remove_from_playlist"; + let confirmingType: ActionType = null; let explorerName: string; - let isDestructiveConfirmType: RemoveType = null; - let isLoading: RemoveType = null; - let songId; + let loadingType: ActionType = null; + let position = { x: 0, y: 0 }; + let showMenu = false; + let song: Song; + let songs: Song[]; onMount(async () => { const os = await type(); @@ -56,20 +55,46 @@ } }); - $: { - if ( - $rightClickedTracks?.map((s) => s.id).includes(songId) || - songId !== $rightClickedTrack?.id - ) { - isDestructiveConfirmType = null; + export function close() { + showMenu = false; + } + + export function isOpen() { + return showMenu; + } + + export function open( + _songs: Song | Song[], + _position: { x: number; y: number }, + ) { + position = _position; + + if (Array.isArray(_songs)) { + song = null; + songs = _songs; + } else { + song = _songs; + songs = []; } + + confirmingType = null; + + showMenu = true; } - function closeMenu() { - showMenu = false; - isDestructiveConfirmType = null; + function compose(action, ...args) { + return () => { + close(); + + action(...args); + }; } + $: isConfirming = (type: ActionType): boolean => confirmingType === type; + $: isDisabled = (type?: ActionType): boolean => + !!loadingType && loadingType !== type; + $: isLoading = (type: ActionType): boolean => loadingType === type; + /** * Playlists are M3U files, and in special cases (like the to-delete playlist, a separate table) * @param tracks @@ -103,35 +128,29 @@ } function removeSongs( - type: RemoveType, + type: ActionType, action: (songs: Song[]) => Promise, ): () => Promise { return async () => { - if (isLoading) { + if (loadingType) { return; } - if (isDestructiveConfirmType !== type) { - songId = $rightClickedTrack?.id; - isDestructiveConfirmType = type; + if (confirmingType !== type) { + confirmingType = type; return; } - isLoading = "remove"; - isDestructiveConfirmType = null; + loadingType = "remove"; + confirmingType = null; - const tracksToRemove = $rightClickedTracks.length - ? $rightClickedTracks - : [$rightClickedTrack]; + const tracksToRemove = songs.length ? songs : [song]; await action(tracksToRemove); - closeMenu(); + loadingType = null; - // Reset - $rightClickedTracks = []; - $rightClickedTrack = null; - isLoading = null; + close(); }; } @@ -158,59 +177,26 @@ await deleteTracksFromPlaylist(songs); } - function searchArtistOnYouTube() { - closeMenu(); - const query = encodeURIComponent($rightClickedTrack.artist); - open(`https://www.youtube.com/results?search_query=${query}`); - } - function searchSongOnYouTube() { - closeMenu(); - const query = encodeURIComponent($rightClickedTrack.title); - open(`https://www.youtube.com/results?search_query=${query}`); - } - function searchArtistOnWikipedia() { - $wikiArtist = $rightClickedTrack.artist; - $isWikiOpen = !$isWikiOpen; - closeMenu(); - } - function openInFinder() { - closeMenu(); - const query = $rightClickedTrack.path.replace( - $rightClickedTrack.file, - "", - ); - open(query); - } - function lookUpChords() { - closeMenu(); - const query = encodeURIComponent( - $rightClickedTrack.artist + - " " + - $rightClickedTrack.title + - " chords", - ); - open(`https://duckduckgo.com/?q=${query}`); - } - function lookUpLyrics() { - closeMenu(); - const query = encodeURIComponent( - $rightClickedTrack.artist + - " " + - $rightClickedTrack.title + - " lyrics", - ); - open(`https://duckduckgo.com/?q=${query}`); - } function openInfo() { - closeMenu(); + if (songs.length === 1) { + $rightClickedTracks = []; + $rightClickedTrack = song; + } else { + $rightClickedTracks = songs; + $rightClickedTrack = null; + } + + close(); $popupOpen = "track-info"; } // Enrichers - let isFetchingOriginCountry = writable(false); - async function fetchingOriginCountry() { - await enrichArtistCountry($rightClickedTrack, isFetchingOriginCountry); + loadingType = "country"; + + await enrichSongCountry(song); + + loadingType = null; } let isReimporting = false; @@ -231,7 +217,7 @@ isReimporting = true; const response = await invoke("scan_paths", { event: { - paths: $rightClickedTracks.map((t) => t.path), + paths: songs.map((t) => t.path), recursive: false, process_albums: true, is_async: false, @@ -263,50 +249,51 @@ if (tagAutoCompleteValue?.length) { tagUserInput = tagAutoCompleteValue; } - if ($rightClickedTracks.length > 1) { + if (songs.length > 1) { await Promise.all( - $rightClickedTracks.map(async (t) => { + songs.map(async (t) => { t.tags = t.tags ? [...t.tags, tagUserInput.toLowerCase().trim()] : [tagUserInput.toLowerCase().trim()]; await db.songs.put(t); }), ); - $rightClickedTracks = $rightClickedTracks; + + songs = songs; } else { - await db.songs.update($rightClickedTrack.id, { - tags: $rightClickedTrack.tags - ? [ - ...$rightClickedTrack.tags, - tagUserInput.toLowerCase().trim(), - ] + await db.songs.update(song.id, { + tags: song.tags + ? [...song.tags, tagUserInput.toLowerCase().trim()] : [tagUserInput.toLowerCase().trim()], }); - $rightClickedTrack = await db.songs.get($rightClickedTrack.id); + + song = await db.songs.get(song.id); } tagUserInput = ""; } async function deleteTag(tag) { console.log("delete"); - if ($rightClickedTracks.length > 1) { + if (songs.length > 1) { await Promise.all( - $rightClickedTracks.map(async (t) => { + songs.map(async (t) => { t.tags = t.tags.filter((t) => t !== tag); await db.songs.put(t); }), ); - $rightClickedTracks = $rightClickedTracks; + + songs = songs; } else { - $rightClickedTrack.tags.splice( - $rightClickedTrack.tags.findIndex((t) => t === tag), + song.tags.splice( + song.tags.findIndex((t) => t === tag), 1, ); - $rightClickedTrack = $rightClickedTrack; - db.songs.update($rightClickedTrack.id, { - tags: $rightClickedTrack.tags, + + db.songs.update(song.id, { + tags: song.tags, }); - $rightClickedTrack = await db.songs.get($rightClickedTrack.id); + + song = await db.songs.get(song.id); } } @@ -317,29 +304,25 @@ {#if showMenu} - + - {#if $rightClickedTrack} + {#if song} - {#if $rightClickedTrack.tags} + {#if song.tags}
- {#each $rightClickedTrack.tags as tag} + {#each song.tags as tag}

{tag}

@@ -367,18 +350,19 @@ autoCompleteValue={tagAutoCompleteValue} onEnterPressed={addTagToContextItem} placeholder="Add a tag" - onEscPressed={closeMenu} - isDisabled={!!isLoading} + onEscPressed={close} + isDisabled={isDisabled()} small /> - {#if $rightClickedTrack.artist} + {#if song.artist} {/if} - {:else if $rightClickedTracks.length} + {:else if songs.length} - {#await commonTagsBetweenTracks($rightClickedTracks) then tags} + {#await commonTagsBetweenTracks(songs) then tags} {#if tags}
{#each tags as tag} @@ -411,7 +395,7 @@ $uiView = "library"; $selectedTags.add(tag); $selectedTags = $selectedTags; - closeMenu(); + close(); }} >

{tag}

@@ -431,8 +415,8 @@ onEnterPressed={addTagToContextItem} autoFocus placeholder="Add a tag" - onEscPressed={closeMenu} - isDisabled={!!isLoading} + onEscPressed={close} + isDisabled={isDisabled()} small /> {/if} @@ -441,61 +425,56 @@ {#if $selectedPlaylistFile || $uiView === "to-delete"} {/if} - {#if $rightClickedTrack} + {#if song} {/if} diff --git a/src/lib/menu/file.ts b/src/lib/menu/file.ts new file mode 100644 index 00000000..e21c2b5e --- /dev/null +++ b/src/lib/menu/file.ts @@ -0,0 +1,7 @@ +import { open } from "@tauri-apps/plugin-shell"; +import type { Song } from "../../App"; + +export function openInFinder(song: Song) { + const query = song.path.replace(song.file, ""); + open(query); +} diff --git a/src/lib/menu/search.ts b/src/lib/menu/search.ts new file mode 100644 index 00000000..afbb35cf --- /dev/null +++ b/src/lib/menu/search.ts @@ -0,0 +1,42 @@ +import { open } from "@tauri-apps/plugin-shell"; +import type { Song } from "../../App"; +import { isWikiOpen, wikiArtist } from "../../data/store"; + +export function searchArtistOnWikiPanel(song: Song) { + wikiArtist.set(song.artist); + isWikiOpen.set(true); +} + +export function searchArtistOnWikipedia(song: Song) { + const query = encodeURIComponent(song.artist); + open(`https://en.wikipedia.org/wiki/${query}`); +} + +export function searchArtistOnYouTube(song: Song) { + const query = encodeURIComponent(song.artist); + open(`https://www.youtube.com/results?search_query=${query}`); +} + +export function searchArtworkOnBrave(song: Song) { + const query = encodeURIComponent(`${song.artist} - ${song.title}`); + open(`https://search.brave.com/images?q=${query}`); +} + +export function searchChords(song: Song) { + const query = encodeURIComponent( + song.artist + " " + song.title + " chords", + ); + open(`https://duckduckgo.com/?q=${query}`); +} + +export function searchLyrics(song: Song) { + const query = encodeURIComponent( + song.artist + " " + song.title + " lyrics", + ); + open(`https://duckduckgo.com/?q=${query}`); +} + +export function searchSongOnYouTube(song: Song) { + const query = encodeURIComponent(song.title); + open(`https://www.youtube.com/results?search_query=${query}`); +} diff --git a/src/lib/queue/QueueMenu.svelte b/src/lib/queue/QueueMenu.svelte index 3e4372b4..f0e0c609 100644 --- a/src/lib/queue/QueueMenu.svelte +++ b/src/lib/queue/QueueMenu.svelte @@ -12,19 +12,24 @@ import MenuInput from "../ui/menu/MenuInput.svelte"; import { createNewPlaylistFile } from "../../data/M3UUtils"; - export let pos = { x: 0, y: 0 }; - export let showMenu = false; - let playlistInput = ""; + let position = { x: 0, y: 0 }; + let showMenu = false; - function clearQueue() { - setQueue([]); + export function close() { + showMenu = false; + } - closeMenu(); + export function open(_position: { x: number; y: number }) { + position = _position; + + showMenu = true; } - function closeMenu() { - showMenu = false; + function clearQueue() { + setQueue([]); + + close(); } function resetToLibrary() { @@ -34,7 +39,7 @@ setQueue($queriedSongs, 0); } - closeMenu(); + close(); } function saveAsPlaylist() { @@ -42,12 +47,12 @@ playlistInput = ""; - closeMenu(); + close(); } {#if showMenu} - + diff --git a/src/lib/queue/TrackMenu.svelte b/src/lib/queue/TrackMenu.svelte index 55c3638e..85ed9259 100644 --- a/src/lib/queue/TrackMenu.svelte +++ b/src/lib/queue/TrackMenu.svelte @@ -1,8 +1,6 @@ {#if showMenu} - + {#if song.artist} @@ -212,7 +200,7 @@ {/if} {#if song} - + {/if} diff --git a/src/lib/views/AlbumsView.svelte b/src/lib/views/AlbumsView.svelte index e34134fa..10a1dbda 100644 --- a/src/lib/views/AlbumsView.svelte +++ b/src/lib/views/AlbumsView.svelte @@ -10,9 +10,6 @@ isPlaying, query, queue, - rightClickedAlbum, - rightClickedTrack, - rightClickedTracks, uiPreferences, uiView, } from "../../data/store"; @@ -30,6 +27,7 @@ const PADDING = 14; let activeAlbums: Album[] = []; + let albumMenu: AlbumMenu; let columnWidth = 0; let container: HTMLDivElement; let currentAlbum: Album; @@ -44,10 +42,8 @@ let isLoading = true; let itemSizes = []; let lastOffset = 0; - let position; let rowCount = 0; let rowHeight = 0; - let showAlbumMenu = false; let virtualList; $: albums = liveQuery(async () => { @@ -245,14 +241,12 @@ async function onRightClick(e, album) { highlightedAlbum = album.id; - $rightClickedAlbum = album; - const tracks = (await db.songs.bulkGet(album.tracksIds)).sort( + + const songs = (await db.songs.bulkGet(album.tracksIds)).sort( (a, b) => a.trackNumber - b.trackNumber, ); - $rightClickedTrack = null; - $rightClickedTracks = tracks; - showAlbumMenu = true; - position = { x: e.clientX, y: e.clientY }; + + albumMenu.open(album, songs, { x: e.clientX, y: e.clientY }); } async function onLeftClick(e, album, index) { @@ -323,8 +317,7 @@ { highlightedAlbum = null; }} diff --git a/src/lib/views/QueueView.svelte b/src/lib/views/QueueView.svelte index 0996aece..232e9cac 100644 --- a/src/lib/views/QueueView.svelte +++ b/src/lib/views/QueueView.svelte @@ -497,9 +497,8 @@ let rangeStartSongIdx = null; let rangeEndSongIdx = null; let highlightedSongIdx = 0; - let showQueueMenu = false; - let showTrackMenu = false; - let menuPos; + let queueMenu: QueueMenu; + let trackMenu: TrackMenu; let shouldProcessDrag = false; let songsHighlighted: Song[] = []; @@ -526,9 +525,7 @@ highlightSong(song, idx, false); } - showTrackMenu = true; - menuPos = { x: e.clientX, y: e.clientY }; - console.log("showTrackMenu", menuPos); + trackMenu.open(songsHighlighted, { x: e.clientX, y: e.clientY }); } function isSongHighlighted(song: Song) { @@ -739,15 +736,18 @@ function onHeaderClick(e) { e.preventDefault(); - showQueueMenu = true; - menuPos = { x: e.detail.evt.clientX, y: e.detail.evt.clientY }; + + queueMenu.open({ x: e.detail.evt.clientX, y: e.detail.evt.clientY }); } function onStageClick(e) { if (e.detail.evt.button === 2) { e.preventDefault(); - showQueueMenu = true; - menuPos = { x: e.detail.evt.clientX, y: e.detail.evt.clientY }; + + queueMenu.open({ + x: e.detail.evt.clientX, + y: e.detail.evt.clientY, + }); } } @@ -919,11 +919,10 @@ - + (songsHighlighted.length = 0)} />
Date: Thu, 16 Jan 2025 03:43:39 +0100 Subject: [PATCH 08/18] fix: wiki panel --- src/lib/views/WikiView.svelte | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/lib/views/WikiView.svelte b/src/lib/views/WikiView.svelte index 44c56d9e..1064bafa 100644 --- a/src/lib/views/WikiView.svelte +++ b/src/lib/views/WikiView.svelte @@ -31,6 +31,7 @@ let wikiSong: Song; $: sections = wtfResult?.sections(); async function getWiki(artist: string) { + console.log(artist); if (!artist) return; isLoading = true; try { @@ -43,6 +44,7 @@ console.log("result", wikiResult); error = null; } catch (err) { + wikiResult = { html: "" }; error = err; } finally { isLoading = false; @@ -77,8 +79,10 @@ onMount(() => { isMounted = true; - $current.song?.artist && getWiki($wikiArtist || $current.song.artist); - // $current.song?.artist && getWikiWtf($current.song.artist); + + if ($current.song?.artist && !$wikiArtist) { + getWiki($current.song.artist); + } }); onDestroy(() => { @@ -94,6 +98,10 @@ enrichLinks(); } + $: if ($wikiArtist) { + getWiki($wikiArtist); + } + let albumMentions: Mention[] = []; let songMentions: Mention[] = []; let artistMentions: Mention[] = []; @@ -345,13 +353,13 @@

{previousArtist}

{/if}
- {#if previousArtist && previousArtist !== $current.song?.artist} + {#if previousArtist && $current.song && previousArtist !== $current.song.artist}
Current artist: getWiki($current.song?.artist)} + text="→ {$current.song.artist}" + onClick={() => getWiki($current.song.artist)} />
{/if} From e77377ed6931e5b83c3f945cfe78630c58000aef Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 16 Jan 2025 12:28:33 +0100 Subject: [PATCH 09/18] fix: move queue tools to submenu --- src/lib/queue/ToolsMenu.svelte | 108 ++++++++++++++++++++++++++++ src/lib/queue/TrackMenu.svelte | 112 ++++++++++-------------------- src/lib/ui/Icon.svelte | 3 + src/lib/ui/menu/Menu.svelte | 9 +++ src/lib/ui/menu/MenuOption.svelte | 6 +- 5 files changed, 161 insertions(+), 77 deletions(-) create mode 100644 src/lib/queue/ToolsMenu.svelte diff --git a/src/lib/queue/ToolsMenu.svelte b/src/lib/queue/ToolsMenu.svelte new file mode 100644 index 00000000..cd1af710 --- /dev/null +++ b/src/lib/queue/ToolsMenu.svelte @@ -0,0 +1,108 @@ + + +{#if showMenu} + + {#if song.artist} + + + + {/if} + + + + {#if song.artist} + + + + + {/if} + +{/if} diff --git a/src/lib/queue/TrackMenu.svelte b/src/lib/queue/TrackMenu.svelte index 85ed9259..e60a62b1 100644 --- a/src/lib/queue/TrackMenu.svelte +++ b/src/lib/queue/TrackMenu.svelte @@ -10,20 +10,13 @@ import Menu from "../ui/menu/Menu.svelte"; import MenuDivider from "../ui/menu/MenuDivider.svelte"; import MenuOption from "../ui/menu/MenuOption.svelte"; - import { enrichSongCountry } from "../data/LibraryEnrichers"; import type { Song } from "../../App"; import { findQueueIndexes, updateQueues } from "../../data/storeHelper"; - import { - searchArtistOnWikiPanel, - searchArtistOnWikipedia, - searchArtistOnYouTube, - searchChords, - searchLyrics, - searchSongOnYouTube, - } from "../menu/search"; import { openInFinder } from "../menu/file"; + import Icon from "../ui/Icon.svelte"; + import ToolsMenu from "./ToolsMenu.svelte"; - type ActionType = "country" | "delete"; + type ActionType = "delete"; export let onUnselect: () => void; @@ -31,10 +24,12 @@ let loadingType: ActionType = null; let position = { x: 0, y: 0 }; let showMenu = false; - let song: Song | null = null; - let songs: Song[] = []; let explorerName: string; + let menu; + let song: Song | null = null; + let songs: Song[] = []; + let toolsMenu: ToolsMenu; onMount(async () => { const os = await type(); @@ -87,6 +82,29 @@ !!loadingType && loadingType !== type; $: isLoading = (type: ActionType): boolean => loadingType === type; + function openInfo() { + if (songs.length === 1) { + $rightClickedTracks = []; + $rightClickedTrack = song; + } else { + $rightClickedTracks = songs; + $rightClickedTrack = null; + } + + close(); + $popupOpen = "track-info"; + } + + function onMoreToolsClick(e) { + var optionRect = e.target.getBoundingClientRect(); + var menuRect = menu.getBoundingClientRect(); + + toolsMenu.open(song, { + x: position.x + menuRect.width, + y: optionRect.y, + }); + } + async function removeFromQueue() { console.log("delete"); if (confirmingType !== "delete") { @@ -115,28 +133,6 @@ } } - function openInfo() { - if (songs.length === 1) { - $rightClickedTracks = []; - $rightClickedTrack = song; - } else { - $rightClickedTracks = songs; - $rightClickedTrack = null; - } - - close(); - $popupOpen = "track-info"; - } - // Enrichers - - async function fetchingOriginCountry() { - loadingType = "country"; - - await enrichSongCountry(song); - - loadingType = "country"; - } - function unselect() { close(); @@ -144,56 +140,20 @@ } + + {#if showMenu} - + {#if song} - {#if song.artist} - - - - {/if} - - - + + More Tools + - {#if song.artist} - - - - - {/if} {/if} {#if songs.length > 1} diff --git a/src/lib/ui/Icon.svelte b/src/lib/ui/Icon.svelte index 0a7c99c2..58f2976e 100644 --- a/src/lib/ui/Icon.svelte +++ b/src/lib/ui/Icon.svelte @@ -119,6 +119,9 @@ "lucide:chevron-down": { svg: ``, }, + "lucide:chevron-right": { + svg: ``, + }, "lucide:chevron-up": { svg: ``, }, diff --git a/src/lib/ui/menu/Menu.svelte b/src/lib/ui/menu/Menu.svelte index 70cddd83..a0884552 100644 --- a/src/lib/ui/menu/Menu.svelte +++ b/src/lib/ui/menu/Menu.svelte @@ -15,6 +15,7 @@ export let maxHeight: number = null; // If this exists, we wrap the contents export let padding = 0; export let sections: MenuSection[] = null; + export let submenu = false; export let hoveredSection = sections !== null ? 0 : null; @@ -24,6 +25,10 @@ ? sections[hoveredSection].items.length : items.length; + export function getBoundingClientRect() { + return menuEl.getBoundingClientRect(); + } + function onKeyUp() { if (hoveredItemIdx > 0) { hoveredItemIdx--; @@ -133,6 +138,7 @@ { @@ -207,6 +213,9 @@ &.relative { position: relative; } + &.submenu { + margin: -8px 0 0 8px; + } } .sections { diff --git a/src/lib/ui/menu/MenuOption.svelte b/src/lib/ui/menu/MenuOption.svelte index 1fb7c370..9f6a5f6e 100644 --- a/src/lib/ui/menu/MenuOption.svelte +++ b/src/lib/ui/menu/MenuOption.svelte @@ -42,7 +42,7 @@ : `background-color: ${$currentThemeObject["menu-item-highlight-bg"]};` : ""} on:click|stopPropagation={(e) => { - !isDisabled && onClick && onClick(); + !isDisabled && onClick && onClick(e); }} >
@@ -181,6 +181,10 @@ font-size: 12.5px; } } + + :global(.right) { + margin: 0 0 0 auto; + } } @keyframes loading { From 0d0631c4a0be364e79f55ed133c51d767b0c0abb Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 16 Jan 2025 14:39:58 +0100 Subject: [PATCH 10/18] feat(wiki): add links when no results --- src/lib/albums/AlbumMenu.svelte | 5 --- src/lib/library/TrackMenu.svelte | 5 ++- src/lib/queue/ToolsMenu.svelte | 5 --- src/lib/views/WikiView.svelte | 60 +++++++++++++++++++++++++++++--- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/lib/albums/AlbumMenu.svelte b/src/lib/albums/AlbumMenu.svelte index 49e8dd06..6cebd218 100644 --- a/src/lib/albums/AlbumMenu.svelte +++ b/src/lib/albums/AlbumMenu.svelte @@ -21,7 +21,6 @@ import { openInFinder } from "../menu/file"; import { searchArtistOnWikiPanel, - searchArtistOnWikipedia, searchArtistOnYouTube, searchArtworkOnBrave, } from "../menu/search"; @@ -236,10 +235,6 @@ onClick={compose(searchArtistOnYouTube, song)} text="YouTube: {song.artist}" /> - {/if} diff --git a/src/lib/queue/ToolsMenu.svelte b/src/lib/queue/ToolsMenu.svelte index cd1af710..c8c07e74 100644 --- a/src/lib/queue/ToolsMenu.svelte +++ b/src/lib/queue/ToolsMenu.svelte @@ -6,7 +6,6 @@ import type { Song } from "../../App"; import { searchArtistOnWikiPanel, - searchArtistOnWikipedia, searchArtistOnYouTube, searchChords, searchLyrics, @@ -95,10 +94,6 @@ onClick={compose(searchArtistOnYouTube, song)} text="YouTube: {song.artist}" /> -
{#if isLoading} -

Loading...

+ Searching wiki for: + {:else} - Viewing wiki for: -

{previousArtist}

+ Viewing wiki for: {/if} +

{previousArtist}

{#if previousArtist && $current.song && previousArtist !== $current.song.artist}
@@ -466,6 +469,41 @@ {/each} {/each} -->
+ {:else} +
+

No result found.

+

+

Search {previousArtist} for:

+ +
{/if}
@@ -724,4 +762,16 @@ // border-radius: 10px; // } } + + .no-result { + padding: 1em; + text-align: start; + background-color: var(--wiki-bg); + color: var(--text); + max-width: 100%; + + ul { + padding-inline-start: 2em; + } + } From 18894e4c2f6a0a67ab0a62f0d15c38005a1df71f Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 16 Jan 2025 15:01:01 +0100 Subject: [PATCH 11/18] fix: hide menu on right click --- src/utils/ClickOutside.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/ClickOutside.ts b/src/utils/ClickOutside.ts index 0d6cf8e4..9e6f6857 100644 --- a/src/utils/ClickOutside.ts +++ b/src/utils/ClickOutside.ts @@ -22,6 +22,7 @@ export function clickOutside(element, callbackFunction) { // so we need to wait before adding a listener. setTimeout(() => { document.body.addEventListener("click", onClick); + document.body.addEventListener("contextmenu", onClick); }, 0); return { @@ -30,6 +31,7 @@ export function clickOutside(element, callbackFunction) { }, destroy() { document.body.removeEventListener("click", onClick); - } + document.body.removeEventListener("contextmenu", onClick); + }, }; } From 1e68c52f4a532ecb02e6ad5eb290e962bcd67dce Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 16 Jan 2025 15:02:36 +0100 Subject: [PATCH 12/18] fix: loading indicator --- src/lib/queue/ToolsMenu.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/queue/ToolsMenu.svelte b/src/lib/queue/ToolsMenu.svelte index c8c07e74..13c0b057 100644 --- a/src/lib/queue/ToolsMenu.svelte +++ b/src/lib/queue/ToolsMenu.svelte @@ -56,7 +56,7 @@ await enrichSongCountry(song); - loadingType = "country"; + loadingType = null; } From 1c8a18db6531bf83403118b5fc383cf01754ac2d Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Thu, 16 Jan 2025 15:09:07 +0100 Subject: [PATCH 13/18] fix: use reactive function --- src/lib/views/WikiView.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/views/WikiView.svelte b/src/lib/views/WikiView.svelte index affd184f..d7ba6cb4 100644 --- a/src/lib/views/WikiView.svelte +++ b/src/lib/views/WikiView.svelte @@ -103,6 +103,8 @@ getWiki($wikiArtist); } + $: _$encodeURIComponent = encodeURIComponent; + let albumMentions: Mention[] = []; let songMentions: Mention[] = []; let artistMentions: Mention[] = []; @@ -484,7 +486,7 @@