From df01bafbd139417c9b645ec2951573c82868a142 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Tue, 22 Oct 2024 03:32:55 +0000 Subject: [PATCH 01/11] Album Page Component --- src/app.rs | 2 ++ src/models.rs | 22 +++++++++++++++++-- src/pages.rs | 3 ++- src/pages/album.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++ src/playbar.rs | 2 +- src/songdata.rs | 3 ++- 6 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/pages/album.rs diff --git a/src/app.rs b/src/app.rs index ed9fd1b..ed8b252 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,6 +7,7 @@ use leptos_meta::*; use leptos_router::*; use crate::pages::login::*; use crate::pages::signup::*; +use crate::pages::album::*; use crate::error_template::{AppError, ErrorTemplate}; @@ -42,6 +43,7 @@ pub fn App() -> impl IntoView { + diff --git a/src/models.rs b/src/models.rs index b96c8d2..b2534dc 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,13 +1,15 @@ use std::time::SystemTime; +use leptos::{server, ServerFnError}; use time::Date; use serde::{Deserialize, Serialize}; +use crate::songdata::SongData; use cfg_if::cfg_if; cfg_if! { if #[cfg(feature = "ssr")] { use diesel::prelude::*; - use crate::database::PgPooledConn; + use crate::database::*; use std::error::Error; } } @@ -499,7 +501,7 @@ impl Album { Ok(()) } - /// Get songs by this artist from the database + /// Get songs by this album from the database /// /// The `id` field of this album must be present (Some) to get songs /// @@ -528,6 +530,22 @@ impl Album { } } +#[server(endpoint = "get_album")] +pub async fn get_album(a_id: i32) -> Result,ServerFnError> { + use crate::schema::songs::dsl::*; + use crate::schema::song_artists::dsl::*; + + let conn = get_db_conn(); + + let songs = songs + .inner_join(song_artists) + .filter(album_id.eq(a_id)) + .select(songs::all_columns()) + .load(conn)?; + + Ok(songs.into()) +} + /// Model for a song #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))] diff --git a/src/pages.rs b/src/pages.rs index 40f63fd..c1e787a 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -1,2 +1,3 @@ pub mod login; -pub mod signup; \ No newline at end of file +pub mod signup; +pub mod album; \ No newline at end of file diff --git a/src/pages/album.rs b/src/pages/album.rs new file mode 100644 index 0000000..bbe3f35 --- /dev/null +++ b/src/pages/album.rs @@ -0,0 +1,54 @@ +use leptos::leptos_dom::*; +use leptos::*; +use leptos_icons::*; +use leptos_router::*; +use crate::models::*; +use crate::components::song_list::*; + + +#[derive(Params, PartialEq)] +struct AlbumParams { + id: i32 +} + +#[component] +pub fn AlbumPage() -> impl IntoView { + let params = use_params::(); + + let id = move || {params.with(|params| { + params.as_ref() + .map(|params| params.id) + .map_err(|e| e.clone()) + }) + }; + + let song_list = create_resource( + id, + |value| async move { + match value { + Ok(v) => {get_album(v).await}, + Err(e) => {Err(ServerFnError::Request("Invalid album!".into()))}, + } + }, + ); + + view! { + "Loading..."

} + > + {move || { + song_list.with( |song_list| { + match song_list { + Some(Ok(s)) => { + view! { }.into_view() + }, + Some(Err(e)) => { + view! {
"Error loading albums"
}.into_view() + }, + None => {view! { }.into_view()} + } + }) + }} +
+ } +} \ No newline at end of file diff --git a/src/playbar.rs b/src/playbar.rs index d113b7a..c14e9f7 100644 --- a/src/playbar.rs +++ b/src/playbar.rs @@ -444,7 +444,7 @@ fn QueueToggle(status: RwSignal) -> impl IntoView { toggle_queue(status); log!("queue button pressed, queue status: {:?}", status.with_untracked(|status| status.queue_open)); }; - + // We use this to prevent the buttons from being focused when clicked // If buttons were focused on clicks, then pressing space bar to play/pause would "click" the button // and trigger unwanted behavior diff --git a/src/songdata.rs b/src/songdata.rs index 36e5679..1da9e69 100644 --- a/src/songdata.rs +++ b/src/songdata.rs @@ -1,12 +1,13 @@ use crate::models::{Album, Artist, Song}; use crate::components::dashboard_tile::DashboardTile; +use serde::{Serialize, Deserialize}; use time::Date; /// Holds information about a song /// /// Intended to be used in the front-end, as it includes artist and album objects, rather than just their ids. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct SongData { /// Song id pub id: i32, From fe1b76e6e40d9a42902e2a4839fa31aa72190b39 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 25 Oct 2024 21:23:21 +0000 Subject: [PATCH 02/11] API Endpoints for Album Queries --- src/api/album.rs | 37 ++++++++++++++++++++++++++++ src/api/mod.rs | 1 + src/app.rs | 4 +-- src/models.rs | 34 ++++++++++++++----------- src/pages.rs | 2 +- src/pages/{album.rs => albumpage.rs} | 7 +++--- 6 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/api/album.rs rename src/pages/{album.rs => albumpage.rs} (92%) diff --git a/src/api/album.rs b/src/api/album.rs new file mode 100644 index 0000000..82f62c9 --- /dev/null +++ b/src/api/album.rs @@ -0,0 +1,37 @@ +use leptos::*; +use crate::models::Album; +use crate::models::Song; +use crate::songdata::SongData; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "ssr")] { + use leptos::server_fn::error::NoCustomError; + use crate::database::get_db_conn; + } +} + +#[server(endpoint = "album/get")] +pub async fn get_album(id: Option) -> Result { + let db_con = &mut get_db_conn(); + let album = Album::get_album(id,db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; + Ok(album) +} + +#[server(endpoint = "album/get_songs")] +pub async fn get_songs(album: Option) -> Result, ServerFnError> { + let db_con = &mut get_db_conn(); + let songs = album.get_songs(db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; + Ok(songs) +} + +// #[server(endpoint = "album/get_song_list")] +// pub async fn get_song_list(album: Option) -> Result, ServerFnError> { +// songs = get_songs(album)?; + +// let mut song_data_list = Vec::new(); +// // TODO: NEEDS SONG DATA QUERIES +// } diff --git a/src/api/mod.rs b/src/api/mod.rs index c287a07..e9ee6c9 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,3 +1,4 @@ pub mod history; pub mod profile; pub mod songs; +pub mod album; diff --git a/src/app.rs b/src/app.rs index ed8b252..689e9cd 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use leptos_meta::*; use leptos_router::*; use crate::pages::login::*; use crate::pages::signup::*; -use crate::pages::album::*; +use crate::pages::albumpage::*; use crate::error_template::{AppError, ErrorTemplate}; @@ -43,7 +43,7 @@ pub fn App() -> impl IntoView { - + // diff --git a/src/models.rs b/src/models.rs index b2534dc..e1a8a63 100644 --- a/src/models.rs +++ b/src/models.rs @@ -528,22 +528,28 @@ impl Album { Ok(my_songs) } -} -#[server(endpoint = "get_album")] -pub async fn get_album(a_id: i32) -> Result,ServerFnError> { - use crate::schema::songs::dsl::*; - use crate::schema::song_artists::dsl::*; - - let conn = get_db_conn(); - - let songs = songs - .inner_join(song_artists) - .filter(album_id.eq(a_id)) - .select(songs::all_columns()) - .load(conn)?; + /// Obtain an album from its albumid + /// # Arguments + /// + /// * `album_id` - The id of the album to select + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result>` - A result indicating success with the desired album, or an error + /// + #[cfg(feature = "ssr")] + pub fn get_album(album_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::albums::dsl::*; + use crate::database::get_db_conn; - Ok(songs.into()) + let album = albums + .find(album_id) + .first(conn)?; + + Ok(album) + } } /// Model for a song diff --git a/src/pages.rs b/src/pages.rs index c1e787a..057a60a 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -1,3 +1,3 @@ pub mod login; pub mod signup; -pub mod album; \ No newline at end of file +pub mod albumpage; \ No newline at end of file diff --git a/src/pages/album.rs b/src/pages/albumpage.rs similarity index 92% rename from src/pages/album.rs rename to src/pages/albumpage.rs index bbe3f35..915d9f4 100644 --- a/src/pages/album.rs +++ b/src/pages/albumpage.rs @@ -1,6 +1,5 @@ use leptos::leptos_dom::*; use leptos::*; -use leptos_icons::*; use leptos_router::*; use crate::models::*; use crate::components::song_list::*; @@ -11,6 +10,7 @@ struct AlbumParams { id: i32 } +/* #[component] pub fn AlbumPage() -> impl IntoView { let params = use_params::(); @@ -43,7 +43,7 @@ pub fn AlbumPage() -> impl IntoView { view! { }.into_view() }, Some(Err(e)) => { - view! {
"Error loading albums"
}.into_view() + view! {
"Error loading albums: :e"
}.into_view() }, None => {view! { }.into_view()} } @@ -51,4 +51,5 @@ pub fn AlbumPage() -> impl IntoView { }} } -} \ No newline at end of file +} +*/ From ff24f68eede077b71c712531eb24af17b4ae756e Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Sat, 26 Oct 2024 03:53:03 +0000 Subject: [PATCH 03/11] A very big SQL Query --- src/models.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/models.rs b/src/models.rs index e1a8a63..441e7fa 100644 --- a/src/models.rs +++ b/src/models.rs @@ -550,6 +550,123 @@ impl Album { Ok(album) } + + /// Obtain an album from its albumid + /// # Arguments + /// + /// * `album_id` - The id of the album to select + /// * `conn` - A mutable reference to a database connection + /// + /// # Returns + /// + /// * `Result>` - A result indicating success with the desired album, or an error + /// + #[cfg(feature = "ssr")] + pub fn get_song_data(album_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::albums::dsl::*; + use crate::database::get_db_conn; + + " + WITH R1 AS( + SELECT + id AS album_id, + title AS album_title, + release_date AS album_release_date, + image_path AS album_image_path + FROM albums WHERE [ALBUM_ID] = id + ), + R2 AS( + SELECT + id, + title, + track, + duration, + release_date, + storage_path AS song_path, + image_path, + r.album_id, + album_title, + album_release_date, + album_image_path + FROM + R1 r LEFT JOIN songs s ON r.album_id = s.album_id + ), + R7 AS( + SELECT + song_id, + artist_id, + name AS artist_name + FROM + song_artists sa LEFT JOIN artists a ON sa.artist_id = a.id + ), + R3 AS( + SELECT + s.song_id, + artist_name, + artist_id + FROM + (SELECT id AS song_id FROM R2) s LEFT JOIN R7 a ON s.song_id = a.song_id + ), + R4 AS( + SELECT + id, + title, + track, + duration, + release_date, + song_path, + image_path, + r.album_id, + album_title, + album_release_date, + album_image_path, + artist_id, + artist_name + FROM + R2 r LEFT JOIN R3 a ON r.id = a.song_id + ), + R5 AS( + SELECT + r.id, + count(sl.user_id) <> 0 AS liked + FROM + R4 r LEFT JOIN song_likes sl ON sl.user_id = [USER_ID] AND r.id = sl.song_id + GROUP BY + r.id + ), + R6 AS( + SELECT + r.id, + count(sd.user_id) <> 0 AS disliked + FROM + R4 r LEFT JOIN song_dislikes sd ON sd.user_id = [USER_ID] AND r.id = sd.song_id + GROUP BY + r.id + ) + SELECT + r.id, + title, + track, + duration, + release_date, + song_path, + image_path, + r.album_id, + album_title, + album_release_date, + album_image_path, + artist_id, + artist_name, + liked, + disliked + FROM R4 r + LEFT JOIN R5 likes ON likes.id = r.id + LEFT JOIN R6 dislikes ON dislikes.id = r.id + ; + " + + Ok(album) + } } /// Model for a song From 7a1ffaad475ed0e3df348504e537e3ebcd1edb09 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 8 Nov 2024 22:16:46 +0000 Subject: [PATCH 04/11] Finished Query for Song Data from Album --- src/api/album.rs | 36 +++++------ src/models.rs | 156 ++++++++++++++++------------------------------- 2 files changed, 72 insertions(+), 120 deletions(-) diff --git a/src/api/album.rs b/src/api/album.rs index 82f62c9..8d62d95 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -9,29 +9,29 @@ cfg_if! { if #[cfg(feature = "ssr")] { use leptos::server_fn::error::NoCustomError; use crate::database::get_db_conn; + use crate::auth::get_user; } } -#[server(endpoint = "album/get")] -pub async fn get_album(id: Option) -> Result { - let db_con = &mut get_db_conn(); - let album = Album::get_album(id,db_con) - .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; - Ok(album) -} +// #[server(endpoint = "album/get")] +// pub async fn get_album(id: Option) -> Result { +// let db_con = &mut get_db_conn(); +// let album = Album::get_album(id,db_con) +// .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; +// Ok(album) +// } -#[server(endpoint = "album/get_songs")] -pub async fn get_songs(album: Option) -> Result, ServerFnError> { - let db_con = &mut get_db_conn(); - let songs = album.get_songs(db_con) - .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; - Ok(songs) -} +// #[server(endpoint = "album/get_songs")] +// pub async fn get_songs(album: Option) -> Result, ServerFnError> { +// let db_con = &mut get_db_conn(); +// let songs = album.get_songs(db_con) +// .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; +// Ok(songs) +// } // #[server(endpoint = "album/get_song_list")] -// pub async fn get_song_list(album: Option) -> Result, ServerFnError> { -// songs = get_songs(album)?; - -// let mut song_data_list = Vec::new(); +// pub async fn get_song_data(album: Option) -> Result, ServerFnError> { +// let user = get_user().await?; +// let db_con = &mut get_db_conn(); // // TODO: NEEDS SONG DATA QUERIES // } diff --git a/src/models.rs b/src/models.rs index 441e7fa..46475f9 100644 --- a/src/models.rs +++ b/src/models.rs @@ -540,7 +540,7 @@ impl Album { /// * `Result>` - A result indicating success with the desired album, or an error /// #[cfg(feature = "ssr")] - pub fn get_album(album_id: i32, conn: &mut PgPooledConn) -> Result> { + pub fn get_album(album_id: i32, user_id: i32, conn: &mut PgPooledConn) -> Result> { use crate::schema::albums::dsl::*; use crate::database::get_db_conn; @@ -562,110 +562,62 @@ impl Album { /// * `Result>` - A result indicating success with the desired album, or an error /// #[cfg(feature = "ssr")] - pub fn get_song_data(album_id: i32, conn: &mut PgPooledConn) -> Result> { - use crate::schema::albums::dsl::*; + pub fn get_song_data(album_id: i32, user_like_dislike_id: i32, conn: &mut PgPooledConn) -> Result, Box> { + use crate::schema::*; use crate::database::get_db_conn; + use std::collections::HashMap; - " - WITH R1 AS( - SELECT - id AS album_id, - title AS album_title, - release_date AS album_release_date, - image_path AS album_image_path - FROM albums WHERE [ALBUM_ID] = id - ), - R2 AS( - SELECT - id, - title, - track, - duration, - release_date, - storage_path AS song_path, - image_path, - r.album_id, - album_title, - album_release_date, - album_image_path - FROM - R1 r LEFT JOIN songs s ON r.album_id = s.album_id - ), - R7 AS( - SELECT - song_id, - artist_id, - name AS artist_name - FROM - song_artists sa LEFT JOIN artists a ON sa.artist_id = a.id - ), - R3 AS( - SELECT - s.song_id, - artist_name, - artist_id - FROM - (SELECT id AS song_id FROM R2) s LEFT JOIN R7 a ON s.song_id = a.song_id - ), - R4 AS( - SELECT - id, - title, - track, - duration, - release_date, - song_path, - image_path, - r.album_id, - album_title, - album_release_date, - album_image_path, - artist_id, - artist_name - FROM - R2 r LEFT JOIN R3 a ON r.id = a.song_id - ), - R5 AS( - SELECT - r.id, - count(sl.user_id) <> 0 AS liked - FROM - R4 r LEFT JOIN song_likes sl ON sl.user_id = [USER_ID] AND r.id = sl.song_id - GROUP BY - r.id - ), - R6 AS( - SELECT - r.id, - count(sd.user_id) <> 0 AS disliked - FROM - R4 r LEFT JOIN song_dislikes sd ON sd.user_id = [USER_ID] AND r.id = sd.song_id - GROUP BY - r.id - ) - SELECT - r.id, - title, - track, - duration, - release_date, - song_path, - image_path, - r.album_id, - album_title, - album_release_date, - album_image_path, - artist_id, - artist_name, - liked, - disliked - FROM R4 r - LEFT JOIN R5 likes ON likes.id = r.id - LEFT JOIN R6 dislikes ON dislikes.id = r.id - ; - " + let songs: Vec<(Album, Option, Option, Option<(i32, i32)>, Option<(i32, i32)>)> = + albums::table + .find(album_id) + .left_join(songs::table.on(albums::id.nullable().eq(songs::album_id))) + .left_join(song_artists::table.inner_join(artists::table).on(songs::id.eq(song_artists::song_id))) + .left_join(song_likes::table.on(songs::id.eq(song_likes::song_id).and(song_likes::user_id.eq(user_like_dislike_id)))) + .left_join(song_dislikes::table.on(songs::id.eq(song_dislikes::song_id).and(song_dislikes::user_id.eq(user_like_dislike_id)))) + .select(( + albums::all_columns, + songs::all_columns.nullable(), + artists::all_columns.nullable(), + song_likes::all_columns.nullable(), + song_dislikes::all_columns.nullable() + )) + .load(conn)?; - Ok(album) + let mut album_songs: HashMap = HashMap::with_capacity(songs.len()); + + for (album, song, artist, like, dislike) in songs { + if let Some(song) = song { + let like_dislike = match (like, dislike) { + (Some(_), Some(_)) => Some((true, true)), + (Some(_), None) => Some((true, false)), + (None, Some(_)) => Some((false, true)), + _ => None, + }; + + let image_path = song.image_path.unwrap_or( + album.image_path.clone().unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string())); + + let songdata = SongData { + id: song.id.unwrap(), + title: song.title, + artists: artist.map(|artist| vec![artist]).unwrap_or_default(), + album: Some(album), + track: song.track, + duration: song.duration, + release_date: song.release_date, + song_path: song.storage_path, + image_path: image_path, + like_dislike: like_dislike, + }; + + album_songs.insert(song.id.unwrap(), songdata); + } + } + + // Sort the songs by date + let mut songdata: Vec = album_songs.into_values().collect(); + songdata.sort_by(|a, b| b.track.cmp(&a.track)); + Ok(songdata) } } From 568f6ada0e4d4dc92ecfa4584f224f4f92728778 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Tue, 12 Nov 2024 21:44:30 +0000 Subject: [PATCH 05/11] Working Albums Page (BETA) --- src/api/album.rs | 37 ++++++++++++++++--------------------- src/app.rs | 2 +- src/models.rs | 2 +- src/pages/albumpage.rs | 10 +++++----- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/api/album.rs b/src/api/album.rs index 8d62d95..33a5299 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -13,25 +13,20 @@ cfg_if! { } } -// #[server(endpoint = "album/get")] -// pub async fn get_album(id: Option) -> Result { -// let db_con = &mut get_db_conn(); -// let album = Album::get_album(id,db_con) -// .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; -// Ok(album) -// } +#[server(endpoint = "album/get")] +pub async fn get_album(id: i32) -> Result { + let db_con = &mut get_db_conn(); + let album = Album::get_album(id,db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; + Ok(album) +} -// #[server(endpoint = "album/get_songs")] -// pub async fn get_songs(album: Option) -> Result, ServerFnError> { -// let db_con = &mut get_db_conn(); -// let songs = album.get_songs(db_con) -// .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; -// Ok(songs) -// } - -// #[server(endpoint = "album/get_song_list")] -// pub async fn get_song_data(album: Option) -> Result, ServerFnError> { -// let user = get_user().await?; -// let db_con = &mut get_db_conn(); -// // TODO: NEEDS SONG DATA QUERIES -// } +#[server(endpoint = "album/get_songs")] +pub async fn get_songs(id: i32) -> Result, ServerFnError> { + let user = get_user().await?; + let db_con = &mut get_db_conn(); + // TODO: NEEDS SONG DATA QUERIES + let songdata = Album::get_song_data(id,user.id.unwrap(),db_con) + .map_err(|e| ServerFnError::::ServerError(format!("Error getting song data: {}", e)))?; + Ok(songdata) +} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 689e9cd..e3e9226 100644 --- a/src/app.rs +++ b/src/app.rs @@ -43,7 +43,7 @@ pub fn App() -> impl IntoView { - // + diff --git a/src/models.rs b/src/models.rs index 46475f9..ed28ba6 100644 --- a/src/models.rs +++ b/src/models.rs @@ -540,7 +540,7 @@ impl Album { /// * `Result>` - A result indicating success with the desired album, or an error /// #[cfg(feature = "ssr")] - pub fn get_album(album_id: i32, user_id: i32, conn: &mut PgPooledConn) -> Result> { + pub fn get_album(album_id: i32, conn: &mut PgPooledConn) -> Result> { use crate::schema::albums::dsl::*; use crate::database::get_db_conn; diff --git a/src/pages/albumpage.rs b/src/pages/albumpage.rs index 915d9f4..392cc9b 100644 --- a/src/pages/albumpage.rs +++ b/src/pages/albumpage.rs @@ -3,6 +3,7 @@ use leptos::*; use leptos_router::*; use crate::models::*; use crate::components::song_list::*; +use crate::api::album::*; #[derive(Params, PartialEq)] @@ -10,7 +11,6 @@ struct AlbumParams { id: i32 } -/* #[component] pub fn AlbumPage() -> impl IntoView { let params = use_params::(); @@ -26,8 +26,8 @@ pub fn AlbumPage() -> impl IntoView { id, |value| async move { match value { - Ok(v) => {get_album(v).await}, - Err(e) => {Err(ServerFnError::Request("Invalid album!".into()))}, + Ok(v) => {get_songs(v).await}, + Err(e) => {Err(ServerFnError::Request(format!("Error getting song data: {}", e).into()))}, } }, ); @@ -40,7 +40,7 @@ pub fn AlbumPage() -> impl IntoView { song_list.with( |song_list| { match song_list { Some(Ok(s)) => { - view! { }.into_view() + view! { }.into_view() }, Some(Err(e)) => { view! {
"Error loading albums: :e"
}.into_view() @@ -52,4 +52,4 @@ pub fn AlbumPage() -> impl IntoView { } } -*/ + From 3b6035dd7180eef9bfbead01543678efeab06f92 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Wed, 20 Nov 2024 02:12:09 +0000 Subject: [PATCH 06/11] Album pages for users not signed in --- src/api/album.rs | 6 ++-- src/models.rs | 64 ++++++++++++++++++++++++++++-------------- src/pages/albumpage.rs | 2 +- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/api/album.rs b/src/api/album.rs index 33a5299..ff1f6cf 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -1,6 +1,5 @@ use leptos::*; use crate::models::Album; -use crate::models::Song; use crate::songdata::SongData; use cfg_if::cfg_if; @@ -23,10 +22,11 @@ pub async fn get_album(id: i32) -> Result { #[server(endpoint = "album/get_songs")] pub async fn get_songs(id: i32) -> Result, ServerFnError> { - let user = get_user().await?; + use crate::auth::get_logged_in_user; + let user = get_logged_in_user().await?; let db_con = &mut get_db_conn(); // TODO: NEEDS SONG DATA QUERIES - let songdata = Album::get_song_data(id,user.id.unwrap(),db_con) + let songdata = Album::get_song_data(id,user,db_con) .map_err(|e| ServerFnError::::ServerError(format!("Error getting song data: {}", e)))?; Ok(songdata) } \ No newline at end of file diff --git a/src/models.rs b/src/models.rs index 4a34b86..08dd956 100644 --- a/src/models.rs +++ b/src/models.rs @@ -561,30 +561,52 @@ impl Album { /// * `Result>` - A result indicating success with the desired album, or an error /// #[cfg(feature = "ssr")] - pub fn get_song_data(album_id: i32, user_like_dislike_id: i32, conn: &mut PgPooledConn) -> Result, Box> { + pub fn get_song_data(album_id: i32, user_like_dislike: Option, conn: &mut PgPooledConn) -> Result, Box> { use crate::schema::*; use crate::database::get_db_conn; use std::collections::HashMap; - - let songs: Vec<(Album, Option, Option, Option<(i32, i32)>, Option<(i32, i32)>)> = - albums::table - .find(album_id) - .left_join(songs::table.on(albums::id.nullable().eq(songs::album_id))) - .left_join(song_artists::table.inner_join(artists::table).on(songs::id.eq(song_artists::song_id))) - .left_join(song_likes::table.on(songs::id.eq(song_likes::song_id).and(song_likes::user_id.eq(user_like_dislike_id)))) - .left_join(song_dislikes::table.on(songs::id.eq(song_dislikes::song_id).and(song_dislikes::user_id.eq(user_like_dislike_id)))) - .select(( - albums::all_columns, - songs::all_columns.nullable(), - artists::all_columns.nullable(), - song_likes::all_columns.nullable(), - song_dislikes::all_columns.nullable() - )) - .load(conn)?; - - let mut album_songs: HashMap = HashMap::with_capacity(songs.len()); - for (album, song, artist, like, dislike) in songs { + let song_list = if let Some(user_like_dislike) = user_like_dislike { + let user_like_dislike_id = user_like_dislike.id.unwrap(); + let song_list: Vec<(Album, Option, Option, Option<(i32, i32)>, Option<(i32, i32)>)> = + albums::table + .find(album_id) + .left_join(songs::table.on(albums::id.nullable().eq(songs::album_id))) + .left_join(song_artists::table.inner_join(artists::table).on(songs::id.eq(song_artists::song_id))) + .left_join(song_likes::table.on(songs::id.eq(song_likes::song_id).and(song_likes::user_id.eq(user_like_dislike_id)))) + .left_join(song_dislikes::table.on(songs::id.eq(song_dislikes::song_id).and(song_dislikes::user_id.eq(user_like_dislike_id)))) + .select(( + albums::all_columns, + songs::all_columns.nullable(), + artists::all_columns.nullable(), + song_likes::all_columns.nullable(), + song_dislikes::all_columns.nullable() + )) + .order(songs::track.asc()) + .load(conn)?; + song_list + } else { + let song_list: Vec<(Album, Option, Option)> = + albums::table + .find(album_id) + .left_join(songs::table.on(albums::id.nullable().eq(songs::album_id))) + .left_join(song_artists::table.inner_join(artists::table).on(songs::id.eq(song_artists::song_id))) + .select(( + albums::all_columns, + songs::all_columns.nullable(), + artists::all_columns.nullable() + )) + .order(songs::track.asc()) + .load(conn)?; + + let song_list: Vec<(Album, Option, Option, Option<(i32, i32)>, Option<(i32, i32)>)> = + song_list.into_iter().map( |(album, song, artist)| (album, song, artist, None, None) ).collect(); + song_list + }; + + let mut album_songs: HashMap = HashMap::with_capacity(song_list.len()); + + for (album, song, artist, like, dislike) in song_list { if let Some(song) = song { let like_dislike = match (like, dislike) { (Some(_), Some(_)) => Some((true, true)), @@ -612,7 +634,7 @@ impl Album { album_songs.insert(song.id.unwrap(), songdata); } } - + // Sort the songs by date let mut songdata: Vec = album_songs.into_values().collect(); songdata.sort_by(|a, b| b.track.cmp(&a.track)); diff --git a/src/pages/albumpage.rs b/src/pages/albumpage.rs index 392cc9b..162f870 100644 --- a/src/pages/albumpage.rs +++ b/src/pages/albumpage.rs @@ -43,7 +43,7 @@ pub fn AlbumPage() -> impl IntoView { view! { }.into_view() }, Some(Err(e)) => { - view! {
"Error loading albums: :e"
}.into_view() + view! {
{format!("Error loading albums: : {}",e)}
}.into_view() }, None => {view! { }.into_view()} } From dd14aa0b4d742fd4e397bf177455c809a372eb3d Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Wed, 20 Nov 2024 04:43:53 +0000 Subject: [PATCH 07/11] AlbumData Query, API Endpoint, and Integration into AlbumPage --- src/albumdata.rs | 6 +++++- src/api/album.rs | 5 +++-- src/components.rs | 1 + src/components/album_info.rs | 27 ++++++++++++++++++++++++ src/models.rs | 40 +++++++++++++++++++++++++++++++----- src/pages/albumpage.rs | 33 +++++++++++++++++++++++++++-- 6 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 src/components/album_info.rs diff --git a/src/albumdata.rs b/src/albumdata.rs index 2b86b25..a621a8c 100644 --- a/src/albumdata.rs +++ b/src/albumdata.rs @@ -1,11 +1,15 @@ use crate::models::Artist; use crate::components::dashboard_tile::DashboardTile; +use crate::components::album_info::AlbumInfo; +use serde::{Serialize, Deserialize}; use chrono::NaiveDate; /// Holds information about an album /// /// Intended to be used in the front-end + +#[derive(Serialize, Deserialize, Clone)] pub struct AlbumData { /// Album id pub id: i32, @@ -36,4 +40,4 @@ impl DashboardTile for AlbumData { fn description(&self) -> Option { Some(format!("Album • {}", Artist::display_list(&self.artists))) } -} +} \ No newline at end of file diff --git a/src/api/album.rs b/src/api/album.rs index ff1f6cf..0bf9390 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -1,5 +1,6 @@ use leptos::*; use crate::models::Album; +use crate::albumdata::AlbumData; use crate::songdata::SongData; use cfg_if::cfg_if; @@ -13,9 +14,9 @@ cfg_if! { } #[server(endpoint = "album/get")] -pub async fn get_album(id: i32) -> Result { +pub async fn get_album(id: i32) -> Result { let db_con = &mut get_db_conn(); - let album = Album::get_album(id,db_con) + let album = Album::get_album_data(id,db_con) .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; Ok(album) } diff --git a/src/components.rs b/src/components.rs index 2624877..de281a7 100644 --- a/src/components.rs +++ b/src/components.rs @@ -8,3 +8,4 @@ pub mod upload; pub mod song_list; pub mod loading; pub mod error; +pub mod album_info; \ No newline at end of file diff --git a/src/components/album_info.rs b/src/components/album_info.rs new file mode 100644 index 0000000..b44a1f5 --- /dev/null +++ b/src/components/album_info.rs @@ -0,0 +1,27 @@ +use leptos::leptos_dom::*; +use leptos::*; +use crate::albumdata::AlbumData; + +#[component] +pub fn AlbumInfo(albumdata: AlbumData) -> impl IntoView { + view! { +
+
+ dashboard-tile +
+
+

{albumdata.title}

+
+ { + albumdata.artists.iter().map(|artist| { + view! { +

{artist.name.clone()}

+ } + }).collect::>() + } +
+
+
+ }.into_view() +} + diff --git a/src/models.rs b/src/models.rs index 08dd956..71c4180 100644 --- a/src/models.rs +++ b/src/models.rs @@ -2,6 +2,7 @@ use chrono::{NaiveDate, NaiveDateTime}; use leptos::{server, ServerFnError}; use serde::{Deserialize, Serialize}; use crate::songdata::SongData; +use crate::albumdata::AlbumData; use cfg_if::cfg_if; @@ -539,15 +540,44 @@ impl Album { /// * `Result>` - A result indicating success with the desired album, or an error /// #[cfg(feature = "ssr")] - pub fn get_album(album_id: i32, conn: &mut PgPooledConn) -> Result> { - use crate::schema::albums::dsl::*; + pub fn get_album_data(album_id: i32, conn: &mut PgPooledConn) -> Result> { + use crate::schema::*; use crate::database::get_db_conn; - let album = albums + let album: Vec<(Album, std::option::Option)> = albums::table .find(album_id) - .first(conn)?; + .left_join(songs::table.on(albums::id.nullable().eq(songs::album_id))) + .left_join(song_artists::table.inner_join(artists::table).on(songs::id.eq(song_artists::song_id))) + .select(( + albums::all_columns, + artists::all_columns.nullable() + )) + .distinct() + .load(conn)?; - Ok(album) + let mut artist_list: Vec = Vec::new(); + + for (_, artist) in album { + if let Some(artist) = artist { + artist_list.push(artist); + } + } + // Get info of album + let albuminfo = albums::table + .filter(albums::id.eq(album_id)) + .first::(conn)?; + + let img = albuminfo.image_path.unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()); + + let albumdata = AlbumData { + id: albuminfo.id.unwrap(), + title: albuminfo.title, + artists: artist_list, + release_date: albuminfo.release_date, + image_path: img + }; + + Ok(albumdata) } /// Obtain an album from its albumid diff --git a/src/pages/albumpage.rs b/src/pages/albumpage.rs index 162f870..62c4ed1 100644 --- a/src/pages/albumpage.rs +++ b/src/pages/albumpage.rs @@ -1,9 +1,10 @@ use leptos::leptos_dom::*; use leptos::*; use leptos_router::*; -use crate::models::*; +use crate::{albumdata, models::*}; use crate::components::song_list::*; use crate::api::album::*; +use crate::components::album_info::*; #[derive(Params, PartialEq)] @@ -32,7 +33,35 @@ pub fn AlbumPage() -> impl IntoView { }, ); + let albumdata = create_resource( + id, + |value| async move { + match value { + Ok(v) => {get_album(v).await}, + Err(e) => {Err(ServerFnError::Request(format!("Error getting song data: {}", e).into()))}, + } + }, + ); + view! { + "Loading..."

} + > + {move || { + albumdata.with( |albumdata| { + match albumdata { + Some(Ok(s)) => { + view! { } + }, + Some(Err(e)) => { + view! {
{format!("Error loading albums: : {}",e)}
}.into_view() + }, + None => {view! { }.into_view()} + } + }) + }} +
+ "Loading..."

} > @@ -40,7 +69,7 @@ pub fn AlbumPage() -> impl IntoView { song_list.with( |song_list| { match song_list { Some(Ok(s)) => { - view! { }.into_view() + view! { } }, Some(Err(e)) => { view! {
{format!("Error loading albums: : {}",e)}
}.into_view() From 21a17a8eb52ca3594099dabebc2e7f74b2680c74 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 22 Nov 2024 21:34:21 +0000 Subject: [PATCH 08/11] Album Page Styling --- src/components/album_info.rs | 14 +++---- src/pages/albumpage.rs | 74 +++++++++++++++++++----------------- style/album_page.scss | 67 ++++++++++++++++++++++++++++++++ style/main.scss | 1 + style/theme.scss | 3 +- 5 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 style/album_page.scss diff --git a/src/components/album_info.rs b/src/components/album_info.rs index b44a1f5..03b2950 100644 --- a/src/components/album_info.rs +++ b/src/components/album_info.rs @@ -5,17 +5,15 @@ use crate::albumdata::AlbumData; #[component] pub fn AlbumInfo(albumdata: AlbumData) -> impl IntoView { view! { -
-
- dashboard-tile -
-
-

{albumdata.title}

-
+
+ dashboard-tile +
+

{albumdata.title}

+
{ albumdata.artists.iter().map(|artist| { view! { -

{artist.name.clone()}

+

{artist.name.clone()}

} }).collect::>() } diff --git a/src/pages/albumpage.rs b/src/pages/albumpage.rs index 62c4ed1..47332ac 100644 --- a/src/pages/albumpage.rs +++ b/src/pages/albumpage.rs @@ -44,41 +44,45 @@ pub fn AlbumPage() -> impl IntoView { ); view! { - "Loading..."

} - > - {move || { - albumdata.with( |albumdata| { - match albumdata { - Some(Ok(s)) => { - view! { } - }, - Some(Err(e)) => { - view! {
{format!("Error loading albums: : {}",e)}
}.into_view() - }, - None => {view! { }.into_view()} - } - }) - }} -
- - "Loading..."

} - > - {move || { - song_list.with( |song_list| { - match song_list { - Some(Ok(s)) => { - view! { } - }, - Some(Err(e)) => { - view! {
{format!("Error loading albums: : {}",e)}
}.into_view() - }, - None => {view! { }.into_view()} - } - }) - }} -
+
+
+ "Loading..."

} + > + {move || { + albumdata.with( |albumdata| { + match albumdata { + Some(Ok(s)) => { + view! { } + }, + Some(Err(e)) => { + view! {
{format!("Error loading album : {}",e)}
}.into_view() + }, + None => {view! { }.into_view()} + } + }) + }} +
+
+ + "Loading..."

} + > + {move || { + song_list.with( |song_list| { + match song_list { + Some(Ok(s)) => { + view! { } + }, + Some(Err(e)) => { + view! {
{format!("Error loading albums: : {}",e)}
}.into_view() + }, + None => {view! { }.into_view()} + } + }) + }} +
+
} } diff --git a/style/album_page.scss b/style/album_page.scss new file mode 100644 index 0000000..55b306b --- /dev/null +++ b/style/album_page.scss @@ -0,0 +1,67 @@ +@import 'theme.scss'; + + + +.album-page-container { + width: 90vw; + + .album-header { + height: 40vh; + width: 65vw; + margin: auto; + + + padding:20px; + + background-image: linear-gradient($accent-color, $background-color); + border-radius: 15px; + + .album-info { + width: 100%; + height: 100%; + } + } +} + +.album-info { + display: flex; + flex-flow: row nowrap; + justify-content: space-around; + + .album-image { + max-width: 80%; + max-height: 80%; + box-shadow: 10px 10px 50px -10px $background-color; + } + + .album-body { + display: flex; + flex-flow: column nowrap; + justify-content: center; + + .album-title { + color: $text-controls-color; + font-size: 40px; + font-weight: bold; + margin:15px; + text-align: center; + } + + .album-artists { + display: flex; + flex-flow: row wrap; + justify-content: space-around; + align-content: space-around; + margin:15px; + + color: $text-controls-color; + font-size: 20px; + + .album-artist { + margin: 5px; + text-align: center; + } + } + } + +} \ No newline at end of file diff --git a/style/main.scss b/style/main.scss index a19abe7..e2be23c 100644 --- a/style/main.scss +++ b/style/main.scss @@ -15,6 +15,7 @@ @import 'song_list.scss'; @import 'profile.scss'; @import 'loading.scss'; +@import 'album_page.scss'; body { font-family: sans-serif; diff --git a/style/theme.scss b/style/theme.scss index b0cea43..0569ca6 100644 --- a/style/theme.scss +++ b/style/theme.scss @@ -10,10 +10,11 @@ $controls-click-color: #909090; $play-bar-background-color: #212121; $play-grad-start: #0a0533; $play-grad-end: $accent-color; +$border-color: #7851ed; $queue-background-color: $play-bar-background-color; $auth-inputs: #796dd4; $auth-containers: white; $dashboard-tile-size: 200px; -$playbar-size: 75px; +$playbar-size: 75px; \ No newline at end of file From 45eb7191f0800778bbf7fdde86083ee2ee5059bb Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 22 Nov 2024 21:47:24 +0000 Subject: [PATCH 09/11] Bugfix and small changes to styling --- src/models.rs | 55 ++++++++++++++++++++++++------------------- style/album_page.scss | 1 + 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/models.rs b/src/models.rs index 71c4180..15a97b4 100644 --- a/src/models.rs +++ b/src/models.rs @@ -638,30 +638,37 @@ impl Album { for (album, song, artist, like, dislike) in song_list { if let Some(song) = song { - let like_dislike = match (like, dislike) { - (Some(_), Some(_)) => Some((true, true)), - (Some(_), None) => Some((true, false)), - (None, Some(_)) => Some((false, true)), - _ => None, - }; - - let image_path = song.image_path.unwrap_or( - album.image_path.clone().unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string())); - - let songdata = SongData { - id: song.id.unwrap(), - title: song.title, - artists: artist.map(|artist| vec![artist]).unwrap_or_default(), - album: Some(album), - track: song.track, - duration: song.duration, - release_date: song.release_date, - song_path: song.storage_path, - image_path: image_path, - like_dislike: like_dislike, - }; - - album_songs.insert(song.id.unwrap(), songdata); + if let Some(stored_songdata) = album_songs.get_mut(&song.id.unwrap()) { + // If the song is already in the map, update the artists + if let Some(artist) = artist { + stored_songdata.artists.push(artist); + } + } else { + let like_dislike = match (like, dislike) { + (Some(_), Some(_)) => Some((true, true)), + (Some(_), None) => Some((true, false)), + (None, Some(_)) => Some((false, true)), + _ => None, + }; + + let image_path = song.image_path.unwrap_or( + album.image_path.clone().unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string())); + + let songdata = SongData { + id: song.id.unwrap(), + title: song.title, + artists: artist.map(|artist| vec![artist]).unwrap_or_default(), + album: Some(album), + track: song.track, + duration: song.duration, + release_date: song.release_date, + song_path: song.storage_path, + image_path: image_path, + like_dislike: like_dislike, + }; + + album_songs.insert(song.id.unwrap(), songdata); + } } } diff --git a/style/album_page.scss b/style/album_page.scss index 55b306b..d00439e 100644 --- a/style/album_page.scss +++ b/style/album_page.scss @@ -60,6 +60,7 @@ .album-artist { margin: 5px; text-align: center; + text-decoration: underline; } } } From e5ce0eab76722b679664cdb38bbeaa17a397011d Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 22 Nov 2024 21:55:59 +0000 Subject: [PATCH 10/11] Fix Build Warnings --- src/albumdata.rs | 1 - src/api/album.rs | 4 ++-- src/models.rs | 7 ++----- src/pages/albumpage.rs | 1 - 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/albumdata.rs b/src/albumdata.rs index a621a8c..f1481ae 100644 --- a/src/albumdata.rs +++ b/src/albumdata.rs @@ -1,6 +1,5 @@ use crate::models::Artist; use crate::components::dashboard_tile::DashboardTile; -use crate::components::album_info::AlbumInfo; use serde::{Serialize, Deserialize}; use chrono::NaiveDate; diff --git a/src/api/album.rs b/src/api/album.rs index 0bf9390..5c4d55e 100644 --- a/src/api/album.rs +++ b/src/api/album.rs @@ -1,5 +1,4 @@ use leptos::*; -use crate::models::Album; use crate::albumdata::AlbumData; use crate::songdata::SongData; @@ -9,12 +8,12 @@ cfg_if! { if #[cfg(feature = "ssr")] { use leptos::server_fn::error::NoCustomError; use crate::database::get_db_conn; - use crate::auth::get_user; } } #[server(endpoint = "album/get")] pub async fn get_album(id: i32) -> Result { + use crate::models::Album; let db_con = &mut get_db_conn(); let album = Album::get_album_data(id,db_con) .map_err(|e| ServerFnError::::ServerError(format!("Error getting album: {}", e)))?; @@ -23,6 +22,7 @@ pub async fn get_album(id: i32) -> Result { #[server(endpoint = "album/get_songs")] pub async fn get_songs(id: i32) -> Result, ServerFnError> { + use crate::models::Album; use crate::auth::get_logged_in_user; let user = get_logged_in_user().await?; let db_con = &mut get_db_conn(); diff --git a/src/models.rs b/src/models.rs index 15a97b4..40fd7f9 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,8 +1,5 @@ use chrono::{NaiveDate, NaiveDateTime}; -use leptos::{server, ServerFnError}; use serde::{Deserialize, Serialize}; -use crate::songdata::SongData; -use crate::albumdata::AlbumData; use cfg_if::cfg_if; @@ -11,6 +8,8 @@ cfg_if! { use diesel::prelude::*; use crate::database::*; use std::error::Error; + use crate::songdata::SongData; + use crate::albumdata::AlbumData; } } @@ -542,7 +541,6 @@ impl Album { #[cfg(feature = "ssr")] pub fn get_album_data(album_id: i32, conn: &mut PgPooledConn) -> Result> { use crate::schema::*; - use crate::database::get_db_conn; let album: Vec<(Album, std::option::Option)> = albums::table .find(album_id) @@ -593,7 +591,6 @@ impl Album { #[cfg(feature = "ssr")] pub fn get_song_data(album_id: i32, user_like_dislike: Option, conn: &mut PgPooledConn) -> Result, Box> { use crate::schema::*; - use crate::database::get_db_conn; use std::collections::HashMap; let song_list = if let Some(user_like_dislike) = user_like_dislike { diff --git a/src/pages/albumpage.rs b/src/pages/albumpage.rs index 47332ac..f65d71e 100644 --- a/src/pages/albumpage.rs +++ b/src/pages/albumpage.rs @@ -1,7 +1,6 @@ use leptos::leptos_dom::*; use leptos::*; use leptos_router::*; -use crate::{albumdata, models::*}; use crate::components::song_list::*; use crate::api::album::*; use crate::components::album_info::*; From 954cc0edce63f3baea8f6efae42fe52efc9af190 Mon Sep 17 00:00:00 2001 From: Aidan Westphal Date: Fri, 22 Nov 2024 22:13:43 +0000 Subject: [PATCH 11/11] Artist links and styling --- src/components/album_info.rs | 2 +- style/album_page.scss | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/album_info.rs b/src/components/album_info.rs index 03b2950..182cc75 100644 --- a/src/components/album_info.rs +++ b/src/components/album_info.rs @@ -13,7 +13,7 @@ pub fn AlbumInfo(albumdata: AlbumData) -> impl IntoView { { albumdata.artists.iter().map(|artist| { view! { -

{artist.name.clone()}

+ {artist.name.clone()} } }).collect::>() } diff --git a/style/album_page.scss b/style/album_page.scss index d00439e..b63e906 100644 --- a/style/album_page.scss +++ b/style/album_page.scss @@ -1,7 +1,5 @@ @import 'theme.scss'; - - .album-page-container { width: 90vw; @@ -65,4 +63,18 @@ } } + a { + color: $text-controls-color; + } + a:visited { + color: $text-controls-color; + } + a:hover { + color: $controls-hover-color; + } + + a:active { + color: $controls-click-color; + } + } \ No newline at end of file