use dioxus::prelude::*; use jacquard_common::xrpc::XrpcClient; use polymodel_api::space_polymodel::actor::get_session::GetSession; mod auth; mod browse; mod client; mod fonts; mod foundation; mod mesh; mod profile; mod session; mod shell; mod thing_card; mod thing_detail; mod viewer; mod viewer_route; // Frontend compile-time configuration, generated by build.rs from POLYMODEL_*. mod env; // Server-only indexing infrastructure (Hydrant firehose + SQLite projection). #[cfg(any(feature = "server", test))] mod indexing; // Server-only XRPC read endpoints (appview query layer). #[cfg(any(feature = "server", test))] mod appview; #[cfg(any(feature = "server", test))] mod oauth; use browse::Browse; use client::PolymodelClient; use foundation::Foundation; use profile::{Profile, ProfileSelf}; use session::SessionIdentity; use shell::ToastShelf; use thing_detail::ThingDetail; use viewer_route::Viewer; const FAVICON: Asset = asset!("/assets/favicon.jpg"); const THEME_CSS: Asset = asset!("/assets/styling/theme.css"); const BASE_CSS: Asset = asset!("/assets/styling/base.css"); const CARDS_CSS: Asset = asset!("/assets/styling/cards.css"); const BROWSE_CSS: Asset = asset!("/assets/styling/browse.css"); const POLYMODEL_CSS: Asset = asset!("/assets/styling/polymodel.css"); const PROFILE_CSS: Asset = asset!("/assets/styling/profile.css"); #[cfg(feature = "server")] fn main() { // Initialise tracing and env before dioxus::serve so our filter takes // precedence over any subscriber the Dioxus runtime installs. let _ = dotenvy::dotenv(); let _ = tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,hydrant=warn")), ) .try_init(); // The closure runs inside the server's Tokio runtime at startup, so the // indexing tasks spawned here live for the process lifetime alongside Axum. dioxus::serve(|| async { let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); // Hydrant selects AWS-LC while other deps enable ring; choose before websocket TLS setup. let cfg = indexing::config::ServerConfig::load(); let db = indexing::db::init_db(&cfg) .await .expect("failed to initialize sqlite projection database"); if cfg.sample_data.should_seed(&cfg.database_url) { indexing::sample_data::seed_if_empty(&db) .await .expect("failed to seed local/demo sample data"); } indexing::start_indexing(db.clone()) .await .expect("failed to start indexing pipeline"); let oauth = oauth::bootstrap_oauth(db.clone(), cfg.base_url.as_deref()) .expect("failed to bootstrap OAuth client"); // The same SQLite pool backs both indexing and the appview read handlers. let app_state = appview::state::AppState::new(db, oauth); Ok(dioxus::server::router(App).merge( appview::router() .merge(jacquard_axum::oauth::routes::< jacquard::identity::PublicResolver, oauth::SqliteAuthStore, appview::state::AppState, >(&app_state.oauth_config.clone())) .with_state(app_state), )) }); } #[cfg(not(feature = "server"))] fn main() { #[cfg(all(target_family = "wasm", target_os = "unknown"))] { console_error_panic_hook::set_once(); let mut wasm_log_config = tracing_wasm::WASMLayerConfigBuilder::new(); wasm_log_config.set_max_level(tracing::Level::INFO); tracing_wasm::set_as_global_default_with_config(wasm_log_config.build()); } dioxus::launch(App); } #[derive(Clone, Debug, PartialEq, Routable)] pub(crate) enum Route { #[route("/")] Browse {}, #[route("/foundation")] Foundation {}, #[route("/profile")] ProfileSelf {}, #[route("/profile/:actor")] Profile { actor: String }, #[route("/viewer/:model_id")] Viewer { model_id: String }, #[route("/:repo/thing/:rkey")] ThingDetail { repo: String, rkey: String }, } #[component] fn App() -> Element { let client = use_context_provider(PolymodelClient::default); let session = use_context_provider(|| Signal::new(SessionIdentity::Anonymous)); let toasts = use_context_provider(|| Signal::new(auth::ToastState::default())); let _session_seed = use_resource(move || { let client = client.clone(); let mut session = session; async move { match client.send(GetSession).await { Ok(response) => match response.into_output() { Ok(output) => session.set(SessionIdentity::from_get_session(output)), Err(error) => tracing::warn!(%error, "failed to decode Polymodel session"), }, Err(error) => tracing::warn!(%error, "failed to fetch Polymodel session"), } } }); rsx! { document::Meta { name: "viewport", content: "width=device-width, initial-scale=1" } document::Title { "Polymodel" } document::Link { rel: "icon", href: FAVICON } Stylesheet { href: THEME_CSS } Stylesheet { href: BASE_CSS } Stylesheet { href: CARDS_CSS } Stylesheet { href: BROWSE_CSS } Stylesheet { href: POLYMODEL_CSS } Stylesheet { href: PROFILE_CSS } Router:: {} ToastShelf { toasts } } }