Finish song page

This commit is contained in:
Ethan Girouard 2024-12-20 13:25:51 -05:00
parent 560fe0355d
commit 28b71df7e6
Signed by: eta357
GPG Key ID: 7BCDC36DFD11C146
2 changed files with 118 additions and 8 deletions

View File

@ -671,7 +671,7 @@ impl Album {
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))] #[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))]
#[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(Clone, Serialize, Deserialize)]
pub struct Song { pub struct Song {
/// A unique id for the song /// A unique id for the song
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]

View File

@ -3,11 +3,18 @@ use leptos_router::use_params_map;
use leptos_icons::*; use leptos_icons::*;
use server_fn::error::NoCustomError; use server_fn::error::NoCustomError;
use crate::api::songs;
use crate::components::loading::*; use crate::components::loading::*;
use crate::components::error::*; use crate::components::error::*;
use crate::api::song::*; use crate::components::song_list::*;
use crate::models::Song; use crate::api::songs::*;
use crate::songs::get_song_by_id; use crate::songdata::SongData;
use crate::util::state::GlobalState;
use std::rc::Rc;
use std::borrow::Borrow;
const PLAY_BTN_SIZE: &str = "3rem";
#[component] #[component]
pub fn SongPage() -> impl IntoView { pub fn SongPage() -> impl IntoView {
@ -76,17 +83,120 @@ fn SongDetails(#[prop(into)] id: MaybeSignal<i32>) -> impl IntoView {
} }
})} })}
</Transition> </Transition>
<SongPlays id />
<MySongPlays id />
} }
} }
#[component] #[component]
fn SongOverview(song: Song) -> impl IntoView { fn SongOverview(song: SongData) -> impl IntoView {
let liked = create_rw_signal(song.like_dislike.map(|ld| ld.0).unwrap_or(false));
let disliked = create_rw_signal(song.like_dislike.map(|ld| ld.1).unwrap_or(false));
let playing = create_rw_signal(false);
let icon = Signal::derive(move || {
if playing.get() {
icondata::BsPauseFill
} else {
icondata::BsPlayFill
}
});
create_effect(move |_| {
GlobalState::play_status().with(|status| {
playing.set(status.queue.front().map(|song| song.id) == Some(song.id) && status.playing);
});
});
let song_rc = Rc::new(song.clone());
let toggle_play_song = move |_| {
GlobalState::play_status().update(|status| {
if status.queue.front().map(|song| song.id) == Some(song_rc.id) {
status.playing = !status.playing;
} else {
if let Some(last_playing) = status.queue.front() {
status.queue.push_front(last_playing.clone());
}
status.queue.clear();
status.queue.push_front(<Rc<SongData> as Borrow<SongData>>::borrow(&song_rc).clone());
status.playing = true;
}
});
};
view! { view! {
<div class="song-header"> <div class="song-header">
<img class="song-image" src=song.image_path />
<h1>{song.title}</h1> <h1>{song.title}</h1>
<p>{format!("Artist: {}", song.artist)}</p>
<p>{format!("Album: {}", song.album.unwrap_or_else(|| "Unknown".to_string()))}</p>
<p>{format!("Duration: {}", song.duration)}</p>
</div> </div>
<div class="song-actions">
<button on:click=toggle_play_song>
<Icon class="controlbtn" width=PLAY_BTN_SIZE height=PLAY_BTN_SIZE icon />
</button>
<SongLikeDislike song_id=song.id liked disliked /><br/>
</div>
<p><SongArtists artists=song.artists /></p>
<p><SongAlbum album=song.album /></p>
<p>{format!("Duration: {}:{:02}", song.duration / 60, song.duration % 60)}</p>
}
}
#[component]
fn SongPlays(#[prop(into)] id: MaybeSignal<i32>) -> impl IntoView {
let plays = create_resource(move || id.get(), move |id| songs::get_song_plays(id));
view! {
<Transition
fallback=move || view! { <Loading /> }
>
{move || plays.get().map(|plays| {
match plays {
Ok(plays) => {
view! {
<p>{format!("Plays: {}", plays)}</p>
}.into_view()
},
Err(error) => {
view! {
<ServerError<NoCustomError>
title="Error fetching song plays"
error
/>
}.into_view()
}
}
})}
</Transition>
}
}
#[component]
fn MySongPlays(#[prop(into)] id: MaybeSignal<i32>) -> impl IntoView {
let plays = create_resource(move || id.get(), move |id| songs::get_my_song_plays(id));
view! {
<Transition
fallback=move || view! { <Loading /> }
>
{move || plays.get().map(|plays| {
match plays {
Ok(plays) => {
view! {
<p>{format!("My Plays: {}", plays)}</p>
}.into_view()
},
Err(error) => {
view! {
<ServerError<NoCustomError>
title="Error fetching my song plays"
error
/>
}.into_view()
}
}
})}
</Transition>
} }
} }