diff --git a/src-tauri/src/metadata.rs b/src-tauri/src/metadata.rs index 52b3fe09..dab0b59d 100644 --- a/src-tauri/src/metadata.rs +++ b/src-tauri/src/metadata.rs @@ -108,6 +108,7 @@ pub struct Song { pub title: String, pub artist: String, pub album: String, + album_id: Option, album_artist: Option, compilation: i32, year: i32, @@ -789,6 +790,8 @@ fn process_directory( app, ) { // info!("Album: {:?}", album); + song.album_id = Some(album.id.clone()); + let existing_album = albums.lock().unwrap().get_mut(&album.id).cloned(); let existing_album_subalbums = @@ -1114,6 +1117,7 @@ pub fn extract_metadata( title, artist, album, + album_id: None, album_artist, compilation, year, diff --git a/src/App.d.ts b/src/App.d.ts index ec228cdd..519a84b1 100644 --- a/src/App.d.ts +++ b/src/App.d.ts @@ -47,6 +47,7 @@ interface Song { title: string; artist: string; album: string; + albumId?: string; albumArtist?: string; year: number; genre: string[]; @@ -176,6 +177,7 @@ interface UserSettings { followSystemOutput: boolean; geniusApiKey?: string; discogsApiKey?: string; + preferredView: "album" | "track"; } interface UIPreferences { @@ -205,7 +207,8 @@ type Comparison = type UiView = | "library" | "favourites" - | "smart-query" + | "smart-query:icon" + | "smart-query:list" | "your-music" | "albums" | "playlists" diff --git a/src/App.svelte b/src/App.svelte index e1570e8a..cbe185c7 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -14,6 +14,7 @@ isLyricsOpen, isMiniPlayer, isQueueOpen, + isQueueShowing, isSidebarFloating, isSidebarOpen, isSidebarShowing, @@ -251,6 +252,7 @@ } $: { + $isQueueShowing = showQueue; $isSidebarShowing = showSidebar; if (showSidebar) { @@ -388,29 +390,31 @@ {/if} -
- {#if $uiView === "playlists"} -
- {#if $selectedPlaylistFile} - - {/if} -
- {:else if $uiView === "to-delete"} -
- -
- {:else if $uiView === "smart-query" || $uiView === "favourites"} -
- {#await selectedQuery then query} - - {/await} -
- {:else if $uiView === "albums"} -
- -
- {/if} -
+ {#if showMainPanel} +
+ {#if $uiView === "playlists"} +
+ {#if $selectedPlaylistFile} + + {/if} +
+ {:else if $uiView === "to-delete"} +
+ +
+ {:else if $uiView.startsWith("smart-query") || $uiView === "favourites"} +
+ {#await selectedQuery then query} + + {/await} +
+ {:else if $uiView === "albums"} +
+ +
+ {/if} +
+ {/if}
{#if $isTagCloudOpen} @@ -424,7 +428,7 @@ >
- {:else if $uiView.match(/^(smart-query)/)} + {:else if $uiView.startsWith("smart-query")} {#if $isSmartQueryBuilderOpen}
- {#if $uiView === "library" || $uiView.match(/^(smart-query|favourites|to-delete)/)} + {#if $uiView === "library" || $uiView === "favourites" || $uiView === "playlists" || $uiView === "smart-query:list" || $uiView === "to-delete"} - {:else if $uiView === "playlists" || $uiView === "to-delete"} - - {:else if $uiView === "albums"} + {:else if $uiView === "albums" || $uiView === "smart-query:icon"} {:else if $uiView === "your-music"} diff --git a/src/data/SmartQueries.ts b/src/data/SmartQueries.ts index b9e64643..93521b25 100644 --- a/src/data/SmartQueries.ts +++ b/src/data/SmartQueries.ts @@ -44,12 +44,12 @@ async function jazzFromThe50s() { .and((song) => song.year < 1960 && song.year > 1949); } -async function favourites() { - return db.songs.filter((s) => s.isFavourite); +async function favourites(): Promise { + return db.songs.filter((s) => s.isFavourite).toArray(); } -async function recentlyAdded() { - return db.songs.orderBy("dateAdded").reverse(); +async function recentlyAdded(): Promise { + return db.songs.orderBy("dateAdded").reverse().toArray(); } async function whereGenreIs(genre: string) { diff --git a/src/data/store.ts b/src/data/store.ts index 01c0ac49..3e4f1c52 100644 --- a/src/data/store.ts +++ b/src/data/store.ts @@ -253,6 +253,7 @@ const defaultSettings: UserSettings = { theme: "dark", outputDevice: null, // default system device, followSystemOutput: true, + preferredView: "track", }; /** @@ -345,6 +346,7 @@ export const currentSongLyrics: Writable = writable(null); // Queue export const isQueueOpen = persistentWritable(false, "isQueueOpen"); export const isQueueCleared = writable(false); +export const isQueueShowing = writable(false); // Wiki export const isWikiOpen = writable(false); @@ -400,11 +402,17 @@ async function init() { ); } - // Get user settings - userSettings.set({ + const settings = { ...defaultSettings, ...fileSettings, - }); + }; + + if (settings.preferredView === "album") { + uiView.set("albums"); + } + + // Get user settings + userSettings.set(settings); // Auto-persist settings userSettings.subscribe(async (val) => { diff --git a/src/lib/albums/AlbumOptions.svelte b/src/lib/albums/AlbumOptions.svelte new file mode 100644 index 00000000..628588f6 --- /dev/null +++ b/src/lib/albums/AlbumOptions.svelte @@ -0,0 +1,279 @@ + + +
+ {#if showAllOptions} +
+ + { + $uiPreferences.albumsViewSortBy = v; + }} + /> +
+ +
+ { + $uiPreferences.albumsViewShowSingles = !showSingles; + }} + /> +
+
+ { + $uiPreferences.albumsViewShowInfo = !showInfo; + }} + /> +
+ + + {:else} + + {/if} +
+ +{#if showOptionsPanel} +
+ +
+ + { + $uiPreferences.albumsViewSortBy = v; + }} + /> +
+ +
+
+ { + $uiPreferences.albumsViewShowSingles = !showSingles; + }} + /> +
+
+ { + $uiPreferences.albumsViewShowInfo = !showInfo; + }} + /> +
+
+ + +
+
+{/if} + + diff --git a/src/lib/albums/AlbumsHeader.svelte b/src/lib/albums/AlbumsHeader.svelte index 785c8e7f..b9255f68 100644 --- a/src/lib/albums/AlbumsHeader.svelte +++ b/src/lib/albums/AlbumsHeader.svelte @@ -1,47 +1,13 @@
@@ -72,149 +29,7 @@ > {$LL.albums.title()} -
- {#if showAllOptions} -
- - { - $uiPreferences.albumsViewSortBy = v; - }} - /> -
- -
- { - $uiPreferences.albumsViewShowSingles = !showSingles; - }} - /> -
-
- { - $uiPreferences.albumsViewShowInfo = !showInfo; - }} - /> -
- - - {:else} - - {/if} -
- - {#if showOptionsPanel} -
- -
- - { - $uiPreferences.albumsViewSortBy = v; - }} - /> -
- -
-
- { - $uiPreferences.albumsViewShowSingles = - !showSingles; - }} - /> -
-
- { - $uiPreferences.albumsViewShowInfo = !showInfo; - }} - /> -
-
- - -
-
- {/if} +
diff --git a/src/lib/library/BottomBar.svelte b/src/lib/library/BottomBar.svelte index 9ebb28dd..b3eceb70 100644 --- a/src/lib/library/BottomBar.svelte +++ b/src/lib/library/BottomBar.svelte @@ -22,6 +22,7 @@ import { isIAPlaying } from "../player/WebAudioPlayer"; import CompressionSelector from "../ui/CompressionSelector.svelte"; import Icon from "../ui/Icon.svelte"; + import ButtonWithIcon from "../ui/ButtonWithIcon.svelte"; let right; let nextUp; @@ -64,6 +65,13 @@ showVisualiser = window.innerWidth > 900 && diff > 150; } } + + function showIconView() { + $uiView = "smart-query:icon"; + } + function showListView() { + $uiView = "smart-query:list"; + } @@ -112,6 +120,26 @@
+ {#if $uiView.startsWith("smart-query")} +
+ +
+ {/if}
@@ -303,6 +331,42 @@ } } + .center { + @media only screen and (min-width: 1800px) { + position: fixed; + left: 50%; + transform: translate(-50%, 0%); + } + + nav { + display: flex; + align-items: center; + align-self: normal; + text-align: start; + justify-content: flex-start; + white-space: nowrap; + gap: 0; + + :global { + > div:nth-child(1) { + border-radius: 0; + border-start-start-radius: 6px !important; + border-end-start-radius: 6px !important; + } + > div:nth-last-child(1) { + border-radius: 0; + border-start-end-radius: 6px !important; + border-end-end-radius: 6px !important; + } + + :focus, + :focus-visible { + outline: 0; + } + } + } + } + .right { display: flex; flex-direction: row; diff --git a/src/lib/library/CanvasLibrary.svelte b/src/lib/library/CanvasLibrary.svelte index 825771fd..bf0c95ab 100644 --- a/src/lib/library/CanvasLibrary.svelte +++ b/src/lib/library/CanvasLibrary.svelte @@ -180,7 +180,7 @@ firstSongInPreviousAlbum: null, firstSongInPreviousArtist: null, }, - songs: [], + songs: [] as Song[], }, )?.songs ?? []; @@ -972,7 +972,7 @@ const tracks = await db.songs.bulkGet(album.tracksIds); setQueue(tracks, song.viewModel.index); - } else if ($uiView === "smart-query") { + } else if ($uiView === "smart-query:list") { setQueue($smartQueryResults, song.viewModel.index); } else { setQueue($queriedSongs, song.viewModel.index); @@ -1635,7 +1635,7 @@ $smartQuery.parts = $smartQuery.parts; $isSmartQueryBuilderOpen = true; $isSmartQuerySaveUiOpen = false; - $uiView = "smart-query"; + $uiView = "smart-query:list"; } function isInvalidValue(value) { diff --git a/src/lib/library/SmartPlaylistHeader.svelte b/src/lib/library/SmartPlaylistHeader.svelte index 7bb4b341..64612752 100644 --- a/src/lib/library/SmartPlaylistHeader.svelte +++ b/src/lib/library/SmartPlaylistHeader.svelte @@ -1,12 +1,15 @@ -{#if $isSmartQueryBuilderOpen} -
- -
-{:else} -

- -  {selectedQuery?.name} -

-{/if} -{#if durationText} -

{durationText}

-{/if} -
-
-

- +
+
+ {#if !$isSmartQueryBuilderOpen} + + {#if $selectedSmartQuery?.startsWith("~usq:")} + + {/if} + {:else} + + + {/if} +
+
+ {#if $isSmartQueryBuilderOpen} +
+ +
+ {:else} +

+ +  {selectedQuery?.name} +

+ {/if} + {#if durationText} +

{durationText}

+ {/if} +
+
+

+ +
+
+
+ {#if $uiView === "smart-query:icon"} + + {/if} +
-{#if !$isSmartQueryBuilderOpen} - {#if $selectedSmartQuery?.startsWith("~usq:")} - - {/if} - -{:else} - - -{/if} diff --git a/src/lib/ui/Icon.svelte b/src/lib/ui/Icon.svelte index 1d4dbaea..cca51ba0 100644 --- a/src/lib/ui/Icon.svelte +++ b/src/lib/ui/Icon.svelte @@ -238,6 +238,9 @@ "material-symbols:grid-view-rounded": { svg: ``, }, + "material-symbols:view-list-rounded": { + svg: ``, + }, "mdi:information-off": { svg: ``, }, diff --git a/src/lib/views/AlbumsView.svelte b/src/lib/views/AlbumsView.svelte index c87a61ca..574821d1 100644 --- a/src/lib/views/AlbumsView.svelte +++ b/src/lib/views/AlbumsView.svelte @@ -7,9 +7,11 @@ import { compressionSelected, current, + importStatus, isPlaying, query, queue, + selectedSmartQuery, uiPreferences, uiView, } from "../../data/store"; @@ -23,6 +25,9 @@ import { debounce } from "lodash-es"; import { getAlbumDetailsHeight } from "../albums/util"; import ScrollTo from "../ui/ScrollTo.svelte"; + import SmartQuery from "../smart-query/Query"; + import BuiltInQueries from "../../data/SmartQueries"; + import ImportPlaceholder from "../library/ImportPlaceholder.svelte"; const PADDING = 14; @@ -40,7 +45,6 @@ let highlightedAlbum; let isCurrentAlbumInView = false; let isInit = true; - let isLoading = true; let itemSizes = []; let lastOffset = 0; let rowCount = 0; @@ -48,18 +52,52 @@ let virtualList; $: albums = liveQuery(async () => { - let albums = await db.albums.toArray(); + let albums: Album[]; - if ($compressionSelected === "lossless") { - albums = albums.filter( - ({ title, lossless }) => title.length && lossless, - ); - } else if ($compressionSelected === "lossy") { - albums = albums.filter( - ({ title, lossless }) => title.length && !lossless, - ); + if ($uiView === "smart-query:icon") { + let songs: Song[]; + + if ($selectedSmartQuery.startsWith("~usq:")) { + // Run the query from the user-built blocks + const query = await SmartQuery.loadWithUQI($selectedSmartQuery); + songs = await query.run(); + } else { + // Run the query from built-in functions + songs = await BuiltInQueries[$selectedSmartQuery].run(); + } + + const byAlbumIds = {}; + + for (const song of songs) { + if (song.albumId) { + if (byAlbumIds[song.albumId]) { + byAlbumIds[song.albumId].push(song); + } else { + byAlbumIds[song.albumId] = [song]; + } + } + } + + albums = await db.albums.bulkGet(Object.keys(byAlbumIds)); + + albums = albums.map((album) => ({ + ...album, + tracksIds: byAlbumIds[album.id].map(({ id }) => id), + })); } else { - albums = albums.filter(({ title }) => title.length); + albums = await db.albums.toArray(); + + if ($compressionSelected === "lossless") { + albums = albums.filter( + ({ title, lossless }) => title.length && lossless, + ); + } else if ($compressionSelected === "lossy") { + albums = albums.filter( + ({ title, lossless }) => title.length && !lossless, + ); + } else { + albums = albums.filter(({ title }) => title.length); + } } if ($uiPreferences.albumsViewSortBy === "title") { @@ -86,8 +124,6 @@ }); } - isLoading = false; - return albums; }); @@ -166,7 +202,7 @@ updatePlayingAlbumOffset(); - return true; + return !!virtualList; } function updatePlayingAlbumOffset() { @@ -262,6 +298,10 @@ } async function onRightClick(e, album) { + if (e.button !== 2) { + return; + } + highlightedAlbum = album.id; const songs = (await db.songs.bulkGet(album.tracksIds)) @@ -356,13 +396,15 @@ />
- {#if isLoading} + {#if !$albums} + {:else if ($importStatus.isImporting && $importStatus.backgroundImport === false) || ($albums.length === 0 && $query.query.length === 0 && !/^(smart-query|favourites|to-delete)/.test($uiView))} + {:else}