Files
coop/web/src/lib.rs
T

164 lines
4.7 KiB
Rust

#![cfg(target_arch = "wasm32")]
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use gpui::*;
use universal_time::{Instant, MonotonicClock, SystemTime, WallClock, define_time_provider};
use wasm_bindgen::prelude::*;
thread_local! {
/// Dropping the handle releases the app, so the page's lifetime must own it.
static APPLICATION: RefCell<Option<ApplicationHandle>> = const { RefCell::new(None) };
}
/// Serves `Date.now()` as the wall clock and `performance.now()` as the monotonic clock.
struct BrowserTimeProvider;
impl WallClock for BrowserTimeProvider {
fn system_time(&self) -> SystemTime {
SystemTime::from_unix_duration(core::time::Duration::from_secs_f64(
js_sys::Date::now() / 1000.0,
))
}
}
impl MonotonicClock for BrowserTimeProvider {
fn instant(&self) -> Instant {
Instant::from_ticks(core::time::Duration::from_secs_f64(instant::now() / 1000.0))
}
}
define_time_provider!(BrowserTimeProvider);
fn init_logging() {
// Called from both entry points; the first one wins
static ONCE: std::sync::OnceLock<()> = std::sync::OnceLock::new();
ONCE.get_or_init(|| {
console_error_panic_hook::set_once();
// Initialize logging to browser console
console_log::init_with_level(log::Level::Info).ok();
// Also initialize tracing for WASM
tracing_wasm::set_as_global_default();
});
}
/// Open the persistent nostr database before the app starts.
#[wasm_bindgen]
pub async fn open_database() -> Result<(), JsValue> {
init_logging();
state::web_database::open().await;
Ok(())
}
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
init_logging();
// Sets the panic hook and GPUI's console logger
gpui_platform::web_init();
let platform = std::rc::Rc::new(gpui_web::WebPlatform::new_with_backend_and_font_fallback(
false,
gpui_web::WebBackendPreference::Auto,
gpui_web::CanvasFontFallback::EmojiAndCjk,
));
let http_client = Arc::new(platform.fetch_http_client());
let app = Application::with_platform(platform)
.with_http_client(http_client)
.with_assets(assets::CombinedAssets);
APPLICATION.with(|application| {
*application.borrow_mut() = Some(app.run_embedded(|cx| {
cx.bind_keys([
KeyBinding::new("cmd-d", chat_ui::ToggleDictation, None),
KeyBinding::new("ctrl-d", chat_ui::ToggleDictation, None),
]);
gpui_kit::init(cx);
// Register the bundled gpui-kit themes before anything renders
settings::init_themes(cx);
load_embedded_fonts(cx);
// Initialize settings
settings::init(cx);
// Apply the persisted appearance, theme selection and tuning
settings::apply_persisted_theme(cx);
// Layer the web font overrides on top of the applied theme
apply_web_fonts(cx);
// Initialize the nostr client
state::init(cx, None);
// Initialize person registry
person::init(cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
nip4e::init(cx);
// Initialize app registry
chat::init(cx);
// Initialize groups registry
nip29::init(cx);
gpui_kit::open_window(WindowOptions::default(), cx, |window, cx| {
workspace::init(window, cx)
})
.expect("Failed to open window. Please restart the application.");
cx.activate(true);
}));
});
Ok(())
}
/// Register every bundled font with the text system before any text is laid out
fn load_embedded_fonts(cx: &mut App) {
let font_paths = assets::Assets::iter()
.filter(|path| path.starts_with("fonts/") && path.ends_with(".ttf"))
.collect::<Vec<_>>();
let fonts = font_paths
.iter()
.filter_map(|path| assets::Assets::get(path.as_ref()))
.map(|font| font.data)
.collect::<Vec<_>>();
cx.text_system()
.add_fonts(fonts)
.expect("Failed to load bundled fonts");
}
fn apply_web_fonts(cx: &mut App) {
use gpui_kit::component::Theme;
const UI_FONT: &str = "Inter";
Theme::update(cx, |theme| {
theme.font_family = UI_FONT.into();
theme.mono_font_family = UI_FONT.into();
for config in [&mut theme.light_theme, &mut theme.dark_theme] {
let config = Rc::make_mut(config);
config.font_family = Some(UI_FONT.into());
config.mono_font_family = Some(UI_FONT.into());
}
});
}