Skip to content

Commit

Permalink
Add basic artist view (no details yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
martpie committed Jan 2, 2025
1 parent ef2466f commit cebeaf1
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 3 deletions.
1 change: 1 addition & 0 deletions src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn main() {
"remove_tracks",
"get_tracks",
"update_track",
"get_artists",
"get_all_playlists",
"get_playlist",
"create_playlist",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/capabilities/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 29 additions & 1 deletion src-tauri/src/libs/database.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<Vec<String>> {
let query = format!(
"SELECT DISTINCT JSON_EXTRACT({}, '$[0]') FROM {};",
"artists",
Track::table_name()
);
info!("query for all artists: {}", query);

let mut result: Vec<String> = 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<Vec<Playlist>> {
let timer = TimeLogger::new("Retrieved and decoded playlists".into());
Expand Down
6 changes: 6 additions & 0 deletions src-tauri/src/plugins/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ async fn remove_tracks(db_state: State<'_, DBState>, ids: Vec<String>) -> AnyRes
db_state.get_lock().await.remove_tracks(&ids).await
}

#[tauri::command]
async fn get_artists(db_state: State<'_, DBState>) -> AnyResult<Vec<String>> {
db_state.get_lock().await.get_artists().await
}

#[tauri::command]
async fn get_all_playlists(db_state: State<'_, DBState>) -> AnyResult<Vec<Playlist>> {
db_state.get_lock().await.get_all_playlists().await
Expand Down Expand Up @@ -426,6 +431,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
get_tracks,
remove_tracks,
update_track,
get_artists,
get_all_playlists,
get_playlist,
get_playlist,
Expand Down
12 changes: 12 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ export default function Footer() {
>
<Icon name="align-justify" fixedWidth />
</NavLink>
<NavLink
to="/artists"
className={({ isActive }) =>
`${styles.footerNavigationLink} ${
isActive && styles.footerNavigationLinkIsActive
}`
}
title="Artists"
draggable={false}
>
<Icon name="microphone" fixedWidth />
</NavLink>
<NavLink
to="/playlists"
className={({ isActive }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/components/SideNavLink.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.sideNavLink {
display: block;
flex-shrink: 0;
width: 100%;
line-height: 1;
font-size: 1rem;
Expand Down
6 changes: 5 additions & 1 deletion src/lib/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Array<Track>> {
Expand Down Expand Up @@ -38,6 +38,10 @@ const database = {
});
},

async getAllArtists(): Promise<Array<string>> {
return invoke('plugin:database|get_artists');
},

// ---------------------------------------------------------------------------
// Playlists read/write actions
// ---------------------------------------------------------------------------
Expand Down
16 changes: 16 additions & 0 deletions src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,6 +29,20 @@ const routeTree: RouteObject[] = [
Component: ViewLibrary,
loader: ViewLibrary.loader,
},
{
path: 'artists',
id: 'artists',
element: <ViewArtists />,
loader: ViewArtists.loader,
children: [
{
path: ':artist',
id: 'artist-details',
element: <ViewArtistDetails />,
loader: ViewArtistDetails.loader,
},
],
},
{
path: 'playlists',
id: 'playlists',
Expand Down
20 changes: 20 additions & 0 deletions src/routes/artist-details.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>{params.artist}</div>;
}

export type ArtistDetailsLoaderData = LoaderData<
typeof ViewArtistDetails.loader
>;

ViewArtistDetails.loader = async ({ params }: LoaderFunctionArgs) => {
if (typeof params.artist !== 'string') {
throw new Error('Invalid artist');
}

return {};
};
44 changes: 44 additions & 0 deletions src/routes/artists.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View
sideNav={
<SideNav title="Artists">
{artists.map((artist) => (
<SideNavLink
key={artist}
id={artist}
label={artist}
href={`/artists/${artist}`}
/>
))}
</SideNav>
}
>
{outlet ?? (
<ViewMessage.Notice>
<p>No artist selected</p>
</ViewMessage.Notice>
)}
</View>
);
}

export type ArtistsLoaderData = LoaderData<typeof ViewArtists.loader>;

ViewArtists.loader = async () => {
const artists = await database.getAllArtists();

return { artists };
};

0 comments on commit cebeaf1

Please sign in to comment.