Display playlists on sidebar

This commit is contained in:
2025-05-06 01:34:44 +00:00
parent 0e0d107d08
commit c17aeb3822

View File

@ -1,6 +1,15 @@
use crate::components::error::Error;
use crate::components::loading::*;
use crate::components::menu::*;
use crate::util::state::GlobalState;
use leptos::html::Div;
use leptos::prelude::*;
use leptos_icons::*;
use leptos_router::components::{Form, A};
use leptos_router::hooks::use_location;
use leptos_use::{on_click_outside_with_options, OnClickOutsideOptions};
use std::sync::Arc;
use web_sys::Response;
#[component]
pub fn Sidebar(
@ -9,7 +18,7 @@ pub fn Sidebar(
add_album_open: RwSignal<bool>,
) -> impl IntoView {
view! {
<div class="flex flex-col">
<div class="flex flex-col w-[250px] min-w-[250px]">
<Menu upload_open add_artist_open add_album_open />
<Playlists />
</div>
@ -17,18 +26,143 @@ pub fn Sidebar(
}
#[component]
pub fn Playlists() -> impl IntoView {
fn AddPlaylistDialog(open: RwSignal<bool>, node_ref: NodeRef<Div>) -> impl IntoView {
let playlist_name = RwSignal::new("".to_string());
let loading = RwSignal::new(false);
let error_msg = RwSignal::new(None);
let handle_response = Arc::new(move |response: &Response| {
loading.set(false);
if response.ok() {
open.set(false);
GlobalState::playlists().refetch();
} else {
error_msg.set(Some("Failed to create playlist".to_string()));
}
});
view! {
<div class="home-card">
<div class="flex">
<h1 class="header">Playlists</h1>
<button class="add-playlist">
<div class="add-sign">
<Icon icon={icondata::IoAddSharp} />
<dialog class="fixed top-0 left-0 w-full h-full bg-black/50 flex items-center justify-center" class:open=open>
<div node_ref=node_ref class="bg-neutral-800 rounded-lg p-4 w-1/3 text-white">
<div class="flex items-center pb-3">
<h1 class="text-2xl">"Create Playlist"</h1>
<button id="add-playlist-dialog-btn" class="control ml-auto" on:click=move |_| open.set(false)>
<Icon icon={icondata::IoClose} {..} class="w-7 h-7" />
</button>
</div>
<Form action="/api/playlists/create" on_response=handle_response.clone()
method="POST" enctype="multipart/form-data".to_string()>
<div class="grid grid-cols-[auto_1fr] gap-4">
<label for="new-playlist-name">"Playlist Name"</label>
<input id="new-playlist-name" name="name"
class="bg-neutral-800 text-neutral-200 border border-neutral-600 rounded-lg p-2 outline-none"
type="text" placeholder="My Playlist" bind:value=playlist_name required autocomplete="off" />
<label for="new-playlist-img">"Cover Image"</label>
<input id="new-playlist-img" name="picture" type="file" accept="image/*" />
</div>
New Playlist
</button>
{move || {
error_msg.get().map(|error| {
view! {
<Error<String>
message=error.clone()
/>
}
})
}}
<div class="flex justify-end">
<button type="submit" class="control-solid" on:click=move |_| {
error_msg.set(None);
loading.set(true);
}>
"Create"
</button>
</div>
</Form>
</div>
</div>
</dialog>
}
}
#[component]
pub fn Playlists() -> impl IntoView {
let location = use_location();
let add_playlist_open = RwSignal::new(false);
let create_playlist = move |_| {
leptos::logging::log!("Creating playlist");
add_playlist_open.set(true);
};
let add_playlist_dialog = NodeRef::<Div>::new();
let _dialog_close_handler = on_click_outside_with_options(
add_playlist_dialog,
move |_| add_playlist_open.set(false),
OnClickOutsideOptions::default().ignore(["#add-playlist-dialog-btn"]),
);
view! {
<div class="home-card">
<div class="flex items-center mb-2">
<h1 class="p-2 text-xl">"Playlists"</h1>
<button class="control-solid ml-auto" on:click=create_playlist>
<Icon icon={icondata::AiPlusOutlined} {..} class="w-4 h-4" />
</button>
</div>
<div>
<Transition
fallback=move || view! { <Loading /> }
>
<ErrorBoundary
fallback=|errors| {
errors.get().into_iter().map(|(_id, error)| {
view! {
<Error<String>
message=error.to_string()
/>
}
}).collect::<Vec<_>>()
}
>
{move || GlobalState::playlists().get().map(|playlists| {
playlists.map(|playlists| {
view! {
{playlists.into_iter().map(|playlist| {
let active = Signal::derive(move || {
location.pathname.get().ends_with(&format!("/playlist/{}", playlist.id))
});
view! {
<A href={format!("/playlist/{}", playlist.id)} {..}
style={move || if active() {"background-color: var(--color-neutral-700);"} else {""}}
class="flex items-center hover:bg-neutral-700 rounded-md my-1" >
<img class="w-15 h-15 rounded-xl p-2 object-cover"
src={format!("/assets/images/playlist/{}.webp", playlist.id)}
onerror={crate::util::img_fallback::MUSIC_IMG_FALLBACK} />
<h2 class="pr-3 my-2">{playlist.name}</h2>
</A>
}
}).collect::<Vec<_>>()}
}
})
})}
</ErrorBoundary>
</Transition>
</div>
</div>
<Show
when=add_playlist_open
fallback=move || view! {}
>
<AddPlaylistDialog node_ref=add_playlist_dialog open=add_playlist_open />
</Show>
}
}