diff --git a/src/components/low_img.rs b/src/components/low_img.rs index 1ac7f4d6..0e2fd771 100644 --- a/src/components/low_img.rs +++ b/src/components/low_img.rs @@ -1,11 +1,17 @@ use leptos::{component, create_rw_signal, view, IntoView, SignalGet, SignalSet}; -use crate::icons::{ - animated_equalizer_icon::AnimatedEqualizerIcon, play_hover_icon::PlayHoverIcon, - song_default_icon::SongDefaultIcon, +use crate::{ + icons::{ + animated_equalizer_icon::AnimatedEqualizerIcon, play_hover_icon::PlayHoverIcon, + song_default_icon::SongDefaultIcon, + }, + utils::common::convert_file_src, }; -#[tracing::instrument(level = "trace", skip(cover_img, show_play_button, show_eq, eq_playing, play_now))] +#[tracing::instrument( + level = "trace", + skip(cover_img, show_play_button, show_eq, eq_playing, play_now) +)] #[component] pub fn LowImg( #[prop()] cover_img: String, @@ -28,14 +34,13 @@ where view! { } .into_view() } else { view! { - // class="fade-in-image" // class="fade-in-image" } diff --git a/src/components/topbar.rs b/src/components/topbar.rs index 554e298c..2f30dd04 100644 --- a/src/components/topbar.rs +++ b/src/components/topbar.rs @@ -4,16 +4,20 @@ use crate::icons::{spotify_icon::SpotifyIcon, youtube_icon::YoutubeIcon}; use leptos::{ component, create_effect, create_node_ref, create_rw_signal, create_write_slice, ev::Event, event_target_value, expect_context, view, window, CollectView, IntoView, RwSignal, SignalGet, - SignalGetUntracked, SignalSet, SignalUpdate, + SignalGetUntracked, SignalSet, SignalSetter, SignalUpdate, }; -use leptos_router::use_navigate; +use leptos_router::{use_navigate, NavigateOptions}; use leptos_use::on_click_outside; use leptos_virtual_scroller::VirtualScroller; use types::{ - entities::{QueryableAlbum, QueryableArtist, QueryableGenre, QueryablePlaylist}, + entities::{ + GetEntityOptions, QueryableAlbum, QueryableArtist, QueryableGenre, QueryablePlaylist, + }, + errors::Result, songs::{GetSongOptions, SearchableSong, Song}, }; -use web_sys::SubmitEvent; +use wasm_bindgen_futures::spawn_local; +use web_sys::{MouseEvent, SubmitEvent}; use crate::{ components::low_img::LowImg, @@ -34,35 +38,38 @@ enum InputFocus { Blur, } -#[tracing::instrument(level = "trace", skip(song))] -#[component] -pub fn SearchResultItem(song: Song) -> impl IntoView { - let player_store = expect_context::>(); - let play_now = create_write_slice(player_store, |p, val| p.play_now(val)); +#[derive(Clone)] +pub struct SearchResultItemData { + pub cover: Option, + pub title: String, + pub subtitle: String, + pub on_click: Rc>, + pub on_icon_click: Rc>, +} - let song_cloned = song.clone(); +#[tracing::instrument(level = "trace", skip(item))] +#[component] +pub fn SearchResultItem(item: SearchResultItemData) -> impl IntoView { view! {
-
-
{song.song.title.clone()}
-
- {song - .artists - .clone() - .unwrap() - .iter() - .filter_map(|a| a.artist_name.clone()) - .collect::>() - .join(", ")} -
+
+
{item.title}
+
{item.subtitle}
@@ -184,6 +191,189 @@ pub fn Accounts() -> impl IntoView { } } +async fn get_search_res( + term: String, + location: String, + play_now: SignalSetter, +) -> Result> { + match location.as_str() { + "/main/artists" => { + let res = crate::utils::invoke::get_entity_by_options(GetEntityOptions { + artist: Some(QueryableArtist { + artist_name: Some(term.clone()), + ..Default::default() + }), + ..Default::default() + }) + .await?; + + let res = serde_wasm_bindgen::from_value::>(res)?; + Ok(res + .into_iter() + .map(|a| SearchResultItemData { + cover: a.artist_coverpath.clone(), + title: a.artist_name.clone().unwrap_or_default(), + subtitle: String::default(), + on_click: Rc::new(Box::new(move || { + use_navigate()( + format!( + "/main/artists/single?entity={}", + serde_json::to_string(&a).unwrap() + ) + .as_str(), + NavigateOptions::default(), + ); + })), + on_icon_click: Rc::new(Box::new(move || {})), + }) + .collect()) + } + "/main/albums" => { + let res = crate::utils::invoke::get_entity_by_options(GetEntityOptions { + album: Some(QueryableAlbum { + album_name: Some(term.clone()), + ..Default::default() + }), + ..Default::default() + }) + .await?; + + let res = serde_wasm_bindgen::from_value::>(res)?; + Ok(res + .into_iter() + .map(|a| SearchResultItemData { + cover: a.album_coverpath_low.clone(), + title: a.album_name.clone().unwrap_or_default(), + subtitle: String::default(), + on_click: Rc::new(Box::new(move || { + use_navigate()( + format!( + "/main/albums/single?entity={}", + serde_json::to_string(&a).unwrap() + ) + .as_str(), + NavigateOptions::default(), + ); + })), + on_icon_click: Rc::new(Box::new(move || {})), + }) + .collect()) + } + "/main/genres" => { + let res = crate::utils::invoke::get_entity_by_options(GetEntityOptions { + genre: Some(QueryableGenre { + genre_name: Some(term.clone()), + ..Default::default() + }), + ..Default::default() + }) + .await?; + + let res = serde_wasm_bindgen::from_value::>(res)?; + Ok(res + .into_iter() + .map(|a| SearchResultItemData { + cover: None, + title: a.genre_name.clone().unwrap_or_default(), + subtitle: String::default(), + on_click: Rc::new(Box::new(move || { + use_navigate()( + format!( + "/main/genres/single?entity={}", + serde_json::to_string(&a).unwrap() + ) + .as_str(), + NavigateOptions::default(), + ); + })), + on_icon_click: Rc::new(Box::new(move || {})), + }) + .collect()) + } + "/main/playlists" => { + let res = crate::utils::invoke::get_entity_by_options(GetEntityOptions { + playlist: Some(QueryablePlaylist { + playlist_name: term.clone(), + playlist_path: Some(term.clone()), + ..Default::default() + }), + ..Default::default() + }) + .await?; + + let res = serde_wasm_bindgen::from_value::>(res)?; + + Ok(res + .into_iter() + .map(|a| SearchResultItemData { + cover: a.playlist_coverpath.clone(), + title: a.playlist_name.clone(), + subtitle: String::default(), + on_click: Rc::new(Box::new(move || { + use_navigate()( + format!( + "/main/playlists/single?entity={}", + serde_json::to_string(&a).unwrap() + ) + .as_str(), + NavigateOptions::default(), + ); + })), + on_icon_click: Rc::new(Box::new(move || {})), + }) + .collect()) + } + _ => { + let res = crate::utils::invoke::get_songs_by_options(GetSongOptions { + song: Some(SearchableSong { + title: Some(term.clone()), + path: Some(term.clone()), + ..Default::default() + }), + album: Some(QueryableAlbum { + album_name: Some(term.clone()), + ..Default::default() + }), + artist: Some(QueryableArtist { + artist_name: Some(term.clone()), + ..Default::default() + }), + genre: Some(QueryableGenre { + genre_name: Some(term.clone()), + ..Default::default() + }), + playlist: Some(QueryablePlaylist { + playlist_name: term, + ..Default::default() + }), + ..Default::default() + }) + .await?; + Ok(res + .into_iter() + .map(|s| SearchResultItemData { + cover: s.album.as_ref().and_then(|a| a.album_coverpath_low.clone()), + title: s.song.title.clone().unwrap_or_default(), + subtitle: s + .artists + .as_ref() + .map(|a| { + a.iter() + .filter_map(|a| a.artist_name.clone()) + .collect::>() + .join(",") + }) + .unwrap_or_default(), + on_click: Rc::new(Box::new(move || {})), + on_icon_click: Rc::new(Box::new(move || { + play_now.set(s.clone()); + })), + }) + .collect()) + } + } +} + #[tracing::instrument(level = "trace", skip())] #[component] pub fn TopBar() -> impl IntoView { @@ -214,6 +404,9 @@ pub fn TopBar() -> impl IntoView { ); }; + let player_store = expect_context::>(); + let play_now = create_write_slice(player_store, |p, val| p.play_now(val)); + let handle_text_change = move |ev: Event| { let text = event_target_value(&ev); input_value.set(text.clone()); @@ -221,33 +414,20 @@ pub fn TopBar() -> impl IntoView { return; } let value = format!("%{}%", text); - get_songs_by_option( - GetSongOptions { - song: Some(SearchableSong { - title: Some(value.clone()), - path: Some(value.clone()), - ..Default::default() - }), - album: Some(QueryableAlbum { - album_name: Some(value.clone()), - ..Default::default() - }), - artist: Some(QueryableArtist { - artist_name: Some(value.clone()), - ..Default::default() - }), - genre: Some(QueryableGenre { - genre_name: Some(value.clone()), - ..Default::default() - }), - playlist: Some(QueryablePlaylist { - playlist_name: value, - ..Default::default() - }), - ..Default::default() - }, - results, - ) + let current_page = window().location().pathname().unwrap(); + tracing::debug!("current page {}", current_page); + + spawn_local(async move { + let search_res = get_search_res(value.clone(), current_page, play_now).await; + match search_res { + Ok(res) => { + results.set(res); + } + Err(e) => tracing::error!("Failed to search {}: {:?}", value, e), + } + }); + + // }; view! { @@ -297,17 +477,14 @@ pub fn TopBar() -> impl IntoView { class="search-results d-flex" class:search-invisible=move || !show_searchbar.get() > -
- } - } - /> - -
+ } + } + /> +
diff --git a/src/utils/common.rs b/src/utils/common.rs index 232e1262..a824f5de 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -103,7 +103,7 @@ pub fn format_duration(secs: f64) -> String { #[tracing::instrument(level = "trace", skip(path))] pub fn convert_file_src(path: String) -> String { - if path.is_empty() { + if path.is_empty() || path.starts_with("asset:") { return path; }