Remove old song list component
All checks were successful
Push Workflows / rustfmt (push) Successful in 10s
Push Workflows / mdbook (push) Successful in 14s
Push Workflows / docs (push) Successful in 2m40s
Push Workflows / mdbook-server (push) Successful in 2m30s
Push Workflows / leptos-test (push) Successful in 3m2s
Push Workflows / test (push) Successful in 3m13s
Push Workflows / clippy (push) Successful in 3m23s
Push Workflows / docker-build (push) Successful in 5m49s
Push Workflows / build (push) Successful in 6m58s
Push Workflows / nix-build (push) Successful in 7m49s
All checks were successful
Push Workflows / rustfmt (push) Successful in 10s
Push Workflows / mdbook (push) Successful in 14s
Push Workflows / docs (push) Successful in 2m40s
Push Workflows / mdbook-server (push) Successful in 2m30s
Push Workflows / leptos-test (push) Successful in 3m2s
Push Workflows / test (push) Successful in 3m13s
Push Workflows / clippy (push) Successful in 3m23s
Push Workflows / docker-build (push) Successful in 5m49s
Push Workflows / build (push) Successful in 6m58s
Push Workflows / nix-build (push) Successful in 7m49s
This commit is contained in:
@@ -14,7 +14,6 @@ pub mod playbar;
|
||||
pub mod queue;
|
||||
pub mod sidebar;
|
||||
pub mod song;
|
||||
pub mod song_list;
|
||||
pub mod songs;
|
||||
pub mod upload;
|
||||
pub mod upload_dropdown;
|
||||
@@ -38,9 +37,6 @@ pub mod all {
|
||||
pub use queue::Queue;
|
||||
pub use sidebar::{Playlists, Sidebar};
|
||||
pub use song::Song;
|
||||
pub use song_list::{
|
||||
SongAlbum, SongArtists, SongImage, SongLikeDislike, SongList, SongListExtra, SongListItem,
|
||||
};
|
||||
pub use songs::all::*;
|
||||
pub use upload::{Album, Artist, Upload, UploadBtn};
|
||||
pub use upload_dropdown::{UploadDropdown, UploadDropdownBtn};
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
const LIKE_DISLIKE_BTN_SIZE: &str = "2em";
|
||||
|
||||
#[component]
|
||||
pub fn SongList(songs: Vec<frontend::Song>) -> impl IntoView {
|
||||
let songs = songs.into_iter().map(|song| (song, ())).collect::<Vec<_>>();
|
||||
|
||||
view! {
|
||||
<SongListInner _songs=songs _show_extra=false />
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SongListExtra<T>(songs: Vec<(frontend::Song, T)>) -> impl IntoView
|
||||
where
|
||||
T: Clone + IntoView + 'static,
|
||||
{
|
||||
view! {
|
||||
<SongListInner _songs=songs _show_extra=true />
|
||||
}
|
||||
}
|
||||
|
||||
// TODO these arguments shouldn't need a leading underscore,
|
||||
// but for some reason the compiler thinks they are unused
|
||||
#[component]
|
||||
fn SongListInner<T>(_songs: Vec<(frontend::Song, 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) = signal(None);
|
||||
Effect::new(move |_| {
|
||||
let clicked_index = handle_queue_remaining.get();
|
||||
|
||||
if let Some(index) = clicked_index {
|
||||
GlobalState::play_status().update(|status| {
|
||||
let song: &(frontend::Song, 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="w-full">
|
||||
<tbody>
|
||||
{
|
||||
songs_2.iter().enumerate().map(|(list_index, (song, extra))| {
|
||||
let song_id = song.id;
|
||||
let playing = RwSignal::new(false);
|
||||
|
||||
Effect::new(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.into()
|
||||
extra={if _show_extra { Some(extra.clone()) } else { None }} list_index do_queue_remaining/>
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SongListItem<T>(
|
||||
song: frontend::Song,
|
||||
song_playing: Signal<bool>,
|
||||
extra: Option<T>,
|
||||
list_index: usize,
|
||||
do_queue_remaining: WriteSignal<Option<usize>>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
T: IntoView + 'static,
|
||||
{
|
||||
let liked = RwSignal::new(song.like_dislike.map(|(liked, _)| liked).unwrap_or(false));
|
||||
let disliked = RwSignal::new(
|
||||
song.like_dislike
|
||||
.map(|(_, disliked)| disliked)
|
||||
.unwrap_or(false),
|
||||
);
|
||||
|
||||
view! {
|
||||
<tr class="group border-b border-t border-neutral-600 last-of-type:border-b-0
|
||||
first-of-type:border-t-0 hover:bg-neutral-700 [&>*]:px-2">
|
||||
<td class="relative w-13 h-13"><SongImage image_path=song.image_path.path() song_playing
|
||||
list_index do_queue_remaining /></td>
|
||||
<td><p>{song.title}</p></td>
|
||||
<td></td>
|
||||
<td><SongArtists artists=song.artists /></td>
|
||||
<td></td>
|
||||
<td><SongAlbum album=song.album /></td>
|
||||
<td></td>
|
||||
<td><SongLikeDislike song_id=song.id liked disliked/></td>
|
||||
<td>{format!("{}:{:02}", song.duration / 60, song.duration % 60)}</td>
|
||||
{extra.map(|extra| view! {
|
||||
<td></td>
|
||||
<td>{extra}</td>
|
||||
})}
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn SongImage(
|
||||
image_path: String,
|
||||
song_playing: Signal<bool>,
|
||||
list_index: usize,
|
||||
do_queue_remaining: WriteSignal<Option<usize>>,
|
||||
) -> impl IntoView {
|
||||
let toggle_play = move |_| {
|
||||
if song_playing.get() {
|
||||
GlobalState::play_status().update(|status| {
|
||||
status.playing = false;
|
||||
});
|
||||
} else {
|
||||
do_queue_remaining.set(Some(list_index));
|
||||
}
|
||||
};
|
||||
|
||||
let icon = Signal::derive(move || {
|
||||
if song_playing.get() {
|
||||
icondata::BsPauseFill
|
||||
} else {
|
||||
icondata::BsPlayFill
|
||||
}
|
||||
});
|
||||
|
||||
let style = Signal::derive(move || {
|
||||
if song_playing.get() {
|
||||
"w-6 h-6 absolute top-1/2 left-1/2 translate-[-50%]"
|
||||
} else {
|
||||
"w-6 h-6 opacity-0 group-hover:opacity-100 absolute top-1/2 left-1/2 translate-[-50%]"
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<img class="group-hover:brightness-45" src={image_path}/>
|
||||
<Icon icon on:click={toggle_play} {..} class=style />
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a song's artists, with links to their artist pages
|
||||
#[component]
|
||||
pub fn SongArtists(artists: Vec<backend::Artist>) -> impl IntoView {
|
||||
let num_artists = artists.len() as isize;
|
||||
|
||||
artists
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, artist)| {
|
||||
let i = i as isize;
|
||||
|
||||
view! {
|
||||
<a class="hover:underline active:text-controls-active"
|
||||
href={format!("/artist/{}", artist.id)}>{artist.name.clone()}</a>
|
||||
{
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match i.cmp(&(num_artists - 2)) {
|
||||
Ordering::Less => ", ",
|
||||
Ordering::Equal => " & ",
|
||||
Ordering::Greater => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Display a song's album, with a link to the album page
|
||||
#[component]
|
||||
pub fn SongAlbum(album: Option<backend::Album>) -> impl IntoView {
|
||||
album.as_ref().map(|album| {
|
||||
view! {
|
||||
<span>
|
||||
<a class="hover:underline active:text-controls-active"
|
||||
href={format!("/album/{}", album.id)}>{album.title.clone()}</a>
|
||||
</span>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Display like and dislike buttons for a song, and indicate if the song is liked or disliked
|
||||
#[component]
|
||||
pub fn SongLikeDislike(
|
||||
#[prop(into)] song_id: Signal<i32>,
|
||||
liked: RwSignal<bool>,
|
||||
disliked: RwSignal<bool>,
|
||||
) -> 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 = Signal::derive(move || {
|
||||
if liked.get() {
|
||||
""
|
||||
} else {
|
||||
"opacity-0 group-hover:opacity-100"
|
||||
}
|
||||
});
|
||||
|
||||
let dislike_class = Signal::derive(move || {
|
||||
if disliked.get() {
|
||||
""
|
||||
} else {
|
||||
"opacity-0 group-hover:opacity-100"
|
||||
}
|
||||
});
|
||||
|
||||
// If an error occurs, check the like/dislike status again to ensure consistency
|
||||
let check_like_dislike = move || {
|
||||
spawn_local(async move {
|
||||
if let Ok((like, dislike)) =
|
||||
api::songs::get_like_dislike_song(song_id.get_untracked()).await
|
||||
{
|
||||
liked.set(like);
|
||||
disliked.set(dislike);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let toggle_like = move |_| {
|
||||
let new_liked = !liked.get_untracked();
|
||||
liked.set(new_liked);
|
||||
disliked.set(disliked.get_untracked() && !liked.get_untracked());
|
||||
|
||||
spawn_local(async move {
|
||||
match api::songs::set_like_song(song_id.get_untracked(), new_liked).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
leptos_err!("Error setting like: {}", e);
|
||||
check_like_dislike();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let toggle_dislike = move |_| {
|
||||
disliked.set(!disliked.get_untracked());
|
||||
liked.set(liked.get_untracked() && !disliked.get_untracked());
|
||||
|
||||
spawn_local(async move {
|
||||
match api::songs::set_dislike_song(song_id.get_untracked(), disliked.get_untracked())
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
leptos_err!("Error setting dislike: {}", e);
|
||||
check_like_dislike();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<button class="control scale-x-[-1]" on:click=toggle_dislike>
|
||||
<Icon width=LIKE_DISLIKE_BTN_SIZE height=LIKE_DISLIKE_BTN_SIZE icon={dislike_icon} {..} class=dislike_class />
|
||||
</button>
|
||||
<button class="control" on:click=toggle_like>
|
||||
<Icon width=LIKE_DISLIKE_BTN_SIZE height=LIKE_DISLIKE_BTN_SIZE icon={like_icon} {..} class=like_class />
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,7 @@ pub fn default_song_list_content(
|
||||
view! {
|
||||
<SongListIndex list_index />
|
||||
|
||||
// TODO remote module path when able
|
||||
<super::song::SongImage
|
||||
<SongImage
|
||||
song={song.clone()}
|
||||
song_playing={Signal::stored(false)}
|
||||
list_index
|
||||
@@ -75,8 +74,7 @@ pub fn queue_content(
|
||||
view! {
|
||||
<SongListIndex list_index />
|
||||
|
||||
// TODO remote module path when able
|
||||
<super::song::SongImage
|
||||
<SongImage
|
||||
song={song.clone()}
|
||||
list_index
|
||||
play_callback
|
||||
@@ -123,8 +121,7 @@ pub fn album_content(
|
||||
view! {
|
||||
<SongListIndex list_index />
|
||||
|
||||
// TODO remote module path when able
|
||||
<super::song::SongImage
|
||||
<SongImage
|
||||
song={song.clone()}
|
||||
list_index
|
||||
play_callback
|
||||
@@ -154,8 +151,7 @@ where
|
||||
S: DisplaySongList<RowArgs = (usize, PlayCallback, frontend::Song)>,
|
||||
{
|
||||
view! {
|
||||
// TODO remote module path when able
|
||||
<super::song_list::SongList
|
||||
<SongList
|
||||
song_ids
|
||||
headers={queue_headers()}
|
||||
cols={queue_content}
|
||||
@@ -169,8 +165,7 @@ where
|
||||
S: DisplaySongList<RowArgs = (usize, PlayCallback, frontend::Song)>,
|
||||
{
|
||||
view! {
|
||||
// TODO remote module path when able
|
||||
<super::song_list::SongList
|
||||
<SongList
|
||||
song_ids
|
||||
headers={album_headers()}
|
||||
cols={album_content}
|
||||
|
||||
Reference in New Issue
Block a user