From cebeaf1553c0d13a21800106808afb406a2fe5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20de=20la=20Martini=C3=A8re?= Date: Tue, 3 Dec 2024 15:58:11 +0100 Subject: [PATCH] Add basic artist view (no details yet) --- src-tauri/build.rs | 1 + src-tauri/capabilities/main.json | 1 + src-tauri/src/libs/database.rs | 30 +++++++++++++++++- src-tauri/src/plugins/db.rs | 6 ++++ src/components/Footer.tsx | 12 ++++++++ src/components/SideNav.tsx | 2 +- src/components/SideNavLink.module.css | 1 + src/lib/database.ts | 6 +++- src/routes.tsx | 16 ++++++++++ src/routes/artist-details.tsx | 20 ++++++++++++ src/routes/artists.tsx | 44 +++++++++++++++++++++++++++ 11 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 src/routes/artist-details.tsx create mode 100644 src/routes/artists.tsx diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 6f7ee29d5..18d1c857f 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -27,6 +27,7 @@ fn main() { "remove_tracks", "get_tracks", "update_track", + "get_artists", "get_all_playlists", "get_playlist", "create_playlist", diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 5073e9c2f..380ecdfce 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -34,6 +34,7 @@ "database:allow-get-tracks", "database:allow-update-track", "database:allow-remove-tracks", + "database:allow-get-artists", "database:allow-get-all-playlists", "database:allow-get-playlist", "database:allow-create-playlist", diff --git a/src-tauri/src/libs/database.rs b/src-tauri/src/libs/database.rs index 776b096e3..99b8ced18 100644 --- a/src-tauri/src/libs/database.rs +++ b/src-tauri/src/libs/database.rs @@ -1,6 +1,7 @@ +use log::info; use ormlite::model::ModelBuilder; use ormlite::sqlite::SqliteConnection; -use ormlite::Model; +use ormlite::{Model, TableMeta}; use std::collections::HashMap; use std::path::PathBuf; @@ -145,6 +146,33 @@ impl DB { Ok(()) } + /** + * Insert a new track in the DB, will fail in case there is a duplicate unique + * key (like track.path) + * + * Doc: https://github.com/khonsulabs/bonsaidb/blob/main/examples/basic-local/examples/basic-local-multidb.rs + */ + pub async fn get_artists(&mut self) -> AnyResult> { + let query = format!( + "SELECT DISTINCT JSON_EXTRACT({}, '$[0]') FROM {};", + "artists", + Track::table_name() + ); + info!("query for all artists: {}", query); + + let mut result: Vec = ormlite::query_as(&query) + .fetch_all(&mut self.connection) + .await? + .into_iter() + .map(|row: (String,)| row.0) + .collect(); + + // sort them alphabetically + result.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); + + Ok(result) + } + /** Get all the playlists (and their content) from the database */ pub async fn get_all_playlists(&mut self) -> AnyResult> { let timer = TimeLogger::new("Retrieved and decoded playlists".into()); diff --git a/src-tauri/src/plugins/db.rs b/src-tauri/src/plugins/db.rs index 2d6bf6ab3..8d357e8ac 100644 --- a/src-tauri/src/plugins/db.rs +++ b/src-tauri/src/plugins/db.rs @@ -290,6 +290,11 @@ async fn remove_tracks(db_state: State<'_, DBState>, ids: Vec) -> AnyRes db_state.get_lock().await.remove_tracks(&ids).await } +#[tauri::command] +async fn get_artists(db_state: State<'_, DBState>) -> AnyResult> { + db_state.get_lock().await.get_artists().await +} + #[tauri::command] async fn get_all_playlists(db_state: State<'_, DBState>) -> AnyResult> { db_state.get_lock().await.get_all_playlists().await @@ -426,6 +431,7 @@ pub fn init() -> TauriPlugin { get_tracks, remove_tracks, update_track, + get_artists, get_all_playlists, get_playlist, get_playlist, diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 798b58783..1b11fe83b 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -57,6 +57,18 @@ export default function Footer() { > + + `${styles.footerNavigationLink} ${ + isActive && styles.footerNavigationLinkIsActive + }` + } + title="Artists" + draggable={false} + > + + diff --git a/src/components/SideNav.tsx b/src/components/SideNav.tsx index ca956e467..b0fae0385 100644 --- a/src/components/SideNav.tsx +++ b/src/components/SideNav.tsx @@ -6,7 +6,7 @@ import styles from './SideNav.module.css'; type Props = { children: React.ReactNode; title: string; - actions: React.ReactNode; + actions?: React.ReactNode; }; export default function SideNav(props: Props) { diff --git a/src/components/SideNavLink.module.css b/src/components/SideNavLink.module.css index d1997b802..2877ca3eb 100644 --- a/src/components/SideNavLink.module.css +++ b/src/components/SideNavLink.module.css @@ -1,5 +1,6 @@ .sideNavLink { display: block; + flex-shrink: 0; width: 100%; line-height: 1; font-size: 1rem; diff --git a/src/lib/database.ts b/src/lib/database.ts index c8bfffcc3..2fe7e113c 100644 --- a/src/lib/database.ts +++ b/src/lib/database.ts @@ -7,7 +7,7 @@ import type { Playlist, ScanResult, Track } from '../generated/typings'; */ const database = { // --------------------------------------------------------------------------- - // Playlists read/write actions + // Library read/write actions // --------------------------------------------------------------------------- async getAllTracks(): Promise> { @@ -38,6 +38,10 @@ const database = { }); }, + async getAllArtists(): Promise> { + return invoke('plugin:database|get_artists'); + }, + // --------------------------------------------------------------------------- // Playlists read/write actions // --------------------------------------------------------------------------- diff --git a/src/routes.tsx b/src/routes.tsx index 00c39e39f..8d9edfd87 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -2,6 +2,8 @@ import { type RouteObject, createHashRouter } from 'react-router'; import GlobalErrorBoundary from './components/GlobalErrorBoundary'; import RootView from './routes/Root'; +import ViewArtistDetails from './routes/artist-details'; +import ViewArtists from './routes/artists'; import ViewLibrary from './routes/library'; import ViewPlaylistDetails from './routes/playlist-details'; import ViewPlaylists from './routes/playlists'; @@ -27,6 +29,20 @@ const routeTree: RouteObject[] = [ Component: ViewLibrary, loader: ViewLibrary.loader, }, + { + path: 'artists', + id: 'artists', + element: , + loader: ViewArtists.loader, + children: [ + { + path: ':artist', + id: 'artist-details', + element: , + loader: ViewArtistDetails.loader, + }, + ], + }, { path: 'playlists', id: 'playlists', diff --git a/src/routes/artist-details.tsx b/src/routes/artist-details.tsx new file mode 100644 index 000000000..ee31ad0dc --- /dev/null +++ b/src/routes/artist-details.tsx @@ -0,0 +1,20 @@ +import { type LoaderFunctionArgs, useParams } from 'react-router-dom'; +import type { LoaderData } from '../types/museeks'; + +export default function ViewArtistDetails() { + // const { /** */ } = useLoaderData() as ArtistDetailsLoaderData; + const params = useParams(); + return
{params.artist}
; +} + +export type ArtistDetailsLoaderData = LoaderData< + typeof ViewArtistDetails.loader +>; + +ViewArtistDetails.loader = async ({ params }: LoaderFunctionArgs) => { + if (typeof params.artist !== 'string') { + throw new Error('Invalid artist'); + } + + return {}; +}; diff --git a/src/routes/artists.tsx b/src/routes/artists.tsx new file mode 100644 index 000000000..5826f0b8a --- /dev/null +++ b/src/routes/artists.tsx @@ -0,0 +1,44 @@ +import { useLoaderData, useOutlet } from 'react-router-dom'; + +import SideNav from '../components/SideNav'; +import SideNavLink from '../components/SideNavLink'; +import View from '../elements/View'; +import * as ViewMessage from '../elements/ViewMessage'; +import database from '../lib/database'; +import type { LoaderData } from '../types/museeks'; + +export default function ViewArtists() { + const { artists } = useLoaderData() as ArtistsLoaderData; + const outlet = useOutlet(); + + return ( + + {artists.map((artist) => ( + + ))} + + } + > + {outlet ?? ( + +

No artist selected

+
+ )} +
+ ); +} + +export type ArtistsLoaderData = LoaderData; + +ViewArtists.loader = async () => { + const artists = await database.getAllArtists(); + + return { artists }; +};