1use crate::simple_vnode::VNode;
10
11#[cfg(target_arch = "wasm32")]
12use std::cell::RefCell;
13#[cfg(target_arch = "wasm32")]
14use std::rc::Rc;
15#[cfg(target_arch = "wasm32")]
16use wasm_bindgen::prelude::*;
17#[cfg(target_arch = "wasm32")]
18use web_sys::{window, Element};
19
20#[cfg(target_arch = "wasm32")]
21thread_local! {
22 static APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
24}
25
26#[cfg(target_arch = "wasm32")]
27struct AppState {
28 render_fn: Rc<dyn Fn() -> VNode>,
29 root_element: Element,
30 document: web_sys::Document,
31}
32
33pub struct App {
35 pub title: String,
37 pub root: VNode,
39 pub render_fn: Option<Box<dyn Fn() -> VNode>>,
41}
42
43impl App {
44 pub fn new(title: impl Into<String>, root: VNode) -> Self {
46 Self {
47 title: title.into(),
48 root,
49 render_fn: None,
50 }
51 }
52
53 pub fn new_reactive<F>(title: impl Into<String>, render_fn: F) -> Self
55 where
56 F: Fn() -> VNode + 'static,
57 {
58 let initial_vnode = render_fn();
59 Self {
60 title: title.into(),
61 root: initial_vnode,
62 render_fn: Some(Box::new(render_fn)),
63 }
64 }
65
66 #[cfg(target_arch = "wasm32")]
68 pub fn run(self) {
69 use crate::simple_renderer;
70 use wasm_bindgen::JsCast;
71
72 let window = web_sys::window().expect("No window found");
74 let document = window.document().expect("No document found");
75 let root_el = document
76 .get_element_by_id("app")
77 .expect("No #app element found in HTML")
78 .dyn_into::<web_sys::HtmlElement>()
79 .expect("Root is not an HTMLElement");
80
81 let render_fn = self.render_fn;
83 let mut current_vnode = self.root;
84
85 let html = simple_renderer::render_to_html(¤t_vnode);
87 root_el.set_inner_html(&html);
88
89 web_sys::console::log_1(&format!("✅ {} mounted", self.title).into());
90
91 }
94
95 #[cfg(all(not(target_arch = "wasm32"), feature = "desktop"))]
97 pub fn run(self) {
98 use crate::desktop_renderer::DesktopRenderer;
99
100 let title = self.title;
101 let render_fn = self.render_fn;
102 let mut current_vnode = self.root;
103
104 let options = eframe::NativeOptions {
105 viewport: eframe::egui::ViewportBuilder::default()
106 .with_inner_size([800.0, 600.0])
107 .with_title(title.clone()),
108 ..Default::default()
109 };
110
111 let _ = eframe::run_simple_native(&title, options, move |ctx, _frame| {
112 let ctx_clone = ctx.clone();
114 crate::desktop_app_context::set_repaint_callback(move || {
115 ctx_clone.request_repaint();
116 });
117
118 if let Some(ref render) = render_fn {
120 current_vnode = render();
121 }
122
123 let mut renderer = DesktopRenderer::new();
125 renderer.render(ctx, ¤t_vnode);
126 });
127
128 crate::desktop_app_context::clear_repaint_callback();
130 }
131
132 #[cfg(all(not(target_arch = "wasm32"), not(feature = "desktop")))]
134 pub fn run(self) {
135 eprintln!("❌ Error: App::run() requires either:");
136 eprintln!(" - WASM target (for browser)");
137 eprintln!(" - 'desktop' feature (for native)");
138 panic!("Cannot run app without a supported platform");
139 }
140
141 #[cfg(target_arch = "wasm32")]
143 fn run_internal(self) -> Result<(), JsValue> {
144 console_error_panic_hook::set_once();
146
147 web_sys::console::log_1(&"🔧 Starting App::run_internal".into());
148
149 let window = window().ok_or("No window found")?;
151 let document = window.document().ok_or("No document found")?;
152
153 web_sys::console::log_1(&"✓ Got window and document".into());
154
155 document.set_title(&self.title);
157
158 let root_element = document
160 .get_element_by_id("app")
161 .or_else(|| document.body().map(|b| b.into()))
162 .ok_or("No root element found")?;
163
164 web_sys::console::log_1(&"✓ Got root element".into());
165
166 root_element.set_inner_html("");
168
169 web_sys::console::log_1(&"✓ Cleared root element".into());
170
171 web_sys::console::log_1(&"🎨 Rendering VNode...".into());
173 let rendered = self.root.render(&document)?;
174
175 web_sys::console::log_1(&"✓ VNode rendered".into());
176
177 root_element.append_child(&rendered)?;
178
179 web_sys::console::log_1(&"✅ UI mounted successfully!".into());
180
181 Ok(())
182 }
183}
184
185#[cfg(target_arch = "wasm32")]
187pub fn mount(root: VNode) {
188 App::new("Windjammer App", root).run()
189}
190
191#[cfg(target_arch = "wasm32")]
193pub fn mount_with_title(title: impl Into<String>, root: VNode) {
194 App::new(title, root).run()
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_app_creation() {
203 let app = App::new("Test App", VNode::Text("Hello".to_string()));
204 assert_eq!(app.title, "Test App");
205 }
206}