omega restructuring + a bunch of changes i have made over the last weeks
This commit is contained in:
269
packages/marts/src/matrix/ui.rs
Normal file
269
packages/marts/src/matrix/ui.rs
Normal file
@@ -0,0 +1,269 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_web::events::FormEvent;
|
||||
use mize::Mize;
|
||||
use matrix_sdk::SyncRoomEvent;
|
||||
|
||||
const ROOM_ID: &str = "!XwqdXGPlmaLatdGUiN:ppc.social";
|
||||
|
||||
/// Props for the chat component
|
||||
#[derive(Props, Clone)]
|
||||
pub struct ChatProps {
|
||||
pub mize: Mize,
|
||||
pub initial_timeline: Vec<SyncRoomEvent>,
|
||||
pub initial_draft: String,
|
||||
pub initial_edit_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Message component - renders a single message
|
||||
#[component]
|
||||
pub fn Message(cx: Scope, event: SyncRoomEvent) -> Element {
|
||||
let sender = event.sender().to_string();
|
||||
let timestamp = event.origin_server_ts();
|
||||
|
||||
// Extract display name from MXID (basic version)
|
||||
let display_name = if let Some(at_pos) = sender.find('@') {
|
||||
if let Some(colon_pos) = sender.rfind(':') {
|
||||
&sender[at_pos + 1..colon_pos]
|
||||
} else {
|
||||
&sender[at_pos + 1..]
|
||||
}
|
||||
} else {
|
||||
&sender
|
||||
};
|
||||
|
||||
// Format timestamp
|
||||
let time_str = {
|
||||
let dt = js_sys::Date::new(&(timestamp as f64));
|
||||
dt.to_locale_string()
|
||||
};
|
||||
|
||||
// Extract message content
|
||||
let content = event
|
||||
.as_original()
|
||||
.and_then(|e| e.content().deserialize().ok())
|
||||
.and_then(|c: matrix_sdk::ruma::events::room::message::RoomMessageEventContent| {
|
||||
c.body().map(|s| s.to_string())
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
// Check if this is an edit
|
||||
let is_edit = event
|
||||
.as_original()
|
||||
.map(|e| {
|
||||
e.content()
|
||||
.get("m.relates_to")
|
||||
.and_then(|v| v.as_object())
|
||||
.and_then(|o| o.get("rel_type"))
|
||||
.and_then(|v| v.as_str())
|
||||
== Some("m.replace")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-event",
|
||||
style: "margin: 14px 16px; padding-bottom: 10px; border-bottom: 1px solid var(--background-modifier-border);",
|
||||
|
||||
div {
|
||||
class: "matrix-chat-msghead",
|
||||
style: "font-size: 1em; font-weight: 500; color: var(--interactive-accent); display: flex; align-items: center; gap: 10px;",
|
||||
|
||||
span { display_name }
|
||||
span {
|
||||
style: "font-size: 0.85em; color: var(--text-muted); margin-left: auto;",
|
||||
{time_str.clone()}
|
||||
if is_edit { " · edited" }
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "matrix-chat-msgbody",
|
||||
style: "margin: 6px 0 0 0; font-size: 1em;",
|
||||
{content}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Editor component - message input area
|
||||
#[component]
|
||||
pub fn Editor(cx: Scope, mize: Mize, draft: UseSignal<String>, edit_id: UseSignal<Option<String>>) -> Element {
|
||||
let on_input = move |evt: FormEvent| {
|
||||
draft.set(evt.value.clone());
|
||||
};
|
||||
|
||||
let on_send = move |_| {
|
||||
let mize_clone = mize.clone();
|
||||
let draft_val = draft.read().clone();
|
||||
let edit_id_val = edit_id.read().clone();
|
||||
|
||||
cx.spawn(async move {
|
||||
if let Ok(mut matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
|
||||
matrix_part.message_draft = draft_val;
|
||||
matrix_part.edit_event_id = edit_id_val;
|
||||
if let Err(e) = matrix_part.send_message().await {
|
||||
console_log!("Failed to send message: {:?}", e);
|
||||
}
|
||||
// Clear local draft
|
||||
draft.set(String::new());
|
||||
edit_id.set(None);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let on_keydown = move |evt: dioxus_web::events::KeyboardEvent| {
|
||||
if (evt.ctrl_key() || evt.meta_key()) && evt.key() == "Enter" {
|
||||
on_send(evt);
|
||||
}
|
||||
};
|
||||
|
||||
let on_cancel = move |_| {
|
||||
draft.set(String::new());
|
||||
edit_id.set(None);
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-editor",
|
||||
style: "border-top: 1px solid var(--background-modifier-border); background: var(--background-primary); padding: 8px 12px;",
|
||||
|
||||
div {
|
||||
class: "matrix-chat-editor-inner",
|
||||
style: "border: 1px solid var(--background-modifier-border); border-radius: 6px; overflow: hidden; background: var(--background-secondary);",
|
||||
|
||||
textarea {
|
||||
r#type: "text",
|
||||
rows: "3",
|
||||
style: "width: 99%; min-height: 40px; resize: vertical; background: transparent; border: none; outline: none; padding: 8px; font-family: inherit; font-size: inherit; color: var(--text-normal);",
|
||||
value: "{draft}",
|
||||
oninput: on_input,
|
||||
onkeydown: on_keydown,
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
r#type: "button",
|
||||
style: "margin-top: 3px; float: right; padding: 6px 14px; font-size: 0.97em; border-radius: 5px; border: none; background: var(--interactive-accent); color: white; cursor: pointer;",
|
||||
onclick: on_send,
|
||||
if edit_id.read().is_some() { "Save" } else { "Send" }
|
||||
}
|
||||
|
||||
if edit_id.read().is_some() {
|
||||
button {
|
||||
r#type: "button",
|
||||
style: "margin-right: 12px; padding: 6px 12px; font-size: 0.93em; border-radius: 5px; border: none; color: var(--text-normal); background: var(--background-secondary); cursor: pointer;",
|
||||
onclick: on_cancel,
|
||||
"Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Timeline component - list of messages
|
||||
#[component]
|
||||
pub fn Timeline(cx: Scope, mize: Mize, timeline: UseSignal<Vec<SyncRoomEvent>>) -> Element {
|
||||
// Periodically refresh timeline from MatrixPart
|
||||
cx.use_effect((), move |_| {
|
||||
let mize_clone = mize.clone();
|
||||
let timeline_clone = timeline.clone();
|
||||
|
||||
// Set up interval to poll for updates
|
||||
let interval = js_sys::set_interval(
|
||||
&Closure::<dyn FnMut()>::wrap(Box::new(move || {
|
||||
if let Ok(matrix_part) = mize_clone.get_part_native::<crate::matrix::MatrixPart>("matrix") {
|
||||
*timeline_clone.write() = matrix_part.timeline.clone();
|
||||
}
|
||||
})),
|
||||
1000, // 1 second
|
||||
);
|
||||
|
||||
// Cleanup on unmount
|
||||
move || {
|
||||
js_sys::clear_interval(interval);
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-scroll",
|
||||
style: "flex-grow: 1; overflow-y: auto; padding: 8px 0; background: var(--background-secondary);",
|
||||
|
||||
// Room name header
|
||||
div {
|
||||
class: "matrix-chat-roomname",
|
||||
style: "font-weight: 600; text-align: center; font-size: 1.1em; margin: 2px 0 12px 0; border-bottom: 1px solid var(--background-modifier-border); color: var(--text-accent); background: var(--background-primary);",
|
||||
"Matrix Chat - {ROOM_ID}"
|
||||
}
|
||||
|
||||
// Messages
|
||||
for event in timeline.read().iter() {
|
||||
rsx! { Message { event: event.clone() } }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Root chat component
|
||||
#[component]
|
||||
pub fn Chat(cx: Scope, props: ChatProps) -> Element {
|
||||
let timeline = use_signal(&cx, || props.initial_timeline);
|
||||
let draft = use_signal(&cx, || props.initial_draft);
|
||||
let edit_id = use_signal(&cx, || props.initial_edit_id);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "matrix-chat-root",
|
||||
style: "height: 100%; display: flex; flex-direction: column; background: var(--background-primary); color: var(--text-normal);",
|
||||
|
||||
// Title
|
||||
div {
|
||||
class: "matrix-chat-title",
|
||||
style: "padding: 12px; font-weight: bold; font-size: 1.2em; text-align: center; border-bottom: 1px solid var(--background-modifier-border);",
|
||||
"Matrix Chat"
|
||||
}
|
||||
|
||||
// Info
|
||||
div {
|
||||
class: "matrix-chat-info",
|
||||
style: "font-size: 0.85em; color: var(--text-muted); text-align: center; margin-bottom: 5px;",
|
||||
"Room: {ROOM_ID}"
|
||||
}
|
||||
|
||||
// Timeline
|
||||
Timeline { mize: props.mize.clone(), timeline }
|
||||
|
||||
// Editor
|
||||
Editor { mize: props.mize.clone(), draft, edit_id }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Mount the Dioxus app into a DOM element
|
||||
pub fn mount_chat_app(
|
||||
element: web_sys::HtmlElement,
|
||||
initial_timeline: Vec<SyncRoomEvent>,
|
||||
initial_draft: String,
|
||||
initial_edit_id: Option<String>,
|
||||
mize: Mize,
|
||||
) {
|
||||
dioxus_web::launch(
|
||||
dioxus::prelude::App::new(move || rsx! {
|
||||
Chat {
|
||||
mize: mize.clone(),
|
||||
initial_timeline: initial_timeline.clone(),
|
||||
initial_draft: initial_draft.clone(),
|
||||
initial_edit_id: initial_edit_id.clone(),
|
||||
}
|
||||
}),
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
||||
// Console log macro
|
||||
#[macro_export]
|
||||
macro_rules! console_log {
|
||||
($($t:tt)*) => {
|
||||
web_sys::console::log_1(&format!($($t)*).into());
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user