synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;

use crate::state::app_state::AppState;

/// A widget definition from room state.
#[derive(Clone, Debug, PartialEq)]
pub struct RoomWidget {
    pub id: String,
    pub name: String,
    pub widget_type: String,
    pub url: String,
    pub creator_user_id: String,
    pub waiting_for_iframe: bool,
}

/// Connection status for a widget.
#[derive(Clone, Debug, PartialEq)]
pub enum WidgetConnectionStatus {
    Connected,
    NotConnected,
}

/// A frame component that wraps widget content with controls.
///
/// Provides a header bar with the widget name and URL, action buttons
/// (open in browser, reload, close), a content area explaining the widget
/// is running externally, and a connection status indicator.
#[component]
pub fn WidgetFrame(
    widget: RoomWidget,
    on_close: EventHandler<String>,
) -> Element {
    let widget_name = widget.name.clone();
    let widget_id = widget.id.clone();
    let widget_type = widget.widget_type.clone();
    let url_for_open = widget.url.clone();
    let url_for_display = widget.url.clone();
    let url_for_content = widget.url.clone();

    // Connection status - widgets opened in external browser are always "Not connected"
    // to the embedded frame since there's no actual iframe.
    let connection_status = if widget.waiting_for_iframe {
        WidgetConnectionStatus::NotConnected
    } else {
        WidgetConnectionStatus::Connected
    };

    let status_text = match connection_status {
        WidgetConnectionStatus::Connected => "Connected",
        WidgetConnectionStatus::NotConnected => "Not connected",
    };

    let status_class = match connection_status {
        WidgetConnectionStatus::Connected => "widget-frame__status widget-frame__status--connected",
        WidgetConnectionStatus::NotConnected => "widget-frame__status widget-frame__status--disconnected",
    };

    let close_id = widget_id.clone();

    rsx! {
        div {
            class: "widget-frame",

            // Header bar
            div {
                class: "widget-frame__header",

                // Widget info
                div {
                    class: "widget-frame__info",
                    span { class: "widget-frame__name", "{widget_name}" }
                    span { class: "widget-frame__url-badge", "{url_for_display}" }
                }

                // Status indicator
                span {
                    class: "{status_class}",
                    "{status_text}"
                }

                // Action buttons
                div {
                    class: "widget-frame__actions",
                    a {
                        class: "widget-frame__action-btn",
                        title: "Open in browser",
                        href: "{url_for_open}",
                        target: "_blank",
                        rel: "noopener noreferrer",
                        "Open in browser"
                    }
                    button {
                        class: "widget-frame__action-btn",
                        title: "Reload widget",
                        onclick: move |_| {
                            tracing::info!("Reload requested for widget: {}", widget_id);
                        },
                        "Reload"
                    }
                    button {
                        class: "widget-frame__action-btn widget-frame__action-btn--close",
                        title: "Close widget",
                        onclick: move |_| {
                            on_close.call(close_id.clone());
                        },
                        "Close"
                    }
                }
            }

            // Content area
            div {
                class: "widget-frame__content",
                div {
                    class: "widget-frame__placeholder",
                    div {
                        class: "widget-frame__icon",
                        // Widget type icon placeholder
                        "{widget_type}"
                    }
                    h3 {
                        class: "widget-frame__title",
                        "{widget_name}"
                    }
                    p {
                        class: "widget-frame__description",
                        "This widget is running in an external browser window. Use the \"Open in browser\" button above to interact with it."
                    }
                    div {
                        class: "widget-frame__url-display",
                        code { "{url_for_content}" }
                    }
                }
            }
        }
    }
}

/// Widget layout component that renders embedded widgets in a room.
#[component]
pub fn WidgetLayout(room_id: String) -> Element {
    let state = use_context::<Signal<AppState>>();
    let mut widgets = use_signal(Vec::<RoomWidget>::new);
    let mut loading = use_signal(|| true);

    let rid = room_id.clone();
    use_effect(move || {
        let rid = rid.clone();
        spawn(async move {
            let client = { state.read().client.clone() };
            if let Some(client) = client {
                if let Ok(room_id) = matrix_sdk::ruma::OwnedRoomId::try_from(rid.as_str()) {
                    if let Some(_room) = client.get_room(&room_id) {
                        // In a full implementation, we'd read im.vector.modular.widgets
                        // state events from the room to discover widgets
                        tracing::debug!("Checking room {room_id} for widgets");
                    }
                }
            }
            loading.set(false);
        });
    });

    if widgets.read().is_empty() {
        return rsx! {};
    }

    let on_close_widget = move |widget_id: String| {
        let mut current = widgets.write();
        current.retain(|w| w.id != widget_id);
        tracing::info!("Widget closed: {}", widget_id);
    };

    rsx! {
        div {
            class: "widget-layout",
            for widget in widgets.read().iter() {
                {
                    let w = widget.clone();
                    let w_id = w.id.clone();
                    rsx! {
                        WidgetFrame {
                            key: "{w_id}",
                            widget: w,
                            on_close: on_close_widget,
                        }
                    }
                }
            }
        }
    }
}