use std::collections::{HashMap, HashSet};
use std::env;
use std::process::ExitCode;
use std::rc::Rc;
use a2ui_base::catalog::basic_functions::build_basic_functions;
use a2ui_base::catalog::function_api::FunctionImplementation;
use a2ui_base::message_processor::MessageProcessor;
use a2ui_base::protocol::server_to_client::A2uiMessage;
use a2ui_dioxus::{Gallery, STYLESHEET};
use a2ui_gallery::sample_loader::{self, Sample};
use a2ui_tui::catalogs::basic::build_basic_catalog;
use a2ui_tui::catalogs::minimal::build_minimal_catalog;
use dioxus::desktop::tao::dpi::LogicalSize;
use dioxus::desktop::{Config, WindowBuilder};
use dioxus::prelude::*;
fn load_catalog_samples(catalog: &str) -> Vec<Sample> {
let subpath = format!("v1_0/catalogs/{catalog}/examples");
if let Ok(root) = env::var("A2UI_SPEC_DIR") {
sample_loader::load_samples_from_dir(&format!("{root}/{subpath}"))
} else {
sample_loader::load_samples(&subpath)
}
}
fn resolve_sample(arg: &str, samples: &[Sample]) -> Option<usize> {
if let Ok(n) = arg.parse::<usize>() {
if n >= 1 && n <= samples.len() {
return Some(n - 1);
}
return None;
}
let needle = arg.to_lowercase();
samples
.iter()
.position(|s| s.name.to_lowercase().contains(&needle))
}
fn main() -> ExitCode {
let mut samples = load_catalog_samples("minimal");
samples.extend(load_catalog_samples("basic"));
if samples.is_empty() {
eprintln!("No samples found.");
return ExitCode::from(1);
}
println!("A2UI dioxus gallery — {} sample(s) available:", samples.len());
for (i, s) in samples.iter().enumerate() {
println!(" {:>2}. {} — {}", i + 1, s.name, s.description);
}
let args: Vec<String> = env::args().skip(1).collect();
let selected = match args.first().map(|s| s.as_str()) {
None => 0,
Some(arg) => match resolve_sample(arg, &samples) {
Some(idx) => idx,
None => {
eprintln!(
"No sample matches `{}`. Pass a 1-based index or a name \
substring (see the list printed above).",
arg
);
return ExitCode::from(1);
}
},
};
let sample = &samples[selected];
println!(
"\nShowing sample #{}: {} ({} messages)",
selected + 1,
sample.name,
sample.messages.len()
);
let entries: Vec<(String, Vec<A2uiMessage>)> = samples
.iter()
.map(|s| (s.name.clone(), s.messages.clone()))
.collect();
let cfg = GalleryConfig { entries, selected };
dioxus::LaunchBuilder::new()
.with_context(cfg)
.with_cfg(desktop! {
Config::new()
.with_window(
WindowBuilder::new()
.with_title("A2UI · Dioxus Gallery")
.with_inner_size(LogicalSize::new(1080.0, 740.0)),
)
.with_custom_head(format!("<style>{STYLESHEET}</style>"))
})
.launch(app);
ExitCode::SUCCESS
}
#[derive(Clone)]
struct GalleryConfig {
entries: Vec<(String, Vec<A2uiMessage>)>,
selected: usize,
}
fn app() -> Element {
let cfg = use_context::<GalleryConfig>();
let functions: Rc<HashMap<String, Box<dyn FunctionImplementation>>> =
use_hook(|| Rc::new(build_function_map()));
let processor: Signal<MessageProcessor> = use_signal(|| {
let catalogs = vec![build_basic_catalog(), build_minimal_catalog()];
let mut p = MessageProcessor::new(catalogs);
if let Some(msgs) = cfg.entries.get(cfg.selected).map(|(_, m)| m.clone()) {
for msg in &msgs {
let _ = p.process_message(msg.clone());
}
}
p
});
let selected: Signal<usize> = use_signal(|| cfg.selected);
let open_modals: Signal<HashSet<String>> = use_signal(HashSet::new);
let focused: Signal<Option<String>> = use_signal(|| None);
let samples: Rc<Vec<(String, Vec<A2uiMessage>)>> = use_hook(|| Rc::new(cfg.entries.clone()));
use_context_provider(|| processor);
use_context_provider(|| functions);
use_context_provider(|| selected);
use_context_provider(|| open_modals);
use_context_provider(|| focused);
use_context_provider(|| samples);
rsx! { Gallery {} }
}
fn build_function_map() -> HashMap<String, Box<dyn FunctionImplementation>> {
build_basic_functions()
.into_iter()
.map(|f| (f.name().to_string(), f))
.collect()
}