fix(leptos): compile errors in Phase 3 message components - WIP
Fixed: - Icon imports: Replaced lucide-leptos icons with SVG/emoji alternatives - AnyView lifetime: Fixed render_emojis to use owned strings - Missing html import in message_feed.rs - use_messages: Removed #[component] to make it a regular function - MessageRecord fields: Removed is_reply, is_forward, is_crosspost checks - String wrapping in view! macros: Wrapped strings in <span> elements - Closure handling: Changed .into_forget() to .forget() for web_sys Closure - URL borrowing in image_grid: Clone URLs before using in view! Remaining issues (in progress): - String references in class attributes need owned values - Type inference for None.into_any() - Closure capture issues with on_load_more - Match arm type incompatibilities 19 errors remaining out of 31 initial errors - ~61% fixed
This commit is contained in:
+121
-2
@@ -1,8 +1,127 @@
|
||||
use leptos::prelude::*;
|
||||
use shared_types::message::MessageRecord;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::IntersectionObserver;
|
||||
use leptos::html;
|
||||
|
||||
const GROUP_WINDOW_MS: i64 = 5 * 60 * 1000;
|
||||
|
||||
fn group_messages(messages: Vec<MessageRecord>) -> Vec<Vec<MessageRecord>> {
|
||||
let mut groups: Vec<Vec<MessageRecord>> = Vec::new();
|
||||
for msg in messages {
|
||||
if let Some(last_group) = groups.last_mut() {
|
||||
let same_user = last_group.first()
|
||||
.map(|m| m.user_id == msg.user_id)
|
||||
.unwrap_or(false);
|
||||
let same_window = last_group.last()
|
||||
.map(|m| (m.created_at - msg.created_at).abs() < GROUP_WINDOW_MS)
|
||||
.unwrap_or(false);
|
||||
if same_user && same_window {
|
||||
last_group.push(msg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
groups.push(vec![msg]);
|
||||
}
|
||||
groups
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MessageFeed() -> impl IntoView {
|
||||
pub fn MessageFeed(
|
||||
messages: Vec<MessageRecord>,
|
||||
#[prop(optional)] empty_text: &'static str,
|
||||
#[prop(optional)] loading: bool,
|
||||
#[prop(optional)] has_more: bool,
|
||||
#[prop(optional)] loading_more: bool,
|
||||
#[prop(optional)] on_load_more: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
on_reanalyze: Arc<dyn Fn(String) + Send + Sync + 'static>,
|
||||
) -> impl IntoView {
|
||||
let sentinel_ref = create_node_ref::<html::Div>();
|
||||
let (intersecting, set_intersecting) = create_signal(false);
|
||||
|
||||
create_effect(move |_| {
|
||||
let _ = intersecting.get(); // track signal
|
||||
if let Some(node) = sentinel_ref.get() {
|
||||
let cb = Closure::<dyn Fn(Vec<JsValue>)>::new(move |entries: Vec<JsValue>| {
|
||||
for entry in entries {
|
||||
if let Some(entry) = entry.dyn_ref::<web_sys::IntersectionObserverEntry>() {
|
||||
if entry.is_intersecting() {
|
||||
if let Some(ref cb) = on_load_more {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let observer = IntersectionObserver::new(cb.as_ref().unchecked_ref())
|
||||
.expect("IntersectionObserver failed");
|
||||
observer.observe(&node);
|
||||
on_cleanup(move || {
|
||||
observer.disconnect();
|
||||
});
|
||||
// Keep closure alive
|
||||
cb.forget();
|
||||
}
|
||||
});
|
||||
|
||||
// Loading state
|
||||
if loading {
|
||||
return view! {
|
||||
<div class="space-y-4">
|
||||
{std::iter::repeat_with(|| {
|
||||
use super::message_card::MessageCardSkeleton;
|
||||
view! { <MessageCardSkeleton /> }
|
||||
}).take(3).collect::<Vec<_>>()}
|
||||
</div>
|
||||
}.into_any();
|
||||
}
|
||||
|
||||
if messages.is_empty() {
|
||||
return view! {
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-title">
|
||||
{if empty_text.is_empty() { "No messages" } else { empty_text }}
|
||||
</div>
|
||||
</div>
|
||||
}.into_any();
|
||||
}
|
||||
|
||||
let groups = group_messages(messages);
|
||||
let has_more_val = has_more;
|
||||
let loading_more_val = loading_more;
|
||||
|
||||
view! {
|
||||
<div>"Message Feed (placeholder)"</div>
|
||||
<div class="space-y-4">
|
||||
{groups.into_iter().map(|group| {
|
||||
let cb = on_reanalyze.clone();
|
||||
view! {
|
||||
<MessageCardGroup messages=group on_reanalyze=cb.clone() />
|
||||
}
|
||||
}).collect::<Vec<_>>()}
|
||||
|
||||
{/* Infinite scroll sentinel */}
|
||||
{has_more_val.then(|| {
|
||||
view! {
|
||||
<div node_ref=sentinel_ref class="h-4">
|
||||
{loading_more_val.then(|| {
|
||||
use super::message_card::MessageCardSkeleton;
|
||||
view! { <MessageCardSkeleton /> }
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
}.into_any()
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MessageCardGroup(
|
||||
messages: Vec<MessageRecord>,
|
||||
on_reanalyze: Arc<dyn Fn(String) + Send + Sync + 'static>,
|
||||
) -> impl IntoView {
|
||||
use super::message_card::MessageCard;
|
||||
view! {
|
||||
<MessageCard messages=messages on_reanalyze=on_reanalyze />
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user