use std::collections::HashSet;
use std::rc::Rc;
use a2ui_base::components::dispatch_event;
use a2ui_base::event::{InputEvent, InputKey};
use a2ui_base::interaction::apply_event_result;
use a2ui_base::message_processor::MessageProcessor;
use a2ui_base::model::component_context::ComponentContext;
use a2ui_base::protocol::server_to_client::A2uiMessage;
use dioxus::prelude::*;
use crate::node::{A2uiNode, Functions, OnActivate};
#[component]
pub fn Gallery() -> Element {
let processor: Signal<MessageProcessor> = use_context();
let functions: Rc<Functions> = use_context();
let selected: Signal<usize> = use_context();
let open_modals: Signal<HashSet<String>> = use_context();
let samples: Rc<Vec<(String, Vec<A2uiMessage>)>> = use_context();
let on_activate =
use_hook(|| make_on_activate(processor, functions.clone(), open_modals));
use_context_provider(|| on_activate.clone());
let mut modal_ids: Vec<String> = open_modals.read().iter().cloned().collect();
modal_ids.sort();
rsx! {
div { class: "app",
Sidebar { samples: samples.clone(), selected, processor, open_modals }
MainPane { samples: samples.clone(), selected }
}
for modal_id in modal_ids {
ModalOverlay { modal_id }
}
}
}
#[component]
fn Sidebar(
samples: Rc<Vec<(String, Vec<A2uiMessage>)>>,
selected: Signal<usize>,
processor: Signal<MessageProcessor>,
open_modals: Signal<HashSet<String>>,
) -> Element {
let count = samples.len();
let sel = *selected.read();
let rows: Vec<(usize, String, Rc<Vec<(String, Vec<A2uiMessage>)>>)> = samples
.iter()
.enumerate()
.map(|(i, (name, _))| (i, name.clone(), samples.clone()))
.collect();
rsx! {
aside { class: "sidebar",
div { class: "sidebar__brand",
span { class: "sidebar__mark", "◆" }
span { class: "sidebar__title",
b { "A2UI" }
span { "Dioxus Gallery" }
}
}
hr {}
div { class: "sidebar__section mono", "SAMPLES" }
div { class: "sidebar__list",
for (i, name, row_samples) in rows {
button {
key: "{i}",
class: if i == sel { "sample sample--sel" } else { "sample" },
onclick: move |_| {
load_sample(processor, selected, open_modals, &row_samples, i);
},
span { class: "sample__idx mono", "{i + 1}" }
span { class: "sample__name", "{name}" }
}
}
}
hr {}
div { class: "sidebar__foot mono", "{count} samples" }
}
}
}
#[component]
fn MainPane(samples: Rc<Vec<(String, Vec<A2uiMessage>)>>, selected: Signal<usize>) -> Element {
let sel = *selected.read();
let name = samples.get(sel).map(|(n, _)| n.clone()).unwrap_or_default();
let count = samples.len();
rsx! {
main { class: "main",
div { class: "topbar",
span { class: "topbar__crumb mono", "Preview" }
span { class: "topbar__sep", "›" }
span { class: "topbar__title", "{name}" }
span { class: "spacer" }
span { class: "topbar__chip mono", "{sel + 1} / {count}" }
}
hr {}
div { class: "preview",
A2uiNode { id: "root".to_string(), base_path: "".to_string() }
}
}
}
}
#[component]
fn ModalOverlay(modal_id: String) -> Element {
let processor: Signal<MessageProcessor> = use_context();
let (content_id, title) = {
let p = processor.read();
let Some(surface) = p.model.surfaces().next() else {
return rsx! { span {} };
};
let components = surface.components.borrow();
let Some(m) = components.get(&modal_id) else {
return rsx! { span {} };
};
if m.component_type != "Modal" {
return rsx! { span {} };
}
let content = m.get_property::<String>("content");
let title = m
.get_property::<String>("title")
.unwrap_or_else(|| "Dialog".to_string());
(content, title)
};
let Some(content_id) = content_id else {
return rsx! { span {} };
};
let mut open_modals: Signal<HashSet<String>> = use_context();
let scrim_id = modal_id.clone();
let close_id = modal_id.clone();
rsx! {
div { class: "modal-wrap",
button {
class: "scrim",
onclick: move |_| {
open_modals.write().remove(&scrim_id);
},
}
div { class: "modal",
div { class: "modal__head",
span { class: "modal__title", "{title}" }
span { class: "spacer" }
button {
class: "btn btn--borderless",
onclick: move |_| {
open_modals.write().remove(&close_id);
},
"✕"
}
}
hr {}
div { class: "col",
A2uiNode { id: content_id, base_path: "".to_string() }
}
}
}
}
}
fn load_sample(
mut processor: Signal<MessageProcessor>,
mut selected: Signal<usize>,
mut open_modals: Signal<HashSet<String>>,
samples: &Rc<Vec<(String, Vec<A2uiMessage>)>>,
idx: usize,
) {
let msgs = samples.get(idx).map(|(_, m)| m.clone()).unwrap_or_default();
{
let mut p = processor.write();
p.reset();
for msg in &msgs {
let _ = p.process_message(msg.clone());
}
}
open_modals.write().clear();
selected.set(idx);
}
fn make_on_activate(
processor: Signal<MessageProcessor>,
functions: Rc<Functions>,
open_modals: Signal<HashSet<String>>,
) -> OnActivate {
Rc::new(move |node_id: String| {
let mut proc = processor;
{
let result = {
let p = proc.read();
let Some(surface) = p.model.surfaces().next() else {
return;
};
let Some(comp_type) = surface
.components
.borrow()
.get(&node_id)
.map(|m| m.component_type.clone())
else {
return;
};
let data_model = surface.data_model.borrow();
let components = surface.components.borrow();
let ctx = ComponentContext::new(
node_id.clone(),
surface.id.clone(),
&data_model,
&components,
functions.as_ref(),
"",
Some(node_id.clone()),
);
dispatch_event(
&comp_type,
&ctx,
&InputEvent::KeyPress { key: InputKey::Enter },
)
};
if let Some(result) = result {
let mut p = proc.write();
let _ = apply_event_result(&mut p, result);
}
}
apply_modal_interaction(proc, open_modals, &node_id);
})
}
fn apply_modal_interaction(
processor: Signal<MessageProcessor>,
mut open_modals: Signal<HashSet<String>>,
node_id: &str,
) {
let modal_id = {
let p = processor.read();
let Some(surface) = p.model.surfaces().next() else {
return;
};
let components = surface.components.borrow();
let is_modal = components
.get(node_id)
.map(|m| m.component_type == "Modal")
.unwrap_or(false);
if is_modal {
let mut mods = open_modals.write();
if mods.insert(node_id.to_string()) {
return; }
Some(node_id.to_string()) } else {
components
.all()
.iter()
.find_map(|(id, m)| {
(m.component_type == "Modal"
&& m.get_property::<String>("trigger").as_deref() == Some(node_id))
.then(|| id.clone())
})
}
};
match modal_id {
Some(id) if id == node_id => {
open_modals.write().remove(&id);
}
Some(id) => {
open_modals.write().insert(id);
}
None => {}
}
}