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/data/store.ts b/src/data/store.ts index ec973190..6485fd2c 100644 --- a/src/data/store.ts +++ b/src/data/store.ts @@ -139,7 +139,6 @@ export const nextUpSong: Writable = writable(null); export const songsJustAdded: Writable = writable([]); export const songJustAdded = writable(false); export const shouldShowToast = writable(true); -export const rightClickedAlbum: Writable = writable(null); export const rightClickedTrack: Writable = writable(null); export const rightClickedTracks: Writable = writable(null); export const playerTime = writable(0); diff --git a/src/data/storeHelper.ts b/src/data/storeHelper.ts index c9dfc0a2..10427e79 100644 --- a/src/data/storeHelper.ts +++ b/src/data/storeHelper.ts @@ -13,6 +13,7 @@ import { } from "./store"; import AudioPlayer from "../lib/player/AudioPlayer"; import { get } from "svelte/store"; +import { remove } from "lodash-es"; import type SmartQuery from "src/lib/smart-query/Query"; export function findQueueIndex({ id }: Song): number { @@ -25,6 +26,13 @@ export function findQueueIndexes(songs: Song[]): number[] { return songs.map(({ id }) => q.findIndex((song) => song.id === id)); } +export function removeQueuedSongs(songs: string[]) { + const olds = get(queue); + const news = remove(olds, ({ id }) => !songs.includes(id)); + + setQueue(news, false); +} + export function resetDraggedSongs() { if (get(draggedSongs).length) { draggedOrigin.set(null); diff --git a/src/lib/albums/AlbumMenu.svelte b/src/lib/albums/AlbumMenu.svelte index 301f8fc0..1ad7ed0c 100644 --- a/src/lib/albums/AlbumMenu.svelte +++ b/src/lib/albums/AlbumMenu.svelte @@ -1,26 +1,50 @@ {#if showMenu} - + - {#if $rightClickedAlbum} - - - + + + {#if song.artist} - {#if artworkResult && artworkResultForAlbum === $rightClickedAlbum.id} - - {/if} - - + {/if} + + {#if hasResult("artwork-online")} + + {/if} + + {#if hasResult("artwork-local")} + + {/if} + + {#if song.artist} - - - - {/if} + + + + + {/if} diff --git a/src/lib/data/LibraryEnrichers.ts b/src/lib/data/LibraryEnrichers.ts index eabbd7ec..b5c664d1 100644 --- a/src/lib/data/LibraryEnrichers.ts +++ b/src/lib/data/LibraryEnrichers.ts @@ -2,14 +2,16 @@ import { writeFile } from "@tauri-apps/plugin-fs"; import WBK from "wikibase-sdk"; import { getImageFormat } from "../../utils/FileUtils"; import { db } from "../../data/db"; -import type { Album, Song } from "../../App"; -import { convertFileSrc } from "@tauri-apps/api/core"; +import type { Album, Song, ToImport } from "../../App"; +import { convertFileSrc, invoke } 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 type EnricherResult = { success?: string; error?: string }; + export async function findCountryByArtist(artistName) { if (!artistName?.length) return null; @@ -18,7 +20,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 +31,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 +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,16 +417,16 @@ export async function addCountryDataAllSongs() { } else { addOriginCountryStatus.set({ percent: Math.ceil( - ((idx + 1) / allArtists.length) * 100 - ) + ((idx + 1) / allArtists.length) * 100, + ), }); } - } + }, ); }); } -async function enrichArtistCountry(artist) { +export async function enrichArtistCountry(artist: string): Promise { const country = await findCountryByArtist(artist); console.log("country", country); if (country) { @@ -438,7 +440,56 @@ async function enrichArtistCountry(artist) { 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) { + song.originCountry = country; + + // Find all songs with this artist + const artistSongs = await db.songs + .where("artist") + .equals(song.artist) + .toArray(); + + db.songs.bulkPut( + 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!", + }; +} diff --git a/src/lib/info/MetadataSection.svelte b/src/lib/info/MetadataSection.svelte index e88b48db..0b675607 100644 --- a/src/lib/info/MetadataSection.svelte +++ b/src/lib/info/MetadataSection.svelte @@ -357,7 +357,7 @@ (m) => m.genericId === "artist", )?.value; - if (artist.length) { + if (artist?.length) { for (let i = 1; i < $rightClickedTracks.length; i += 1) { const { mappedMetadata } = await readMappedMetadataFromSong( diff --git a/src/lib/library/CanvasLibrary.svelte b/src/lib/library/CanvasLibrary.svelte index 8fde869c..a8b40fc1 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"; @@ -379,6 +377,9 @@ let ctx: CanvasRenderingContext2D; let dpr; + // if cursor over the library + let isOver = false; + // drag-n-drop let isDraggingOver = false; @@ -941,8 +942,7 @@ let rangeStartSongIdx = null; let rangeEndSongIdx = null; let highlightedSongIdx = 0; - let showTrackMenu = false; - let menuPos; + let trackMenu: TrackMenu; let currentSongInView = false; let currentSongScrollIdx = null; @@ -993,8 +993,22 @@ } else { $rightClickedTrack = song; } - showTrackMenu = true; - menuPos = { x: e.clientX, y: e.clientY }; + + // reposition menu if in a virtual-list + const list = e.target.closest(".virtual-list-inner"); + if (list) { + var rect = list.getBoundingClientRect(); + + trackMenu.open( + songsHighlighted.length > 1 ? songsHighlighted : song, + { x: e.clientX - rect.left, y: e.clientY - rect.top }, + ); + } else { + trackMenu.open( + songsHighlighted.length > 1 ? songsHighlighted : song, + { x: e.clientX, y: e.clientY }, + ); + } } /** @@ -1234,8 +1248,8 @@ "input")) && document.activeElement.tagName.toLowerCase() !== "textarea" ) { - if (showTrackMenu) { - showTrackMenu = false; + if (trackMenu.isOpen()) { + trackMenu.close(); } else { songsHighlighted = []; } @@ -1243,6 +1257,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) { @@ -1688,7 +1712,10 @@ - + (songsHighlighted.length = 0)} +/> { + isOver = true; isDraggingOver = $selectedPlaylistFile && $draggedSongs?.length > 0; }} on:mouseleave={() => { + isOver = false; isDraggingOver = false; }} > @@ -2081,19 +2110,19 @@ e, ) => { // 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 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"; export let columnOrder; export let fields; diff --git a/src/lib/library/TrackMenu.svelte b/src/lib/library/TrackMenu.svelte index ee2b5586..32bdfd76 100644 --- a/src/lib/library/TrackMenu.svelte +++ b/src/lib/library/TrackMenu.svelte @@ -1,7 +1,6 @@ {#if showMenu} - + - {#if $rightClickedTrack} + {#if song} - {#if $rightClickedTrack.tags} + {#if song.tags}
- {#each $rightClickedTrack.tags as tag} + {#each song.tags as tag}

{tag}

@@ -420,36 +359,39 @@ autoCompleteValue={tagAutoCompleteValue} onEnterPressed={addTagToContextItem} placeholder="Add a tag" - onEscPressed={closeMenu} + onEscPressed={close} + isDisabled={isDisabled()} small /> - - - - - - - - - - - {:else if $rightClickedTracks.length} + {:else if songs.length} - {#await commonTagsBetweenTracks($rightClickedTracks) then tags} + {#await commonTagsBetweenTracks(songs) then tags} {#if tags}
{#each tags as tag} @@ -462,7 +404,7 @@ $uiView = "library"; $selectedTags.add(tag); $selectedTags = $selectedTags; - closeMenu(); + close(); }} >

{tag}

@@ -482,7 +424,8 @@ onEnterPressed={addTagToContextItem} autoFocus placeholder="Add a tag" - onEscPressed={closeMenu} + onEscPressed={close} + isDisabled={isDisabled()} small /> {/if} @@ -491,41 +434,59 @@ {#if $selectedPlaylistFile || $uiView === "to-delete"} {/if} { - removeTrackFromLibrary(); - }} + isConfirming={isConfirming("remove")} + isDisabled={isDisabled("remove")} + isLoading={isLoading("remove")} + onClick={removeSongs("remove", removeFromLibrary)} text={$LL.trackMenu.removeFromLibrary( - $rightClickedTracks.length ? $rightClickedTracks.length : 1, + songs.length ? songs.length : 1, )} confirmText="Click again to confirm" + description={isLoading("remove") ? "Removing from library..." : ""} /> { - deleteFile(); - }} - description={$LL.trackMenu.deleteFileHint()} - text={$LL.trackMenu.deleteFile( - $rightClickedTracks.length ? $rightClickedTracks.length : 1, - )} + isConfirming={isConfirming("delete")} + isDisabled={isDisabled("delete")} + isLoading={isLoading("delete")} + onClick={removeSongs("delete", removeFromSystem)} + text={$LL.trackMenu.deleteFile(songs.length ? songs.length : 1)} confirmText="Click again to confirm" + description={isLoading("delete") + ? "Moving to Trash / Recycle bin..." + : $LL.trackMenu.deleteFileHint()} /> - - + {#if song} + + {/if} +
{/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 a53aeeb5..f0e0c609 100644 --- a/src/lib/queue/QueueMenu.svelte +++ b/src/lib/queue/QueueMenu.svelte @@ -5,26 +5,31 @@ smartQueryResults, uiView, } from "../../data/store"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import { setQueue } from "../../data/storeHelper"; - import MenuDivider from "../menu/MenuDivider.svelte"; - import MenuInput from "../menu/MenuInput.svelte"; + import MenuDivider from "../ui/menu/MenuDivider.svelte"; + 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/ToolsMenu.svelte b/src/lib/queue/ToolsMenu.svelte new file mode 100644 index 00000000..13c0b057 --- /dev/null +++ b/src/lib/queue/ToolsMenu.svelte @@ -0,0 +1,103 @@ + + +{#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 6179faed..e60a62b1 100644 --- a/src/lib/queue/TrackMenu.svelte +++ b/src/lib/queue/TrackMenu.svelte @@ -1,26 +1,35 @@ + + {#if showMenu} - + + {#if song} + + + More Tools + + + {/if} {#if songs.length > 1} {/if} + + + {#if song} - - - - - - - - - - - - {/if} diff --git a/src/lib/sidebar/Sidebar.svelte b/src/lib/sidebar/Sidebar.svelte index 523b1ec1..9558406c 100644 --- a/src/lib/sidebar/Sidebar.svelte +++ b/src/lib/sidebar/Sidebar.svelte @@ -62,8 +62,8 @@ } from "../../data/store"; import LL from "../../i18n/i18n-svelte"; import { currentThemeObject } from "../../theming/store"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import audioPlayer from "../player/AudioPlayer"; import { isIAPlaying } from "../player/WebAudioPlayer"; import type { SavedSmartQuery } from "../smart-query/QueryPart"; @@ -74,7 +74,7 @@ import { optionalTippy } from "../ui/TippyAction"; import VolumeSlider from "../ui/VolumeSlider.svelte"; import Seekbar from "./Seekbar.svelte"; - import MenuDivider from "../menu/MenuDivider.svelte"; + import MenuDivider from "../ui/menu/MenuDivider.svelte"; import { resetDraggedSongs, setDraggedPlaylist, diff --git a/src/lib/smart-query/SmartQueryBuilder.svelte b/src/lib/smart-query/SmartQueryBuilder.svelte index cebd6b28..914c38f6 100644 --- a/src/lib/smart-query/SmartQueryBuilder.svelte +++ b/src/lib/smart-query/SmartQueryBuilder.svelte @@ -10,7 +10,7 @@ } from "../../data/store"; import LL from "../../i18n/i18n-svelte"; import { autoWidth } from "../../utils/AutoWidth"; - import Menu from "../menu/Menu.svelte"; + import Menu from "../ui/menu/Menu.svelte"; import Icon from "../ui/Icon.svelte"; import type { QueryPartStruct } from "./QueryPart"; import { BUILT_IN_QUERY_PARTS } from "./QueryParts"; diff --git a/src/lib/ui/CompressionSelector.svelte b/src/lib/ui/CompressionSelector.svelte index b5a7a40d..f29e5e75 100644 --- a/src/lib/ui/CompressionSelector.svelte +++ b/src/lib/ui/CompressionSelector.svelte @@ -1,8 +1,8 @@ { highlightedAlbum = null; }} diff --git a/src/lib/views/InternetArchiveView.svelte b/src/lib/views/InternetArchiveView.svelte index d2f1803e..223fc8e3 100644 --- a/src/lib/views/InternetArchiveView.svelte +++ b/src/lib/views/InternetArchiveView.svelte @@ -23,8 +23,8 @@ iaSelectedItem, webPlayerVolume, } from "../../data/store"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import { open } from "@tauri-apps/plugin-shell"; let audios; 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)} />
{ 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 +99,12 @@ enrichLinks(); } + $: if ($wikiArtist) { + getWiki($wikiArtist); + } + + $: _$encodeURIComponent = encodeURIComponent; + let albumMentions: Mention[] = []; let songMentions: Mention[] = []; let artistMentions: Mention[] = []; @@ -339,19 +350,21 @@ />
{#if isLoading} -

Loading...

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

{previousArtist}

+ Viewing wiki for: {/if} +

{previousArtist}

- {#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} @@ -458,6 +471,41 @@ {/each} {/each} -->
+ {:else} +
+

No result found.

+

+

Search {previousArtist} for:

+ +
{/if} @@ -716,4 +764,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; + } + } diff --git a/src/lib/your-music/OtherTab.svelte b/src/lib/your-music/OtherTab.svelte index 753f4f7a..6a114ce1 100644 --- a/src/lib/your-music/OtherTab.svelte +++ b/src/lib/your-music/OtherTab.svelte @@ -4,8 +4,8 @@ import { onMount } from "svelte"; import { flip } from "svelte/animate"; import { quadInOut } from "svelte/easing"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import FileBlock from "./FileBlock.svelte"; import LinkBlock from "./LinkBlock.svelte"; import { open } from "@tauri-apps/plugin-dialog"; diff --git a/src/lib/your-music/Scrapbook.svelte b/src/lib/your-music/Scrapbook.svelte index 9bd7c8cd..b08f5b13 100644 --- a/src/lib/your-music/Scrapbook.svelte +++ b/src/lib/your-music/Scrapbook.svelte @@ -23,13 +23,13 @@ import FileBlock from "./FileBlock.svelte"; import LinkBlock from "./LinkBlock.svelte"; import { onMount } from "svelte"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import { flip } from "svelte/animate"; import { quadInOut } from "svelte/easing"; import { getLinkItemWithData } from "../../utils/URLMetadata"; import TagCloud from "./TagCloud.svelte"; - import MenuInput from "../menu/MenuInput.svelte"; + import MenuInput from "../ui/menu/MenuInput.svelte"; import hotkeys from "hotkeys-js"; import Icon from "../ui/Icon.svelte"; import ButtonWithIcon from "../ui/ButtonWithIcon.svelte"; diff --git a/src/lib/your-music/SongDetails.svelte b/src/lib/your-music/SongDetails.svelte index 412527b9..3695b976 100644 --- a/src/lib/your-music/SongDetails.svelte +++ b/src/lib/your-music/SongDetails.svelte @@ -32,8 +32,8 @@ hoveredFiles, } from "../../data/store"; import { getContentFileType } from "../../utils/FileUtils"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import Icon from "../ui/Icon.svelte"; import Input from "../ui/Input.svelte"; import KeySelector from "../ui/KeySelector.svelte"; diff --git a/src/lib/your-music/YourArtists.svelte b/src/lib/your-music/YourArtists.svelte index 09841b88..ba88ea36 100644 --- a/src/lib/your-music/YourArtists.svelte +++ b/src/lib/your-music/YourArtists.svelte @@ -17,8 +17,8 @@ import { loadArtistsFromSongbook } from "../../data/ArtistsToolkitData"; import LL from "../../i18n/i18n-svelte"; import { currentThemeObject } from "../../theming/store"; - import Menu from "../menu/Menu.svelte"; - import MenuOption from "../menu/MenuOption.svelte"; + import Menu from "../ui/menu/Menu.svelte"; + import MenuOption from "../ui/menu/MenuOption.svelte"; import Divider from "../ui/Divider.svelte"; import Dropdown from "../ui/Dropdown.svelte"; import Icon from "../ui/Icon.svelte"; 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); + }, }; }