use super::app_drawer::App_drawer; use super::gen_components::{ empty_message, on_shownotes_click, person_episode_item, Search_nav, UseScrollToTop, }; use super::virtual_list::PersonEpisodeVirtualList; use crate::components::audio::on_play_pause; use crate::components::audio::AudioPlayer; use crate::components::context::{AppState, ExpandedDescriptions, UIState}; use crate::components::gen_funcs::sanitize_html_with_blank_target; use crate::requests::people_req::PersonEpisode; use crate::requests::people_req::{self, PersonSubscription}; use i18nrs::yew::use_translation; use yew::prelude::*; use yew::{function_component, html, Html}; use yew_router::history::BrowserHistory; use yewdux::prelude::*; // use crate::components::gen_funcs::check_auth; use crate::components::gen_funcs::format_datetime; use crate::components::gen_funcs::match_date_format; use crate::components::gen_funcs::parse_date; use std::rc::Rc; use wasm_bindgen::prelude::*; #[derive(Clone, PartialEq, Properties, Debug)] struct PersonWithEpisodes { person: PersonSubscription, episodes: Vec, is_expanded: bool, } #[function_component(SubscribedPeople)] pub fn subscribed_people() -> Html { let (i18n, _) = use_translation(); let (desc_state, desc_dispatch) = use_store::(); let active_modal = use_state(|| None::); let show_modal = use_state(|| false); let active_clonedal = active_modal.clone(); let active_modal_clone = active_modal.clone(); let on_modal_open = Callback::from(move |episode_id: i32| { active_modal_clone.set(Some(episode_id)); }); let active_modal_clone = active_modal.clone(); let on_modal_close = Callback::from(move |_| { active_modal_clone.set(None); }); // let error = use_state(|| None); let (post_state, post_dispatch) = use_store::(); let (audio_state, audio_dispatch) = use_store::(); let loading = use_state(|| true); let expanded_state = use_state(|| std::collections::HashMap::::new()); let subscribed_people = use_state(|| Vec::::new()); let api_key = post_state .auth_details .as_ref() .map(|ud| ud.api_key.clone()); let user_id = post_state.user_details.as_ref().map(|ud| ud.UserID.clone()); let server_name = post_state .auth_details .as_ref() .map(|ud| ud.server_name.clone()); // Effect to fetch subscriptions on component mount // Effect to fetch subscriptions on component mount // // { let api_key = api_key.clone(); let user_id = user_id.clone(); let server_name = server_name.clone(); let subscribed_people = subscribed_people.clone(); let loading = loading.clone(); use_effect_with((), move |_| { if let (Some(server), Some(Some(key)), Some(uid)) = (server_name.clone(), api_key.clone(), user_id.clone()) { wasm_bindgen_futures::spawn_local(async move { match people_req::call_get_person_subscriptions(&server, &key, uid).await { Ok(subscriptions) => { let people = subscriptions .into_iter() .map(|sub| PersonWithEpisodes { person: sub, episodes: vec![], is_expanded: false, }) .collect(); subscribed_people.set(people); } Err(e) => { log::error!("Failed to fetch subscriptions: {}", e); } } loading.set(false); }); } else { loading.set(false); } || () }); } { let api_key = api_key.clone(); let user_id = user_id.clone(); let server_name = server_name.clone(); let subscribed_people = subscribed_people.clone(); let expanded_state = expanded_state.clone(); use_effect_with(expanded_state.clone(), move |expanded_state| { let api_key = api_key.clone(); let server_name = server_name.clone(); let user_id = user_id.clone(); let subscribed_people = subscribed_people.clone(); if let (Some(server), Some(Some(key)), Some(uid)) = (server_name, api_key, user_id) { let people = (*subscribed_people).clone(); // Move people IDs into the async block instead of the whole people Vec let person_ids: Vec<_> = people .iter() .filter(|person| { expanded_state .get(&person.person.personid) .copied() .unwrap_or(false) && person.episodes.is_empty() }) .map(|person| (person.person.personid, person.person.name.clone())) .collect(); for (person_id, person_name) in person_ids { let server = server.clone(); let key = key.clone(); let subscribed_people = subscribed_people.clone(); wasm_bindgen_futures::spawn_local(async move { // In the effect that loads episodes match people_req::call_get_person_episodes(&server, &key, uid, person_id) .await { Ok(new_episodes) => { subscribed_people.set( (*subscribed_people) .clone() .into_iter() .map(|mut p| { if p.person.personid == person_id { web_sys::console::log_1( &format!( "Setting episodes for person {}: {:?}", person_id, new_episodes ) .into(), ); p.episodes = new_episodes.clone(); } p }) .collect(), ); // Log the updated state web_sys::console::log_1( &format!("Updated people state: {:?}", *subscribed_people) .into(), ); } Err(e) => { log::error!( "Failed to fetch episodes for person {}: {}", person_name, e ); } } }); } } || () }); } let toggle_person = { let expanded_state = expanded_state.clone(); Callback::from(move |person_id: i32| { let mut new_state = (*expanded_state).clone(); let current_state = new_state.get(&person_id).copied().unwrap_or(false); new_state.insert(person_id, !current_state); expanded_state.set(new_state); }) }; let people = (*subscribed_people).clone(); let render_audio = audio_state.clone(); let render_people = { let people = people.clone(); let active_clonedal = active_clonedal.clone(); let no_people_found = i18n.t("people_subs.no_subscribed_people_found").to_string(); let subscribe_message = i18n.t("people_subs.subscribe_to_hosts_message").to_string(); let episode_count_text = i18n.t("people_subs.episode_count").to_string(); let shows_text = i18n.t("people_subs.shows").to_string(); let avatar_alt_text = i18n.t("people_subs.avatar_alt").to_string(); move || { if people.is_empty() { html! { { empty_message( &no_people_found, &subscribe_message )} } } else { html! {
{ people.into_iter().map(|person| { let active_modal = active_clonedal.clone(); let is_expanded = *expanded_state.get(&person.person.personid).unwrap_or(&false); html! {
{ render_host_with_episodes( &person.person, person.episodes.clone(), is_expanded, toggle_person.reform(move |_| person.person.personid), post_state.clone(), post_dispatch.clone(), render_audio.clone(), desc_state.clone(), desc_dispatch.clone(), audio_dispatch.clone(), *show_modal, on_modal_open.clone(), on_modal_close.clone(), active_modal, &episode_count_text, &shows_text, &avatar_alt_text, )}
} }).collect::() }
} } } }; html! { <>
{ if *loading { html! {
} } else { html! {

{&i18n.t("people_subs.subscribed_people")}

{ render_people() }
} } } { if let Some(audio_props) = &audio_state.currently_playing { html! { } } else { html! {} } }
} } #[allow(dead_code)] fn get_proxied_image_url(server_name: &str, original_url: String) -> String { let proxied_url = format!( "{}/api/proxy/image?url={}", server_name, urlencoding::encode(&original_url) ); web_sys::console::log_1(&format!("Proxied URL: {}", proxied_url).into()); proxied_url } #[allow(dead_code)] fn render_host_with_episodes( person: &PersonSubscription, episodes: Vec, is_expanded: bool, toggle_host_expanded: Callback, state: Rc, dispatch: Dispatch, audio_state: Rc, desc_rc: Rc, desc_state: Dispatch, audio_dispatch: Dispatch, _show_modal: bool, on_modal_open: Callback, on_modal_close: Callback, active_modal: UseStateHandle>, episode_count_text: &str, shows_text: &str, avatar_alt_text: &str, ) -> Html { let _episode_count = episodes.len(); let history_clone = BrowserHistory::new(); let api_key = state.auth_details.as_ref().map(|ud| ud.api_key.clone()); let user_id = state.user_details.as_ref().map(|ud| ud.UserID.clone()); let server_name = state.auth_details.as_ref().map(|ud| ud.server_name.clone()); let proxied_url = get_proxied_image_url(&server_name.clone().unwrap(), person.image.clone()); let handle_click = { let toggle_host_expanded = toggle_host_expanded.clone(); Callback::from(move |e: MouseEvent| { e.stop_propagation(); // Stop event propagation e.prevent_default(); // Prevent default behavior toggle_host_expanded.emit(e); }) }; html! {
{format!("{}

{ &person.name }

{ format!("{}: {}", episode_count_text, person.episode_count) }

{ format!("{}: {}", shows_text, person.associatedpodcasts) }

{ if is_expanded { let episode_count = episodes.len(); web_sys::console::log_1(&format!("Attempting to render {} episodes", episode_count).into()); html! {
// Old episode rendering code (disabled for virtual scrolling) { if false { let _ = episodes.iter().map(|episode| { let id_string = episode.episodeid.to_string(); let desc_expanded = desc_rc.expanded_descriptions.contains(&id_string); let episode_url_for_closure = episode.episodeurl.clone(); let episode_title_for_closure = episode.episodetitle.clone(); let episode_description_for_closure = episode.episodedescription.clone(); let episode_artwork_for_closure = episode.episodeartwork.clone(); let episode_duration_for_closure = episode.episodeduration.clone(); let listener_duration_for_closure = episode.listenduration.clone(); let episode_id_for_closure = episode.episodeid.clone(); let episode_is_youtube = Some(episode.is_youtube.clone()); let _completed = false; let user_id_play = user_id.clone(); let server_name_play = server_name.clone(); let api_key_play = api_key.clone(); let audio_dispatch = audio_dispatch.clone(); let is_current_episode = audio_state .currently_playing .as_ref() .map_or(false, |current| current.episode_id == episode.episodeid); let is_playing = audio_state.audio_playing.unwrap_or(false); let date_format = match_date_format(state.date_format.as_deref()); let datetime = parse_date(&episode.episodepubdate, &state.user_tz); let format_release = format!( "{}", format_datetime(&datetime, &state.hour_preference, date_format) ); let on_play_pause = on_play_pause( episode_url_for_closure.clone(), episode_title_for_closure.clone(), episode_description_for_closure.clone(), format_release.clone(), episode_artwork_for_closure.clone().unwrap(), episode_duration_for_closure.clone(), episode_id_for_closure.clone(), Some(listener_duration_for_closure.clone()), api_key_play.unwrap().unwrap(), user_id_play.unwrap(), server_name_play.unwrap(), audio_dispatch.clone(), audio_state.clone(), None, episode_is_youtube.clone(), ); let on_shownotes_click = on_shownotes_click( history_clone.clone(), dispatch.clone(), Some(episode_id_for_closure.clone()), Some(String::from("Not needed")), Some(String::from("Not needed")), Some(String::from("Not needed")), true, Some(true), Some(false), ); #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = window)] fn toggleDescription(guid: &str, expanded: bool); } let toggle_expanded = { let desc_dispatch = desc_state.clone(); let episode_guid = episode.episodeid.clone().to_string(); Callback::from(move |_: MouseEvent| { let guid = episode_guid.clone(); desc_dispatch.reduce_mut(move |state| { if state.expanded_descriptions.contains(&guid) { state.expanded_descriptions.remove(&guid); // Collapse the description toggleDescription(&guid, false); // Call JavaScript function } else { state.expanded_descriptions.insert(guid.clone()); // Expand the description toggleDescription(&guid, true); // Call JavaScript function } }); }) }; let show_modal = *active_modal == Some(episode.episodeid); person_episode_item( Box::new(episode.clone()), sanitize_html_with_blank_target(&episode.episodedescription), desc_expanded, &format_release, on_play_pause, on_shownotes_click, toggle_expanded, episode.episodeduration, Some(episode.listenduration), "people", Callback::noop(), false, episode.episodeurl.clone(), false, show_modal, on_modal_open.clone(), on_modal_close.clone(), is_current_episode, is_playing, ) }).collect::>(); html! {} } else { html! {} } }
} } else { html! {} }}
} }