use super::{Component, JsValue, Scope};
use crate::OwnerHandle;
use std::{cell::RefCell, rc::Rc};
#[derive(Default, PartialEq)]
enum Phase {
#[default]
Idle,
Starting,
Running,
Stopping,
}
#[derive(Default)]
struct Application {
phase: Phase,
scope: Option<Rc<Scope>>,
}
thread_local! { static APP: RefCell<Application> = RefCell::new(Application::default()); }
struct Transition(bool);
impl Drop for Transition {
fn drop(&mut self) {
if self.0 {
drop(APP.with_borrow_mut(|app| app.scope.take()));
APP.with_borrow_mut(|app| app.phase = Phase::Idle);
}
}
}
pub fn mount<C: Component>(
make: impl FnOnce(OwnerHandle) -> Result<C, JsValue>,
) -> Result<(), JsValue> {
mount_scope(|| C::prepare_component(None, Box::new(make)))
}
#[doc(hidden)]
pub fn mount_scope(prepare: impl FnOnce() -> Result<Scope, JsValue>) -> Result<(), JsValue> {
APP.with_borrow_mut(|app| {
if app.phase != Phase::Idle {
return Err(JsValue::from_str(
"fusor: application already mounted or changing lifecycle state",
));
}
app.phase = Phase::Starting;
Ok(())
})?;
let mut transition = Transition(true);
let scope = Rc::new(prepare()?);
APP.with_borrow_mut(|app| app.scope = Some(scope.clone()));
scope.try_commit()?;
APP.with_borrow_mut(|app| app.phase = Phase::Running);
transition.0 = false;
Ok(())
}
pub fn owner() -> Option<OwnerHandle> {
APP.with_borrow(|app| app.scope.as_ref().map(|scope| scope.owner()))
}
pub fn unmount() -> Result<(), JsValue> {
let scope = APP.with_borrow_mut(|app| match app.phase {
Phase::Idle => Ok(None),
Phase::Running => {
app.phase = Phase::Stopping;
Ok(app.scope.take())
}
_ => Err(JsValue::from_str("fusor: reentrant application teardown")),
})?;
let _transition = Transition(true);
drop(scope);
Ok(())
}