1use leptos::{either::Either, ev::MouseEvent};
2use leptos::prelude::*;
3use leptos_router::components::Outlet;
4use leptos_router::hooks::use_params_map;
5use crate::{app::FeedRoute, prelude::*};
6
7use apb::Object;
8
9#[component]
10pub fn ObjectView() -> impl IntoView {
11 let params = use_params_map();
12 let matched_route = use_context::<ReadSignal<crate::app::FeedRoute>>().expect("missing route context");
13 let auth = use_context::<Auth>().expect("missing auth context");
14 let refresh = use_context::<WriteSignal<()>>().expect("missing refresh context");
15 let (loading, set_loading) = signal(false);
16 let id = Signal::derive(move || params.get().get("id").unwrap_or_default());
17 let object = LocalResource::new(
18 move || {
19 let (oid, _loading) = (id.get(), loading.get());
20 async move {
21 tracing::info!("rerunning fetcher");
22 let obj = cache::OBJECTS.fetch(&oid, U::Object, auth).await?;
23
24 if let Ok(author) = obj.attributed_to().id() {
26 cache::OBJECTS.fetch(&author, U::Actor, auth).await;
27 }
28 if let Ok(quote) = obj.quote_url().id() {
29 cache::OBJECTS.fetch("e, U::Object, auth).await;
30 }
31
32 Some(obj)
33
34 }
41 }
42 );
43
44 view! {
51 {move || match object.try_get() {
52 None => ().into_any(), Some(None) => view! { <Loader /> }.into_any(), Some(Some(None)) => { let raw_id = params.get().get("id").unwrap_or_default();
56 let uid = uriproxy::uri(URL_BASE, uriproxy::UriClass::Object, &raw_id);
57 view! { <p class="center"><code>loading failed</code><sup><small><a class="clean" href={uid} target="_blank">"↗"</a></small></sup></p> }.into_any()
58 },
59 Some(Some(Some(o))) => { tracing::info!("redrawing object");
61 view! { <Object object=o.clone() /> }.into_any()
62 },
63 }}
64
65 <p>
66 <span class:tab-active=move || matches!(matched_route.get(), FeedRoute::Context)><a class="clean" href=move || format!("/web/objects/{}", id.get())><span class="emoji ml-2">"🕸️"</span><span class:hidden-on-mobile=move || !matches!(matched_route.get(), FeedRoute::Context)>" context"</span></a></span>
67 <span class:tab-active=move || matches!(matched_route.get(), FeedRoute::Replies)><a class="clean" href=move || format!("/web/objects/{}/replies", id.get())><span class="emoji ml-2">"📫"</span><span class:hidden-on-mobile=move || !matches!(matched_route.get(), FeedRoute::Replies)>" replies"</span></a></span>
68 <span class:tab-active=move || matches!(matched_route.get(), FeedRoute::ObjectLikes)><a class="clean" href=move || format!("/web/objects/{}/likes", id.get())><span class="emoji ml-2">"⭐"</span><span class:hidden-on-mobile=move || !matches!(matched_route.get(), FeedRoute::ObjectLikes)>" likes"</span></a></span>
69 {move || if auth.present() {
70 if loading.get() {
71 Some(Either::Left(view! {
72 <span style="float: right">
73 "fetching "<span class="dots"></span>
74 </span>
75 }))
76 } else {
77 Some(Either::Right(view! {
78 <span style="float: right">
79 <a
80 class="clean"
81 on:click=move |ev| fetch_cb(ev, set_loading, id.get(), auth, refresh)
82 href="#"
83 >
84 <span class="emoji ml-2">"↺ "</span>"fetch"
85 </a>
86 </span>
87 }))
88 }
89 } else {
90 None
91 }}
92 </p>
93 <hr class="color" />
94
95 {move || if object.try_get().is_some_and(|x| x.is_some()) {
96 tracing::info!("redrawing outlet");
97 Some(view! { <Outlet /> })
98 } else {
99 None
100 }}
101 }
102}
103
104fn fetch_cb(ev: MouseEvent, set_loading: WriteSignal<bool>, oid: String, auth: Auth, refresh: WriteSignal<()>) {
105 let api = Uri::api(U::Object, &oid, false);
106 ev.prevent_default();
107 set_loading.set(true);
108 leptos::task::spawn_local(async move {
109 if let Err(e) = Http::fetch::<serde_json::Value>(&format!("{api}/replies?fetch=true"), auth).await {
110 tracing::error!("failed crawling replies for {oid}: {e}");
111 }
112 cache::OBJECTS.invalidate(&Uri::full(U::Object, &oid));
113 tracing::info!("invalidated {}", Uri::full(U::Object, &oid));
114 set_loading.set(false);
115 refresh.set(());
116 });
117}