From 727029b7577047c44f000675dc0ea7e4b5e15b72 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 16:03:18 -0400 Subject: [PATCH 01/22] Create DB tables for likes and dislikes --- .../down.sql | 2 ++ .../up.sql | 11 ++++++++++ src/schema.rs | 20 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 migrations/2024-05-10-195644_create_likes_dislikes_table/down.sql create mode 100644 migrations/2024-05-10-195644_create_likes_dislikes_table/up.sql diff --git a/migrations/2024-05-10-195644_create_likes_dislikes_table/down.sql b/migrations/2024-05-10-195644_create_likes_dislikes_table/down.sql new file mode 100644 index 0000000..c341129 --- /dev/null +++ b/migrations/2024-05-10-195644_create_likes_dislikes_table/down.sql @@ -0,0 +1,2 @@ +DROP TABLE song_likes; +DROP TABLE song_dislikes; diff --git a/migrations/2024-05-10-195644_create_likes_dislikes_table/up.sql b/migrations/2024-05-10-195644_create_likes_dislikes_table/up.sql new file mode 100644 index 0000000..aa8ed05 --- /dev/null +++ b/migrations/2024-05-10-195644_create_likes_dislikes_table/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE song_likes ( + song_id INTEGER REFERENCES songs(id) ON DELETE CASCADE NOT NULL, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL, + PRIMARY KEY (song_id, user_id) +); + +CREATE TABLE song_dislikes ( + song_id INTEGER REFERENCES songs(id) ON DELETE CASCADE NOT NULL, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL, + PRIMARY KEY (song_id, user_id) +); diff --git a/src/schema.rs b/src/schema.rs index b98d736..fc9f7c9 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -29,6 +29,20 @@ diesel::table! { } } +diesel::table! { + song_dislikes (song_id, user_id) { + song_id -> Int4, + user_id -> Int4, + } +} + +diesel::table! { + song_likes (song_id, user_id) { + song_id -> Int4, + user_id -> Int4, + } +} + diesel::table! { songs (id) { id -> Int4, @@ -56,6 +70,10 @@ diesel::joinable!(album_artists -> albums (album_id)); diesel::joinable!(album_artists -> artists (artist_id)); diesel::joinable!(song_artists -> artists (artist_id)); diesel::joinable!(song_artists -> songs (song_id)); +diesel::joinable!(song_dislikes -> songs (song_id)); +diesel::joinable!(song_dislikes -> users (user_id)); +diesel::joinable!(song_likes -> songs (song_id)); +diesel::joinable!(song_likes -> users (user_id)); diesel::joinable!(songs -> albums (album_id)); diesel::allow_tables_to_appear_in_same_query!( @@ -63,6 +81,8 @@ diesel::allow_tables_to_appear_in_same_query!( albums, artists, song_artists, + song_dislikes, + song_likes, songs, users, ); From c07237ad8a4c499db047f28bd7452ac761287c0f Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 17:38:37 -0400 Subject: [PATCH 02/22] Move redundant controlbtn styling --- style/playbar.scss | 47 +++++++++++++++------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/style/playbar.scss b/style/playbar.scss index 6d91b94..8e627c0 100644 --- a/style/playbar.scss +++ b/style/playbar.scss @@ -64,23 +64,6 @@ flex-direction: row; justify-content: center; align-items: center; - - button { - .controlbtn { - color: $text-controls-color; - } - - .controlbtn:hover { - color: $controls-hover-color; - } - - .controlbtn:active { - color: $controls-click-color; - } - - background-color: transparent; - border: transparent; - } } .playduration { @@ -94,22 +77,22 @@ bottom: 13px; top: 13px; right: 90px; + } - button { - .controlbtn { - color: $text-controls-color; - } - - .controlbtn:hover { - color: $controls-hover-color; - } - - .controlbtn:active { - color: $controls-click-color; - } - - background-color: transparent; - border: transparent; + button { + .controlbtn { + color: $text-controls-color; } + + .controlbtn:hover { + color: $controls-hover-color; + } + + .controlbtn:active { + color: $controls-click-color; + } + + background-color: transparent; + border: transparent; } } From a0da89204c453bcb6ba182f36524310a15706f1a Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 17:39:39 -0400 Subject: [PATCH 03/22] Move MediaInfo into playbar-left-group div --- src/playbar.rs | 2 ++ style/playbar.scss | 34 ++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/playbar.rs b/src/playbar.rs index 7bfc1b0..9d76b59 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -486,7 +486,9 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView { on:timeupdate=on_time_update on:ended=on_end type="audio/mpeg" />
+
+
diff --git a/style/playbar.scss b/style/playbar.scss index 8e627c0..fcb76aa 100644 --- a/style/playbar.scss +++ b/style/playbar.scss @@ -39,23 +39,25 @@ } } - .media-info { - font-size: 16; - margin-left: 10px; - - position: absolute; - top: 50%; - transform: translateY(-50%); - display: grid; - grid-template-columns: 50px 1fr; - - .media-info-img { - width: 50px; - } - - .media-info-text { - text-align: left; + .playbar-left-group { + .media-info { + font-size: 16; margin-left: 10px; + + position: absolute; + top: 50%; + transform: translateY(-50%); + display: grid; + grid-template-columns: 50px 1fr; + + .media-info-img { + width: 50px; + } + + .media-info-text { + text-align: left; + margin-left: 10px; + } } } From 28ff98ab32bb2f800cc3d2a942c957d8a8337bf5 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:22:59 -0400 Subject: [PATCH 04/22] Add LikeDislike component --- src/playbar.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++ src/playstatus.rs | 6 ++++ 2 files changed, 76 insertions(+) diff --git a/src/playbar.rs b/src/playbar.rs index 9d76b59..a3a9333 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -278,6 +278,75 @@ fn MediaInfo(status: RwSignal) -> impl IntoView { } } +/// The like and dislike buttons +#[component] +fn LikeDislike(status: RwSignal) -> impl IntoView { + let like_icon = Signal::derive(move || { + status.with(|status| { + if status.liked { + icondata::TbThumbUpFilled + } else { + icondata::TbThumbUp + } + }) + }); + + let dislike_icon = Signal::derive(move || { + status.with(|status| { + if status.disliked { + icondata::TbThumbDownFilled + } else { + icondata::TbThumbDown + } + }) + }); + + let toggle_like = move |_| { + status.update(|status| { + if status.queue.is_empty() { + return; + } + + status.liked = !status.liked; + + if status.liked { + status.disliked = false; + } + + // TODO call the API to like the song + }); + }; + + let toggle_dislike = move |_| { + status.update(|status| { + if status.queue.is_empty() { + return; + } + + status.disliked = !status.disliked; + + if status.disliked { + status.liked = false; + } + + // TODO call the API to dislike the song + }); + }; + + // TODO update like and dislike status using the API when a new song starts playing + + view! { + + } +} + /// The play progress bar, and click handler for skipping to a certain time in the song #[component] fn ProgressBar(percentage: MaybeSignal, status: RwSignal) -> impl IntoView { @@ -488,6 +557,7 @@ pub fn PlayBar(status: RwSignal) -> impl IntoView {
+
diff --git a/src/playstatus.rs b/src/playstatus.rs index 5952ee9..42b4774 100644 --- a/src/playstatus.rs +++ b/src/playstatus.rs @@ -17,6 +17,10 @@ pub struct PlayStatus { pub history: VecDeque, /// A queue of songs that have yet to be played, ordered from next up to last pub queue: VecDeque, + /// Whether the current playing song is liked + pub liked: bool, + /// Whether the current playing song is disliked + pub disliked: bool, } impl PlayStatus { @@ -59,6 +63,8 @@ impl Default for PlayStatus { audio_player: None, history: VecDeque::new(), queue: VecDeque::new(), + liked: false, + disliked: false, } } } From 2b9d8495788e6ac4b9ab8df240b4ee4109d8c041 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:23:23 -0400 Subject: [PATCH 05/22] Add styling for horizontally mirrored button --- style/playbar.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/style/playbar.scss b/style/playbar.scss index fcb76aa..c81c659 100644 --- a/style/playbar.scss +++ b/style/playbar.scss @@ -82,6 +82,14 @@ } button { + .hmirror { + -moz-transform: scale(-1, 1); + -webkit-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); + } + .controlbtn { color: $text-controls-color; } From 7a750b60aa7ce05769885f79690575881962b066 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:24:02 -0400 Subject: [PATCH 06/22] Update styling to fit LikeDislike component --- src/playbar.rs | 2 -- style/playbar.scss | 31 +++++++++++++++---------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/playbar.rs b/src/playbar.rs index a3a9333..1cc9f5b 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -267,14 +267,12 @@ fn MediaInfo(status: RwSignal) -> impl IntoView { }); view! { -
{name}
{artist} - {album}
-
} } diff --git a/style/playbar.scss b/style/playbar.scss index c81c659..522ea11 100644 --- a/style/playbar.scss +++ b/style/playbar.scss @@ -40,24 +40,23 @@ } .playbar-left-group { - .media-info { - font-size: 16; + display: flex; + position: absolute; + top: 50%; + transform: translateY(-50%); + margin-left: 10px; + + .media-info-img { + width: 50px; + } + + .media-info-text { + text-align: left; margin-left: 10px; + } - position: absolute; - top: 50%; - transform: translateY(-50%); - display: grid; - grid-template-columns: 50px 1fr; - - .media-info-img { - width: 50px; - } - - .media-info-text { - text-align: left; - margin-left: 10px; - } + .like-dislike { + margin-left: 20px; } } From 08f1b95c18c3a59c8fc443d7e6dd05b100dab13d Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:26:59 -0400 Subject: [PATCH 07/22] Add get_user auth function --- src/auth.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/auth.rs b/src/auth.rs index 2eda7cf..58ba6f4 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -120,3 +120,26 @@ pub async fn require_auth() -> Result<(), ServerFnError> { } }) } + +/// Get the current logged-in user +/// Returns a Result with the user if they are logged in +/// Returns an error if the user is not logged in, or if there is an error getting the user +/// Intended to be used in a route to get the current user: +/// ```rust +/// use leptos::*; +/// use libretunes::auth::get_user; +/// #[server(endpoint = "user_route")] +/// pub async fn user_route() -> Result<(), ServerFnError> { +/// let user = get_user().await?; +/// println!("Logged in as: {}", user.username); +/// // Do something with the user +/// Ok(()) +/// } +/// ``` +#[cfg(feature = "ssr")] +pub async fn get_user() -> Result { + let auth_session = extract::>().await + .map_err(|e| ServerFnError::::ServerError(format!("Error getting auth session: {}", e)))?; + + auth_session.user.ok_or(ServerFnError::::ServerError("User not logged in".to_string())) +} From 2ed67e5545edcd2b02a20d0335da2f50ee4c34d8 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:28:39 -0400 Subject: [PATCH 08/22] Add various like/dislike functions for users --- src/models.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/src/models.rs b/src/models.rs index 15a6d12..5a01257 100644 --- a/src/models.rs +++ b/src/models.rs @@ -43,6 +43,135 @@ pub struct User { pub created_at: Option, } +impl User { + /// Like or unlike a song for this user + /// If likeing a song, remove dislike if it exists + #[cfg(feature = "ssr")] + pub async fn set_like_song(self: &Self, song_id: i32, like: bool, conn: &mut PgPooledConn) -> + Result<(), Box> { + use log::*; + debug!("Setting like for song {} to {}", song_id, like); + + use crate::schema::song_likes; + use crate::schema::song_dislikes; + + let my_id = self.id.ok_or("User id must be present (Some) to like/un-like a song")?; + + if like { + diesel::insert_into(song_likes::table) + .values((song_likes::song_id.eq(song_id), song_likes::user_id.eq(my_id))) + .execute(conn)?; + + // Remove dislike if it exists + diesel::delete(song_dislikes::table.filter(song_dislikes::song_id.eq(song_id) + .and(song_dislikes::user_id.eq(my_id)))) + .execute(conn)?; + } else { + diesel::delete(song_likes::table.filter(song_likes::song_id.eq(song_id).and(song_likes::user_id.eq(my_id)))) + .execute(conn)?; + } + + Ok(()) + } + + /// Get the like status of a song for this user + #[cfg(feature = "ssr")] + pub async fn get_like_song(self: &Self, song_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::song_likes; + + let my_id = self.id.ok_or("User id must be present (Some) to get like status of a song")?; + + let like = song_likes::table + .filter(song_likes::song_id.eq(song_id).and(song_likes::user_id.eq(my_id))) + .first::<(i32, i32)>(conn) + .optional()? + .is_some(); + + Ok(like) + } + + /// Get songs liked by this user + #[cfg(feature = "ssr")] + pub async fn get_liked_songs(self: &Self, conn: &mut PgPooledConn) -> Result, Box> { + use crate::schema::songs::dsl::*; + use crate::schema::song_likes::dsl::*; + + let my_id = self.id.ok_or("User id must be present (Some) to get liked songs")?; + + let my_songs = songs + .inner_join(song_likes) + .filter(user_id.eq(my_id)) + .select(songs::all_columns()) + .load(conn)?; + + Ok(my_songs) + } + + /// Dislike or remove dislike from a song for this user + /// If disliking a song, remove like if it exists + #[cfg(feature = "ssr")] + pub async fn set_dislike_song(self: &Self, song_id: i32, dislike: bool, conn: &mut PgPooledConn) -> + Result<(), Box> { + use log::*; + debug!("Setting dislike for song {} to {}", song_id, dislike); + + use crate::schema::song_likes; + use crate::schema::song_dislikes; + + let my_id = self.id.ok_or("User id must be present (Some) to dislike/un-dislike a song")?; + + if dislike { + diesel::insert_into(song_dislikes::table) + .values((song_dislikes::song_id.eq(song_id), song_dislikes::user_id.eq(my_id))) + .execute(conn)?; + + // Remove like if it exists + diesel::delete(song_likes::table.filter(song_likes::song_id.eq(song_id) + .and(song_likes::user_id.eq(my_id)))) + .execute(conn)?; + } else { + diesel::delete(song_dislikes::table.filter(song_dislikes::song_id.eq(song_id) + .and(song_dislikes::user_id.eq(my_id)))) + .execute(conn)?; + } + + Ok(()) + } + + /// Get the dislike status of a song for this user + #[cfg(feature = "ssr")] + pub async fn get_dislike_song(self: &Self, song_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::song_dislikes; + + let my_id = self.id.ok_or("User id must be present (Some) to get dislike status of a song")?; + + let dislike = song_dislikes::table + .filter(song_dislikes::song_id.eq(song_id).and(song_dislikes::user_id.eq(my_id))) + .first::<(i32, i32)>(conn) + .optional()? + .is_some(); + + Ok(dislike) + } + + /// Get songs disliked by this user + #[cfg(feature = "ssr")] + pub async fn get_disliked_songs(self: &Self, conn: &mut PgPooledConn) -> Result, Box> { + use crate::schema::songs::dsl::*; + use crate::schema::song_likes::dsl::*; + + let my_id = self.id.ok_or("User id must be present (Some) to get disliked songs")?; + + let my_songs = songs + .inner_join(song_likes) + .filter(user_id.eq(my_id)) + .select(songs::all_columns()) + .load(conn)?; + + Ok(my_songs) + } +} + /// Model for an artist #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::artists))] From 775c7eff3ae2e41ef4bcd32f386f9d6a32069af3 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:29:56 -0400 Subject: [PATCH 09/22] Add api module --- src/api/mod.rs | 0 src/lib.rs | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 src/api/mod.rs diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 89cd04e..f96486f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ pub mod users; pub mod search; pub mod fileserv; pub mod error_template; +pub mod api; + use cfg_if::cfg_if; cfg_if! { From e411ab9eeed3f0f57aa785e6ddbbc7f17ac4393e Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Fri, 10 May 2024 20:30:33 -0400 Subject: [PATCH 10/22] Add API functions for liking and disliking songs --- src/api/mod.rs | 1 + src/api/songs.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/api/songs.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index e69de29..fe30466 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -0,0 +1 @@ +pub mod songs; diff --git a/src/api/songs.rs b/src/api/songs.rs new file mode 100644 index 0000000..efb9209 --- /dev/null +++ b/src/api/songs.rs @@ -0,0 +1,55 @@ +use leptos::*; + +use cfg_if::cfg_if; + + +cfg_if! { + if #[cfg(feature = "ssr")] { + use leptos::server_fn::error::NoCustomError; + use crate::database::get_db_conn; + use crate::auth::get_user; + } +} + +/// Like or unlike a song +#[server(endpoint = "songs/set_like")] +pub async fn set_like_song(song_id: i32, like: bool) -> Result<(), ServerFnError> { + let user = get_user().await.map_err(|e| ServerFnError:::: + ServerError(format!("Error getting user: {}", e)))?; + + let db_con = &mut get_db_conn(); + + user.set_like_song(song_id, like, db_con).await.map_err(|e| ServerFnError:::: + ServerError(format!("Error liking song: {}", e))) +} + +/// Dislike or remove dislike from a song +#[server(endpoint = "songs/set_dislike")] +pub async fn set_dislike_song(song_id: i32, dislike: bool) -> Result<(), ServerFnError> { + let user = get_user().await.map_err(|e| ServerFnError:::: + ServerError(format!("Error getting user: {}", e)))?; + + let db_con = &mut get_db_conn(); + + user.set_dislike_song(song_id, dislike, db_con).await.map_err(|e| ServerFnError:::: + ServerError(format!("Error disliking song: {}", e))) +} + +/// Get the like and dislike status of a song +#[server(endpoint = "songs/get_like_dislike")] +pub async fn get_like_dislike_song(song_id: i32) -> Result<(bool, bool), ServerFnError> { + let user = get_user().await.map_err(|e| ServerFnError:::: + ServerError(format!("Error getting user: {}", e)))?; + + let db_con = &mut get_db_conn(); + + // TODO this could probably be done more efficiently with a tokio::try_join, but + // doing so is much more complicated than it would initially seem + + let like = user.get_like_song(song_id, db_con).await.map_err(|e| ServerFnError:::: + ServerError(format!("Error getting song liked: {}", e)))?; + let dislike = user.get_dislike_song(song_id, db_con).await.map_err(|e| ServerFnError:::: + ServerError(format!("Error getting song disliked: {}", e)))?; + + Ok((like, dislike)) +} From d45f102be751788eebbaacb896eb14a94ac69e36 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 28 Jul 2024 22:37:17 -0400 Subject: [PATCH 11/22] Remove TryInto for Song These pre-defined conversion traits are meant to be fast, and implementing this conversion that accesses the database goes against this design. --- src/songdata.rs | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/songdata.rs b/src/songdata.rs index 23ce415..0c1b670 100644 --- a/src/songdata.rs +++ b/src/songdata.rs @@ -28,43 +28,6 @@ pub struct SongData { pub image_path: String, } -#[cfg(feature = "ssr")] -impl TryInto for Song { - type Error = Box; - - /// Convert a Song object into a SongData object - /// - /// This conversion is expensive, as it requires database queries to get the artist and album objects. - /// The SongData/Song conversions are also not truly reversible, - /// due to the way the image_path, album, and artist data is handled. - fn try_into(self) -> Result { - use crate::database; - let mut db_con = database::get_db_conn(); - - let album = self.get_album(&mut db_con)?; - - // Use the song's image path if it exists, otherwise use the album's image path, or fallback to the placeholder - let image_path = self.image_path.clone().unwrap_or_else(|| { - album - .as_ref() - .and_then(|album| album.image_path.clone()) - .unwrap_or_else(|| "/assets/images/placeholder.jpg".to_string()) - }); - - Ok(SongData { - id: self.id.ok_or("Song id must be present (Some) to convert to SongData")?, - title: self.title.clone(), - artists: self.get_artists(&mut db_con)?, - album: album, - track: self.track, - duration: self.duration, - release_date: self.release_date, - // TODO https://gitlab.mregirouard.com/libretunes/libretunes/-/issues/35 - song_path: self.storage_path, - image_path: image_path, - }) - } -} impl TryInto for SongData { type Error = Box; @@ -72,7 +35,7 @@ impl TryInto for SongData { /// Convert a SongData object into a Song object /// /// The SongData/Song conversions are also not truly reversible, - /// due to the way the image_path, album, and and artist data is handled. + /// due to the way the image_path data is handled. fn try_into(self) -> Result { Ok(Song { id: Some(self.id), From e5a6e8f44e1470ad712e14b9bc45cb7cb2f47f55 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 28 Jul 2024 22:40:52 -0400 Subject: [PATCH 12/22] Add like_dislike field to SongData --- src/songdata.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/songdata.rs b/src/songdata.rs index 0c1b670..61e263f 100644 --- a/src/songdata.rs +++ b/src/songdata.rs @@ -26,6 +26,8 @@ pub struct SongData { /// Path to song image, relative to the root of the web server. /// For example, `"/assets/images/Song.jpg"` pub image_path: String, + /// Whether the song is liked by the user + pub like_dislike: Option<(bool, bool)>, } From afbcc65457d8d44a654d863229c8181aaa64f8b1 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Sun, 28 Jul 2024 22:42:04 -0400 Subject: [PATCH 13/22] Implement like/dislike using SongData instead of PlayStatus --- src/playbar.rs | 106 ++++++++++++++++++++++++++++++++++------------ src/playstatus.rs | 6 --- 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/playbar.rs b/src/playbar.rs index 157964a..1b464bb 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -1,5 +1,7 @@ use crate::models::Artist; use crate::playstatus::PlayStatus; +use crate::songdata::SongData; +use crate::api::songs; use leptos::ev::MouseEvent; use leptos::html::{Audio, Div}; use leptos::leptos_dom::*; @@ -283,53 +285,101 @@ fn MediaInfo(status: RwSignal) -> impl IntoView { fn LikeDislike(status: RwSignal) -> impl IntoView { let like_icon = Signal::derive(move || { status.with(|status| { - if status.liked { - icondata::TbThumbUpFilled - } else { - icondata::TbThumbUp + match status.queue.front() { + Some(SongData { like_dislike: Some((true, _)), .. }) => icondata::TbThumbUpFilled, + _ => icondata::TbThumbUp, } }) }); let dislike_icon = Signal::derive(move || { status.with(|status| { - if status.disliked { - icondata::TbThumbDownFilled - } else { - icondata::TbThumbDown + match status.queue.front() { + Some(SongData { like_dislike: Some((_, true)), .. }) => icondata::TbThumbDownFilled, + _ => icondata::TbThumbDown, } }) }); let toggle_like = move |_| { status.update(|status| { - if status.queue.is_empty() { - return; + match status.queue.front_mut() { + Some(SongData { id, like_dislike: Some((liked, disliked)), .. }) => { + *liked = !*liked; + + if *liked { + *disliked = false; + } + + let id = *id; + let liked = *liked; + spawn_local(async move { + if let Err(e) = songs::set_like_song(id, liked).await { + error!("Error liking song: {:?}", e); + } + }); + }, + Some(SongData { id, like_dislike, .. }) => { + // This arm should only be reached if like_dislike is None + // In this case, the buttons will show up not filled, indicating that the song is not + // liked or disliked. Therefore, clicking the like button should like the song. + + *like_dislike = Some((true, false)); + + let id = *id; + spawn_local(async move { + if let Err(e) = songs::set_like_song(id, true).await { + error!("Error liking song: {:?}", e); + } + }); + }, + _ => { + log!("Unable to like song: No song in queue"); + return; + } } - - status.liked = !status.liked; - - if status.liked { - status.disliked = false; - } - - // TODO call the API to like the song }); }; let toggle_dislike = move |_| { + log!("Dislike button pressed"); + status.update(|status| { - if status.queue.is_empty() { - return; + match status.queue.front_mut() { + Some(SongData { id, like_dislike: Some((liked, disliked)), .. }) => { + *disliked = !*disliked; + + if *disliked { + *liked = false; + } + + let id = *id; + let disliked = *disliked; + spawn_local(async move { + if let Err(e) = songs::set_dislike_song(id, disliked).await { + error!("Error disliking song: {:?}", e); + } + }); + }, + Some(SongData { id, like_dislike, .. }) => { + // This arm should only be reached if like_dislike is None + // In this case, the buttons will show up not filled, indicating that the song is not + // liked or disliked. Therefore, clicking the dislike button should dislike the song. + + *like_dislike = Some((false, true)); + + let id = *id; + spawn_local(async move { + if let Err(e) = songs::set_dislike_song(id, true).await { + error!("Error disliking song: {:?}", e); + } + }); + }, + _ => { + log!("Unable to dislike song: No song in queue"); + return; + } } - - status.disliked = !status.disliked; - - if status.disliked { - status.liked = false; - } - - // TODO call the API to dislike the song }); }; diff --git a/src/playstatus.rs b/src/playstatus.rs index 42b4774..5952ee9 100644 --- a/src/playstatus.rs +++ b/src/playstatus.rs @@ -17,10 +17,6 @@ pub struct PlayStatus { pub history: VecDeque, /// A queue of songs that have yet to be played, ordered from next up to last pub queue: VecDeque, - /// Whether the current playing song is liked - pub liked: bool, - /// Whether the current playing song is disliked - pub disliked: bool, } impl PlayStatus { @@ -63,8 +59,6 @@ impl Default for PlayStatus { audio_player: None, history: VecDeque::new(), queue: VecDeque::new(), - liked: false, - disliked: false, } } } From 0c54f0aeb83cfe2b12fcefae7177aadbb2c21824 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Tue, 30 Jul 2024 19:34:15 -0400 Subject: [PATCH 14/22] Remove leftover dislike button logging --- src/playbar.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/playbar.rs b/src/playbar.rs index 1b464bb..7cf59fb 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -342,8 +342,6 @@ fn LikeDislike(status: RwSignal) -> impl IntoView { }; let toggle_dislike = move |_| { - log!("Dislike button pressed"); - status.update(|status| { match status.queue.front_mut() { Some(SongData { id, like_dislike: Some((liked, disliked)), .. }) => { From bab819822dd3e0bac4856d294cd6809646851174 Mon Sep 17 00:00:00 2001 From: Ethan Girouard Date: Tue, 30 Jul 2024 19:34:52 -0400 Subject: [PATCH 15/22] Remove like/dislike API calls TODO comment --- src/playbar.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/playbar.rs b/src/playbar.rs index 7cf59fb..6ef6b7e 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -381,8 +381,6 @@ fn LikeDislike(status: RwSignal) -> impl IntoView { }); }; - // TODO update like and dislike status using the API when a new song starts playing - view! {