Merge pull request 'Implement play/pause for songs in SongList' (#145) from 122-implement-playpause-for-songs-in-songlist into main
Reviewed-on: LibreTunes/LibreTunes#145
This commit is contained in:
commit
190df0edfd
@ -1,3 +1,5 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use leptos::*;
|
||||
use leptos::logging::*;
|
||||
use leptos_icons::*;
|
||||
@ -5,59 +7,82 @@ use leptos_icons::*;
|
||||
use crate::api::songs::*;
|
||||
use crate::songdata::SongData;
|
||||
use crate::models::{Album, Artist};
|
||||
use crate::util::state::GlobalState;
|
||||
|
||||
const LIKE_DISLIKE_BTN_SIZE: &str = "2em";
|
||||
|
||||
#[component]
|
||||
pub fn SongList(songs: MaybeSignal<Vec<SongData>>) -> impl IntoView {
|
||||
view! {
|
||||
<table class="song-list">
|
||||
{
|
||||
songs.with(|songs| {
|
||||
let mut first_song = true;
|
||||
|
||||
songs.iter().map(|song| {
|
||||
let playing = first_song.into();
|
||||
first_song = false;
|
||||
|
||||
let extra = Option::<()>::None;
|
||||
|
||||
view! {
|
||||
<SongListItem song={song.clone()} song_playing=playing extra />
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
</table>
|
||||
}
|
||||
pub fn SongList(songs: Vec<SongData>) -> impl IntoView {
|
||||
__SongListInner(songs.into_iter().map(|song| (song, ())).collect::<Vec<_>>(), false)
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SongListExtra<T>(songs: MaybeSignal<Vec<(SongData, T)>>) -> impl IntoView where
|
||||
pub fn SongListExtra<T>(songs: Vec<(SongData, T)>) -> impl IntoView where
|
||||
T: Clone + IntoView + 'static
|
||||
{
|
||||
__SongListInner(songs, true)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn SongListInner<T>(songs: Vec<(SongData, T)>, show_extra: bool) -> impl IntoView where
|
||||
T: Clone + IntoView + 'static
|
||||
{
|
||||
let songs = Rc::new(songs);
|
||||
let songs_2 = songs.clone();
|
||||
|
||||
// Signal that acts as a callback for a song list item to queue songs after it in the list
|
||||
let (handle_queue_remaining, do_queue_remaining) = create_signal(None);
|
||||
create_effect(move |_| {
|
||||
let clicked_index = handle_queue_remaining.get();
|
||||
|
||||
if let Some(index) = clicked_index {
|
||||
GlobalState::play_status().update(|status| {
|
||||
let song: &(SongData, T) = songs.get(index).expect("Invalid song list item index");
|
||||
|
||||
if status.queue.front().map(|song| song.id) == Some(song.0.id) {
|
||||
// If the clicked song is already at the front of the queue, just play it
|
||||
status.playing = true;
|
||||
} else {
|
||||
// Otherwise, add the currently playing song to the history,
|
||||
// clear the queue, and queue the clicked song and other after it
|
||||
if let Some(last_playing) = status.queue.pop_front() {
|
||||
status.history.push_back(last_playing);
|
||||
}
|
||||
|
||||
status.queue.clear();
|
||||
status.queue.extend(songs.iter().skip(index).map(|(song, _)| song.clone()));
|
||||
status.playing = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<table class="song-list">
|
||||
{
|
||||
songs.with(|songs| {
|
||||
let mut first_song = true;
|
||||
songs_2.iter().enumerate().map(|(list_index, (song, extra))| {
|
||||
let song_id = song.id;
|
||||
let playing = create_rw_signal(false);
|
||||
|
||||
songs.iter().map(|(song, extra)| {
|
||||
let playing = first_song.into();
|
||||
first_song = false;
|
||||
create_effect(move |_| {
|
||||
GlobalState::play_status().with(|status| {
|
||||
playing.set(status.queue.front().map(|song| song.id) == Some(song_id) && status.playing);
|
||||
});
|
||||
});
|
||||
|
||||
view! {
|
||||
<SongListItem song={song.clone()} song_playing=playing extra=Some(extra.clone()) />
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
})
|
||||
view! {
|
||||
<SongListItem song={song.clone()} song_playing=playing.into()
|
||||
extra={if show_extra { Some(extra.clone()) } else { None }} list_index do_queue_remaining/>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
</table>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SongListItem<T>(song: SongData, song_playing: MaybeSignal<bool>, extra: Option<T>) -> impl IntoView where
|
||||
pub fn SongListItem<T>(song: SongData, song_playing: MaybeSignal<bool>, extra: Option<T>,
|
||||
list_index: usize, do_queue_remaining: WriteSignal<Option<usize>>) -> impl IntoView where
|
||||
T: IntoView + 'static
|
||||
{
|
||||
let liked = create_rw_signal(song.like_dislike.map(|(liked, _)| liked).unwrap_or(false));
|
||||
@ -65,7 +90,8 @@ pub fn SongListItem<T>(song: SongData, song_playing: MaybeSignal<bool>, extra: O
|
||||
|
||||
view! {
|
||||
<tr class="song-list-item">
|
||||
<td class="song-image"><SongImage image_path=song.image_path song_playing /></td>
|
||||
<td class="song-image"><SongImage image_path=song.image_path song_playing
|
||||
list_index do_queue_remaining /></td>
|
||||
<td class="song-title"><p>{song.title}</p></td>
|
||||
<td class="song-list-spacer"></td>
|
||||
<td class="song-artists"><SongArtists artists=song.artists /></td>
|
||||
@ -85,13 +111,27 @@ pub fn SongListItem<T>(song: SongData, song_playing: MaybeSignal<bool>, extra: O
|
||||
/// 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<bool>) -> impl IntoView {
|
||||
fn SongImage(image_path: String, song_playing: MaybeSignal<bool>, list_index: usize,
|
||||
do_queue_remaining: WriteSignal<Option<usize>>) -> impl IntoView
|
||||
{
|
||||
let play_song = move |_| {
|
||||
do_queue_remaining.set(Some(list_index));
|
||||
};
|
||||
|
||||
let pause_song = move |_| {
|
||||
GlobalState::play_status().update(|status| {
|
||||
status.playing = false;
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<img class="song-image" src={image_path}/>
|
||||
{if song_playing.get() {
|
||||
view! { <Icon class="song-image-overlay song-playing-overlay" icon=icondata::BsPauseFill /> }.into_view()
|
||||
{move || if song_playing.get() {
|
||||
view! { <Icon class="song-image-overlay song-playing-overlay"
|
||||
icon=icondata::BsPauseFill on:click=pause_song /> }.into_view()
|
||||
} else {
|
||||
view! { <Icon class="song-image-overlay hide-until-hover" icon=icondata::BsPlayFill /> }.into_view()
|
||||
view! { <Icon class="song-image-overlay hide-until-hover"
|
||||
icon=icondata::BsPlayFill on:click=play_song /> }.into_view()
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ fn TopSongs(#[prop(into)] user_id: MaybeSignal<i32>) -> impl IntoView {
|
||||
top_songs.get().map(|top_songs| {
|
||||
top_songs.map(|top_songs| {
|
||||
view! {
|
||||
<SongListExtra songs={top_songs.into()} />
|
||||
<SongListExtra songs=top_songs />
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -255,7 +255,7 @@ fn RecentSongs(#[prop(into)] user_id: MaybeSignal<i32>) -> impl IntoView {
|
||||
recent_songs.get().map(|recent_songs| {
|
||||
recent_songs.map(|recent_songs| {
|
||||
view! {
|
||||
<SongList songs={recent_songs.into()} />
|
||||
<SongList songs=recent_songs />
|
||||
}
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user