diff --git a/src/components.rs b/src/components.rs index 29ac621..893727c 100644 --- a/src/components.rs +++ b/src/components.rs @@ -5,3 +5,4 @@ pub mod personal; pub mod dashboard_tile; pub mod dashboard_row; pub mod upload; +pub mod song_list; diff --git a/src/components/song_list.rs b/src/components/song_list.rs new file mode 100644 index 0000000..2ace92a --- /dev/null +++ b/src/components/song_list.rs @@ -0,0 +1,157 @@ +use leptos::*; +use leptos_icons::*; + +use crate::songdata::SongData; +use crate::models::{Album, Artist}; + +const LIKE_DISLIKE_BTN_SIZE: &str = "2em"; + +#[component] +pub fn SongList(songs: MaybeSignal>) -> impl IntoView { + view! { + + { + songs.with(|songs| { + let mut first_song = true; + + songs.iter().map(|song| { + let playing = first_song.into(); + first_song = false; + + view! { + + } + }).collect::>() + }) + } +
+ } +} + +#[component] +pub fn SongListItem(song: SongData, song_playing: MaybeSignal) -> impl IntoView { + let liked = create_rw_signal(song.like_dislike.map(|(liked, _)| liked).unwrap_or(false)); + let disliked = create_rw_signal(song.like_dislike.map(|(_, disliked)| disliked).unwrap_or(false)); + + view! { + + +

{song.title}

+ + + + + + + {format!("{}:{:02}", song.duration / 60, song.duration % 60)} + + } +} + +/// Display the song's image, with an overlay if the song is playing +/// When the song list item is hovered, the overlay will show the play button +#[component] +fn SongImage(image_path: String, song_playing: MaybeSignal) -> impl IntoView { + view! { + + {if song_playing.get() { + view! { }.into_view() + } else { + view! { }.into_view() + }} + } +} + +/// Displays a song's artists, with links to their artist pages +#[component] +fn SongArtists(artists: Vec) -> impl IntoView { + let num_artists = artists.len() as isize; + + artists.iter().enumerate().map(|(i, artist)| { + let i = i as isize; + + view! { + { + if let Some(id) = artist.id { + view! { {artist.name.clone()} }.into_view() + } else { + view! { {artist.name.clone()} }.into_view() + } + } + {if i < num_artists - 2 { ", " } else if i == num_artists - 2 { " & " } else { "" }} + } + }).collect::>() +} + +/// Display a song's album, with a link to the album page +#[component] +fn SongAlbum(album: Option) -> impl IntoView { + album.as_ref().map(|album| { + view! { + + { + if let Some(id) = album.id { + view! { {album.title.clone()} }.into_view() + } else { + view! { {album.title.clone()} }.into_view() + } + } + + } + }) +} + +/// Display like and dislike buttons for a song, and indicate if the song is liked or disliked +#[component] +fn SongLikeDislike(liked: RwSignal, disliked: RwSignal) -> impl IntoView { + let like_icon = Signal::derive(move || { + if liked.get() { + icondata::TbThumbUpFilled + } else { + icondata::TbThumbUp + } + }); + + let dislike_icon = Signal::derive(move || { + if disliked.get() { + icondata::TbThumbDownFilled + } else { + icondata::TbThumbDown + } + }); + + let like_class = MaybeProp::derive(move || { + if liked.get() { + Some(TextProp::from("controlbtn")) + } else { + Some(TextProp::from("controlbtn hide-until-hover")) + } + }); + + let dislike_class = MaybeProp::derive(move || { + if disliked.get() { + Some(TextProp::from("controlbtn hmirror")) + } else { + Some(TextProp::from("controlbtn hmirror hide-until-hover")) + } + }); + + let toggle_like = move |_| { + liked.set(!liked.get_untracked()); + disliked.set(disliked.get_untracked() && !liked.get_untracked()); + }; + + let toggle_dislike = move |_| { + disliked.set(!disliked.get_untracked()); + liked.set(liked.get_untracked() && !disliked.get_untracked()); + }; + + view! { + + + } +} diff --git a/src/songdata.rs b/src/songdata.rs index 6851a85..36e5679 100644 --- a/src/songdata.rs +++ b/src/songdata.rs @@ -6,6 +6,7 @@ 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)] pub struct SongData { /// Song id pub id: i32, diff --git a/style/main.scss b/style/main.scss index 1a80b34..042e605 100644 --- a/style/main.scss +++ b/style/main.scss @@ -11,6 +11,7 @@ @import 'dashboard_tile.scss'; @import 'dashboard_row.scss'; @import 'upload.scss'; +@import 'song_list.scss'; body { font-family: sans-serif; diff --git a/style/song_list.scss b/style/song_list.scss new file mode 100644 index 0000000..88904ab --- /dev/null +++ b/style/song_list.scss @@ -0,0 +1,124 @@ +table.song-list { + width: 100%; + border-collapse: collapse; + + tr.song-list-item { + border: solid; + border-width: 1px 0; + border-color: #303030; + position: relative; + + td { + color: $text-controls-color; + white-space: nowrap; + padding-left: 10px; + padding-right: 10px; + + a { + text-decoration: none; + color: $text-controls-color; + } + } + + a:hover { + text-decoration: underline $controls-hover-color; + } + + td.song-image { + width: 35px; + display: flex; + + img.song-image { + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); + width: 35px; + height: 35px; + border-radius: 5px; + } + + svg.song-image-overlay { + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); + width: 35px; + height: 35px; + border-radius: 5px; + fill: $text-controls-color; + } + + svg.song-image-overlay:hover { + fill: $controls-hover-color; + } + + svg.song-image-overlay:active { + fill: $controls-click-color; + } + } + + td.song-list-spacer { + width: 20%; + } + + td.song-list-spacer-big { + width: 40%; + } + + button { + svg.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; + } + + .controlbtn:hover { + color: $controls-hover-color; + } + + .controlbtn:active { + color: $controls-click-color; + } + + background-color: transparent; + border: transparent; + } + + .hide-until-hover { + visibility: hidden; + } + + .song-playing-overlay { + background-color: rgba(0, 0, 0, 0.8); + } + } + + tr.song-list-item:first-child { + border-top: none; + } + + tr.song-list-item:last-child { + border-bottom: none; + } + + tr.song-list-item:hover { + background-color: #303030; + + .hide-until-hover { + visibility: visible; + } + + td.song-image { + svg.song-image-overlay { + background-color: rgba(0, 0, 0, 0.8); + } + } + } +}