From f56e13eaa92b2ce31ab7262f7d2e3edf8fe65cdb Mon Sep 17 00:00:00 2001 From: dannyzou18 Date: Thu, 18 Apr 2024 19:44:12 -0400 Subject: [PATCH] added a playlist component and a song component --- src/api/mod.rs | 3 +- src/api/playlists.rs | 57 +++++++++++++++++- src/api/songs.rs | 37 ++++++++++++ src/components/playlist.rs | 115 +++++++++++++++++++++++++++++++++++-- src/models.rs | 4 +- style/sidebar.scss | 81 ++++++++++++++++++++++++-- 6 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 src/api/songs.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 4958949..909022d 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1 +1,2 @@ -pub mod playlists; \ No newline at end of file +pub mod playlists; +pub mod songs; \ No newline at end of file diff --git a/src/api/playlists.rs b/src/api/playlists.rs index ac770e2..3d18cd8 100644 --- a/src/api/playlists.rs +++ b/src/api/playlists.rs @@ -98,4 +98,59 @@ pub async fn add_song(new_playlist_id: Option, new_song_id: Option) -> .map_err(|e| ServerFnError::::ServerError(format!("Error adding song to playlist: {}", e)))?; Ok(()) -} \ No newline at end of file +} +/// Get songs from a playlist +/// +/// # Arguments +/// +/// * `playlist_id` - The id of the playlist +/// +/// # Returns +/// +/// * `Result, ServerFnError>` - A vector of songs if successful, or an error +/// +#[server(endpoint = "playlists/get-songs")] +pub async fn get_songs(new_playlist_id: Option) -> Result, ServerFnError> { + use crate::schema::playlist_songs::dsl::*; + use crate::schema::songs::dsl::*; + use leptos::server_fn::error::NoCustomError; + + let other_playlist_id = new_playlist_id.ok_or(ServerFnError::::ServerError("Playlist id must be present (Some) to get songs".to_string()))?; + + let db_con = &mut get_db_conn(); + let results = playlist_songs + .inner_join(songs) + .filter(playlist_id.eq(other_playlist_id)) + .select(songs::all_columns()) + .load(db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting songs from playlist: {}", e)))?; + + Ok(results) +} +/// Remove a song from a playlist +/// +/// # Arguments +/// +/// * `playlist_id` - The id of the playlist +/// * `song_id` - The id of the song +/// +/// # Returns +/// +/// * `Result<(), ServerFnError>` - An empty result if successful, or an error +/// +#[server(endpoint = "playlists/remove-song")] +pub async fn remove_song(new_playlist_id: Option, new_song_id: Option) -> Result<(), ServerFnError> { + use crate::schema::playlist_songs::dsl::*; + use leptos::server_fn::error::NoCustomError; + + let other_playlist_id = new_playlist_id.ok_or(ServerFnError::::ServerError("Playlist id must be present (Some) to remove song".to_string()))?; + + let other_song_id = new_song_id.ok_or(ServerFnError::::ServerError("Song id must be present (Some) to remove song".to_string()))?; + + let db_con = &mut get_db_conn(); + diesel::delete(playlist_songs.filter(playlist_id.eq(other_playlist_id).and(song_id.eq(other_song_id)))) + .execute(db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error removing song from playlist: {}", e)))?; + + Ok(()) +} \ No newline at end of file diff --git a/src/api/songs.rs b/src/api/songs.rs new file mode 100644 index 0000000..e350e7b --- /dev/null +++ b/src/api/songs.rs @@ -0,0 +1,37 @@ +use leptos::*; +use crate::models::Artist; + + +use cfg_if::cfg_if; + +cfg_if! { +if #[cfg(feature = "ssr")] { + use crate::database::get_db_conn; + use diesel::prelude::*; +} +} + +/// Gets a Vector of Artists associated with a Song +/// +/// # Arguments +/// `song_id_arg` - The id of the Song to get the Artists for +/// +/// # Returns +/// A Result containing a Vector of Artists if the operation was successful, or an error if the operation failed +#[server(endpoint = "songs/get-artists")] +pub async fn get_artists(song_id_arg: Option) -> Result, ServerFnError> { + use crate::schema::artists::dsl::*; + use crate::schema::song_artists::dsl::*; + use leptos::server_fn::error::NoCustomError; + + let other_song_id = song_id_arg.ok_or(ServerFnError::::ServerError("Song id must be present (Some) to get artists".to_string()))?; + + let my_artists = artists + .inner_join(song_artists) + .filter(song_id.eq(other_song_id)) + .select(artists::all_columns()) + .load(&mut get_db_conn())?; + + Ok(my_artists) +} + diff --git a/src/components/playlist.rs b/src/components/playlist.rs index 9ae9c61..165ab44 100644 --- a/src/components/playlist.rs +++ b/src/components/playlist.rs @@ -1,6 +1,21 @@ use leptos::*; use leptos_icons::*; +use leptos::leptos_dom::*; use crate::models::Playlist; +use crate::models::Song; +use crate::api::playlists::get_songs; +use crate::api::songs::get_artists; + +fn convert_seconds(seconds: i32) -> String { + let minutes = seconds / 60; + let seconds = seconds % 60; + let seconds_string = if seconds < 10 { + format!("0{}", seconds) + } else { + format!("{}", seconds) + }; + format!("{}:{}", minutes, seconds_string) +} #[component] pub fn Playlist(playlist: Playlist) -> impl IntoView { @@ -9,17 +24,105 @@ pub fn Playlist(playlist: Playlist) -> impl IntoView { view! {

{playlist.name.clone()}

+
} > -
-
- -
-

{playlist.name.clone()}

-
+
+ + + } + > + } +} +#[component] +pub fn PlayListPopUp(playlist: Playlist, set_show_playlist: WriteSignal) -> impl IntoView { + let (songs, set_songs) = create_signal(vec![]); + + create_effect(move |_| { + spawn_local(async move { + let playlist_songs = get_songs(playlist.id.clone()).await; + if let Err(err) = playlist_songs { + // Handle the error here, e.g., log it or display to the user + log!("Error getting songs: {:?}", err); + } else { + log!("Songs: {:?}", playlist_songs); + log!("number of songs: {:?}", playlist_songs.clone().expect("REASON").len()); + set_songs.update(|value| *value = playlist_songs.unwrap()); + } + }) + }); + + view! { +
+
+ +
+
+

{playlist.name.clone()}

+

{move || songs.get().len()}

+
+ +
    + { + move || songs.get().iter().enumerate().map(|(index,song)| view! { + + }).collect::>() + } +
+
+ } +} +#[component] +pub fn PlaylistSong(song: Song) -> impl IntoView { + let (artists, set_artists) = create_signal("".to_string()); + let (is_hovered, set_is_hovered) = create_signal(false); + + + create_effect(move |_| { + spawn_local(async move { + let song_artists = get_artists(song.id.clone()).await; + if let Err(err) = song_artists { + // Handle the error here, e.g., log it or display to the user + log!("Error getting artist: {:?}", err); + } else { + log!("Artist: {:?}", song_artists); + let mut artist_string = "".to_string(); + let song_artists = song_artists.unwrap(); + for i in 0..song_artists.len() { + if i == song_artists.len() - 1 { + artist_string.push_str(&song_artists[i].name); + } else { + artist_string.push_str(&song_artists[i].name); + artist_string.push_str(" & "); + } + } + set_artists.update(|value| *value = artist_string); + } + }) + }); + + view! { +
+ {song.title.clone()} +
+

{song.title.clone()}

+

{move || artists.get()}

+
+
+ {convert_seconds(song.duration)}

} + > + +
+
+
+ } } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 04c15cf..00ecb7e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -47,7 +47,7 @@ pub struct User { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::artists))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Artist { /// A unique id for the artist #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] @@ -239,7 +239,7 @@ impl Album { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))] #[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Song { /// A unique id for the song #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] diff --git a/style/sidebar.scss b/style/sidebar.scss index 00367f4..72c04be 100644 --- a/style/sidebar.scss +++ b/style/sidebar.scss @@ -176,20 +176,40 @@ border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; + + .screen-darkener { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1; + } + .name { font-size: 1rem; margin-left: 1rem; margin-top: 0.5rem; color: white; } + + .playlist-container { - background-color: red; - width: 10rem; - height: 10rem; + background-color: #1c1c1c; + width: 40rem; + height: 40rem; position: fixed; - top: 40%; + top: 50%; left: 50%; transform: translate(-50%, -50%); + border: 1px solid white; + border-radius: 5px; + padding: 1rem; + padding-top: 0; + z-index: 2; + display: flex; + flex-direction: column; .close-button { position: absolute; @@ -212,6 +232,58 @@ .close-button:active { transform: scale(0.8); } + h1 { + font-size: 2rem; + font-weight: bolder; + margin-bottom: 0.5rem; + } + .info { + display:flex; + flex-direction: row; + } + .songs { + list-style: none; + padding: 0; + margin: 0; + .song { + display: flex; + align-items: center; + padding: 7px 10px 7px 10px; + border-bottom: 1px solid #ccc; /* Separator line color */ + + img { + max-width: 40px; /* Adjust maximum width for images */ + margin-right: 10px; /* Add spacing between image and text */ + border-radius: 5px; /* Add border radius to image */ + } + .song-info { + + h3 { + margin: 0; /* Remove default margin for heading */ + color: #fff; /* Adjust text color for song */ + font-size: 1rem; + font-weight: 200; + } + + p { + margin: 0; /* Remove default margin for paragraph */ + color: #aaa; /* Adjust text color for artist */ + font-size: 0.9rem; + } + } + .right { + position: fixed; + right: 25px; + transition: all 0.3s ease; + .duration { + + } + } + } + .song:first-child { + border-top: 1px solid #ccc; + } + } } } .playlist:hover { @@ -219,5 +291,6 @@ } } } + }