added album search feature for upload

This commit is contained in:
Danny Zou 2024-05-16 18:59:16 -04:00
parent 955fc84412
commit caefc46fcc
3 changed files with 74 additions and 20 deletions

View File

@ -3,7 +3,9 @@ use leptos::*;
use leptos_icons::*; use leptos_icons::*;
use leptos_router::Form; use leptos_router::Form;
use crate::search::search_artists; use crate::search::search_artists;
use crate::search::search_albums;
use crate::models::Artist; use crate::models::Artist;
use crate::models::Album;
#[component] #[component]
pub fn UploadBtn(dialog_open: RwSignal<bool>) -> impl IntoView { pub fn UploadBtn(dialog_open: RwSignal<bool>) -> impl IntoView {
@ -27,12 +29,16 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
let (artists, set_artists) = create_signal("".to_string()); let (artists, set_artists) = create_signal("".to_string());
let (filtered_artists, set_filtered_artists) = create_signal(vec![]); let (filtered_artists, set_filtered_artists) = create_signal(vec![]);
let (albums, set_albums) = create_signal("".to_string());
let (filtered_albums, set_filtered_albums) = create_signal(vec![]);
let close_dialog = move |ev: leptos::ev::MouseEvent| { let close_dialog = move |ev: leptos::ev::MouseEvent| {
ev.prevent_default(); ev.prevent_default();
open.set(false); open.set(false);
}; };
// Create a filter function to handle filtering artists // Create a filter function to handle filtering artists
let handle_filter = move |ev: leptos::ev::Event| { // Allow users to search for artists by name, converts the artist name to artist id to be handed off to backend
let handle_filter_artists = move |ev: leptos::ev::Event| {
ev.prevent_default(); ev.prevent_default();
let artist_input: String = event_target_value(&ev); let artist_input: String = event_target_value(&ev);
@ -55,6 +61,26 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
} }
}) })
}; };
// Create a filter function to handle filtering albums
// Allow users to search for albums by title, converts the album title to album id to be handed off to backend
let handle_filter_albums = move |ev: leptos::ev::Event| {
ev.prevent_default();
let album_input: String = event_target_value(&ev);
//Update the album signal with the input
set_albums.update(|value: &mut String| *value = album_input);
spawn_local(async move {
let filter_results = search_albums(albums.get_untracked(), 3).await;
if let Err(err) = filter_results {
log!("Error filtering albums: {:?}", err);
} else if let Ok(albums) = filter_results {
log!("Filtered albums: {:?}", albums);
set_filtered_albums.update(|value| *value = albums);
}
})
};
view! { view! {
<Show when=open fallback=move || view! {}> <Show when=open fallback=move || view! {}>
<div class="upload-container" open=open> <div class="upload-container" open=open>
@ -67,16 +93,16 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
<input type="text" name="title" required class="text-input" required/> <input type="text" name="title" required class="text-input" required/>
<span>Title</span> <span>Title</span>
</div> </div>
<div class="artists"> <div class="artists has-search">
<div class="input-bx"> <div class="input-bx">
<input type="text" name="artist_ids" class="text-input" prop:value=artists required on:input=handle_filter/> <input type="text" name="artist_ids" class="text-input" prop:value=artists required on:input=handle_filter_artists/>
<span>Artists</span> <span>Artists</span>
</div> </div>
<Show <Show
when=move || {filtered_artists.get().len() > 0} when=move || {filtered_artists.get().len() > 0}
fallback=move || view! {} fallback=move || view! {}
> >
<ul class="artist_results"> <ul class="artist_results search-results">
{ {
move || filtered_artists.get().iter().enumerate().map(|(_index,filtered_artist)| view! { move || filtered_artists.get().iter().enumerate().map(|(_index,filtered_artist)| view! {
<Artist artist=filtered_artist.clone() artists=artists set_artists=set_artists set_filtered=set_filtered_artists/> <Artist artist=filtered_artist.clone() artists=artists set_artists=set_artists set_filtered=set_filtered_artists/>
@ -85,10 +111,25 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
</ul> </ul>
</Show> </Show>
</div> </div>
<div class="albums has-search">
<div class="input-bx"> <div class="input-bx">
<input type="text" name="album_id" class="text-input" required/> <input type="text" name="album_id" class="text-input" required prop:value=albums on:input=handle_filter_albums/>
<span>Album ID</span> <span>Album ID</span>
</div> </div>
<Show
when=move || {filtered_albums.get().len() > 0}
fallback=move || view! {}
>
<ul class="album_results search-results">
{
move || filtered_albums.get().iter().enumerate().map(|(_index,filtered_album)| view! {
<Album album=filtered_album.clone() _albums=albums set_albums=set_albums set_filtered=set_filtered_albums/>
}).collect::<Vec<_>>()
}
</ul>
</Show>
</div>
<div class="input-bx"> <div class="input-bx">
<input type="number" name="track_number" class="text-input" required/> <input type="number" name="track_number" class="text-input" required/>
<span>Track Number</span> <span>Track Number</span>
@ -113,12 +154,11 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
#[component] #[component]
pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSignal<String>, set_filtered: WriteSignal<Vec<Artist>>) -> impl IntoView { pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSignal<String>, set_filtered: WriteSignal<Vec<Artist>>) -> impl IntoView {
// Converts artist name to artist id and adds it to the artist input
// Create a function to add an artist to the artist input
let add_artist = move |_| { let add_artist = move |_| {
//Create an empty string to hold the artist ids //Create an empty string to hold previous artist ids
let mut s: String = String::from(""); let mut s: String = String::from("");
//Get the current value of the artist input //Get the current artist input
let all_artirts: String = artists.get(); let all_artirts: String = artists.get();
//Split the input into a vector of artists separated by commas //Split the input into a vector of artists separated by commas
let mut ids: Vec<&str> = all_artirts.split(",").collect(); let mut ids: Vec<&str> = all_artirts.split(",").collect();
@ -152,8 +192,25 @@ pub fn Artist(artist: Artist, artists: ReadSignal<String>, set_artists: WriteSig
}; };
view! { view! {
<div class="artist" on:click=add_artist> <div class="artist result" on:click=add_artist>
{artist.name.clone()} {artist.name.clone()}
</div> </div>
} }
} }
#[component]
pub fn Album(album: Album, _albums: ReadSignal<String>, set_albums: WriteSignal<String>, set_filtered: WriteSignal<Vec<Album>>) -> impl IntoView {
//Converts album title to album id to upload a song
let add_album = move |_| {
let value_str = match album.id.clone() {
Some(v) => v.to_string(),
None => String::from("None"),
};
set_albums.update(|value| *value = value_str);
set_filtered.update(|value| *value = vec![]);
};
view! {
<div class="album result" on:click=add_album>
{album.title.clone()}
</div>
}
}

View File

@ -168,7 +168,7 @@ impl Artist {
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable, Identifiable))] #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::albums))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::albums))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Album { pub struct Album {
/// A unique id for the album /// A unique id for the album
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]

View File

@ -141,13 +141,10 @@
color: #7f8fa6; color: #7f8fa6;
} }
} }
.artists { .has-search {
position: relative; position: relative;
width: 325px; width: 325px;
.input-bx { .search-results {
}
.artist_results {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 1px solid white; border: 1px solid white;
@ -158,7 +155,7 @@
z-index: 2; z-index: 2;
border-radius: 5px; border-radius: 5px;
padding: 0; padding: 0;
.artist { .result {
border-bottom: 1px solid white; border-bottom: 1px solid white;
padding: 10px; padding: 10px;
cursor: pointer; cursor: pointer;
@ -167,7 +164,7 @@
background-color: #7f8fa6; background-color: #7f8fa6;
} }
} }
.artist:last-child { .result:last-child {
border-bottom: none; border-bottom: none;
} }
} }