refactor state init

This commit is contained in:
2026-09-18 20:22:51 +07:00
parent 1b6ef6f574
commit 91c40b0799
9 changed files with 158 additions and 140 deletions
+8 -15
View File
@@ -3,7 +3,7 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task, Window}; use gpui::{App, AppContext, Context, Entity, Global, SharedString, Task};
use gpui_updater_core::{EngineConfig, Release, UpdateEngine, UpdateStatus, Verification, Version}; use gpui_updater_core::{EngineConfig, Release, UpdateEngine, UpdateStatus, Verification, Version};
use instant::Duration; use instant::Duration;
@@ -35,7 +35,7 @@ fn uses_managed_updates() -> bool {
} }
/// Initialize the auto-update system. /// Initialize the auto-update system.
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
if uses_managed_updates() { if uses_managed_updates() {
log::info!( log::info!(
"Skipping auto-update initialization: updates are managed by the installed distribution channel (Flatpak/Snap)" "Skipping auto-update initialization: updates are managed by the installed distribution channel (Flatpak/Snap)"
@@ -60,10 +60,7 @@ pub fn init(window: &mut Window, cx: &mut App) {
return; return;
}; };
AutoUpdater::set_global( AutoUpdater::set_global(cx.new(|cx| AutoUpdater::new(version, filter, cx)), cx);
cx.new(|cx| AutoUpdater::new(window, version, filter, cx)),
cx,
);
} }
struct GlobalAutoUpdater(Entity<AutoUpdater>); struct GlobalAutoUpdater(Entity<AutoUpdater>);
@@ -103,21 +100,17 @@ impl AutoUpdater {
cx.set_global(GlobalAutoUpdater(state)); cx.set_global(GlobalAutoUpdater(state));
} }
fn new( fn new(version: Version, filter: AssetFilter, cx: &mut Context<Self>) -> Self {
window: &mut Window, let entity = cx.entity().downgrade();
version: Version,
filter: AssetFilter,
cx: &mut Context<Self>,
) -> Self {
let source = GiteaSource::new(GITEA_API_BASE, GITEA_REPO_OWNER, GITEA_REPO_NAME, filter); let source = GiteaSource::new(GITEA_API_BASE, GITEA_REPO_OWNER, GITEA_REPO_NAME, filter);
let config = EngineConfig::new(version.clone()).verification(Verification::Checksum); let config = EngineConfig::new(version.clone()).verification(Verification::Checksum);
let engine = Arc::new(UpdateEngine::new(source, config)); let engine = Arc::new(UpdateEngine::new(source, config));
// Schedule an auto-check after a 2-minute delay // Schedule an auto-check after a 2-minute delay
cx.defer_in(window, |_this, _window, cx| { cx.defer(move |cx| {
cx.spawn(async move |this, cx| { cx.spawn(async move |cx| {
cx.background_executor().timer(AUTO_CHECK_DELAY).await; cx.background_executor().timer(AUTO_CHECK_DELAY).await;
this.update(cx, |this, cx| this.check(cx)).ok(); entity.update(cx, |this, cx| this.check(cx)).ok();
}) })
.detach(); .detach();
}); });
+10 -6
View File
@@ -26,8 +26,8 @@ pub use state::FileAttachment;
/// A static keypair used only for signing locally-cached rumor events. /// A static keypair used only for signing locally-cached rumor events.
static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate); static LOCAL_KEYS: LazyLock<Keys> = LazyLock::new(Keys::generate);
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
ChatRegistry::set_global(cx.new(|cx| ChatRegistry::new(window, cx)), cx); ChatRegistry::set_global(cx.new(ChatRegistry::new), cx);
} }
struct GlobalChatRegistry(Entity<ChatRegistry>); struct GlobalChatRegistry(Entity<ChatRegistry>);
@@ -150,7 +150,8 @@ impl ChatRegistry {
} }
/// Create a new chat registry instance /// Create a new chat registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let entity = cx.entity().downgrade();
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let (tx, rx) = flume::unbounded::<Signal>(); let (tx, rx) = flume::unbounded::<Signal>();
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -167,9 +168,12 @@ impl ChatRegistry {
}), }),
); );
// Run at the end of the current cycle cx.defer(move |cx| {
cx.defer_in(window, |this, _window, cx| { entity
this.get_rooms(cx); .update(cx, |this, cx| {
this.get_rooms(cx);
})
.ok();
}); });
Self { Self {
+14 -10
View File
@@ -4,7 +4,7 @@ use anyhow::Result;
use concord::CommunityId; use concord::CommunityId;
use concord::cord01::KIND_WRAP; use concord::cord01::KIND_WRAP;
use concord::store::CommunityState; use concord::store::CommunityState;
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task, Window}; use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use state::NostrRegistry; use state::NostrRegistry;
@@ -15,8 +15,8 @@ mod sync;
pub use community::*; pub use community::*;
pub use sync::*; pub use sync::*;
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
CommunityRegistry::set_global(cx.new(|cx| CommunityRegistry::new(window, cx)), cx); CommunityRegistry::set_global(cx.new(CommunityRegistry::new), cx);
} }
struct GlobalCommunityRegistry(Entity<CommunityRegistry>); struct GlobalCommunityRegistry(Entity<CommunityRegistry>);
@@ -56,7 +56,8 @@ impl CommunityRegistry {
cx.set_global(GlobalCommunityRegistry(state)); cx.set_global(GlobalCommunityRegistry(state));
} }
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let entity = cx.entity().downgrade();
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let (tx, rx) = flume::bounded::<Signal>(256); let (tx, rx) = flume::bounded::<Signal>(256);
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -69,12 +70,15 @@ impl CommunityRegistry {
} }
})); }));
cx.defer_in(window, move |this, _window, cx| { cx.defer(move |cx| {
this.handle_notifications(cx); entity
.update(cx, |this, cx| {
if nostr.read(cx).current_user().is_some() { this.handle_notifications(cx);
this.load(cx); if nostr.read(cx).current_user().is_some() {
} this.load(cx);
}
})
.ok();
}); });
Self { Self {
+26 -12
View File
@@ -24,8 +24,8 @@ use ui::{Disableable, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
const IDENTIFIER: &str = "coop:device"; const IDENTIFIER: &str = "coop:device";
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
DeviceRegistry::set_global(cx.new(|cx| DeviceRegistry::new(window, cx)), cx); DeviceRegistry::set_global(cx.new(DeviceRegistry::new), cx);
} }
struct GlobalDeviceRegistry(Entity<DeviceRegistry>); struct GlobalDeviceRegistry(Entity<DeviceRegistry>);
@@ -89,7 +89,8 @@ impl DeviceRegistry {
} }
/// Create a new device registry instance /// Create a new device registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let entity = cx.entity().downgrade();
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let settings = AppSettings::global(cx); let settings = AppSettings::global(cx);
@@ -114,8 +115,10 @@ impl DeviceRegistry {
}), }),
); );
cx.defer_in(window, |this, window, cx| { cx.defer(move |cx| {
this.handle_notifications(window, cx); entity
.update(cx, |this, cx| this.handle_notifications(cx))
.ok();
}); });
Self { Self {
@@ -127,7 +130,7 @@ impl DeviceRegistry {
} }
} }
fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn handle_notifications(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer(); let signer = nostr.read(cx).signer();
@@ -168,18 +171,18 @@ impl DeviceRegistry {
Ok(()) Ok(())
})); }));
self.tasks.push(cx.spawn_in(window, async move |this, cx| { self.tasks.push(cx.spawn(async move |this, cx| {
while let Ok(event) = rx.recv_async().await { while let Ok(event) = rx.recv_async().await {
match event.kind { match event.kind {
Kind::Custom(10044) => { Kind::Custom(10044) => {
this.update_in(cx, |this, _window, cx| { this.update(cx, |this, cx| {
this.set_encryption(&event, cx); this.set_encryption(&event, cx);
})?; })?;
} }
// New request event from other device // New request event from other device
Kind::Custom(4454) => { Kind::Custom(4454) => {
this.update_in(cx, |this, window, cx| { this.update(cx, |this, cx| {
this.ask_for_approval(event, window, cx); this.ask_for_approval(event, cx);
})?; })?;
} }
// New response event from the master device // New response event from the master device
@@ -591,7 +594,7 @@ impl DeviceRegistry {
} }
/// Handle encryption request /// Handle encryption request
fn ask_for_approval(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) { fn ask_for_approval(&mut self, event: Event, cx: &mut Context<Self>) {
// Ignore if there is already a pending request // Ignore if there is already a pending request
if self.pending_request { if self.pending_request {
return; return;
@@ -600,7 +603,18 @@ impl DeviceRegistry {
// Show notification // Show notification
let notification = self.notification(event, cx); let notification = self.notification(event, cx);
window.push_notification(notification, cx);
// The registry is global and not bound to a window, so surface the
// request in an open window.
if let Some(window) = cx.windows().first().copied() {
if let Err(error) = window.update(cx, |_view, window, cx| {
window.push_notification(notification, cx);
}) {
log::warn!("Failed to show encryption key request: {error}");
}
} else {
log::warn!("Failed to show encryption key request: no open window");
}
} }
/// Build a notification for the encryption request. /// Build a notification for the encryption request.
+7 -6
View File
@@ -3,7 +3,7 @@ use std::sync::RwLock;
use anyhow::{Error, anyhow}; use anyhow::{Error, anyhow};
use common::EventExt; use common::EventExt;
use gpui::{App, AppContext, Context, Entity, Global, Task, Window}; use gpui::{App, AppContext, Context, Entity, Global, Task};
use instant::Duration; use instant::Duration;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
@@ -13,8 +13,8 @@ mod person;
pub use person::*; pub use person::*;
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
PersonRegistry::set_global(cx.new(|cx| PersonRegistry::new(window, cx)), cx); PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
} }
struct GlobalPersonRegistry(Entity<PersonRegistry>); struct GlobalPersonRegistry(Entity<PersonRegistry>);
@@ -56,7 +56,8 @@ impl PersonRegistry {
} }
/// Create a new person registry instance /// Create a new person registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let entity = cx.entity().downgrade();
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
@@ -96,8 +97,8 @@ impl PersonRegistry {
})); }));
// Load all user profiles from the database // Load all user profiles from the database
cx.defer_in(window, |this, _window, cx| { cx.defer(move |cx| {
this.load(cx); entity.update(cx, |this, cx| this.load(cx)).ok();
}); });
Self { Self {
+17 -15
View File
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
use smallvec::{SmallVec, smallvec}; use smallvec::{SmallVec, smallvec};
use theme::{Theme, ThemeFamily, ThemeMode}; use theme::{Theme, ThemeFamily, ThemeMode};
pub fn init(window: &mut Window, cx: &mut App) { pub fn init(cx: &mut App) {
AppSettings::set_global(cx.new(|cx| AppSettings::new(window, cx)), cx) AppSettings::set_global(cx.new(AppSettings::new), cx)
} }
const DEFAULT_FILE_SERVER: &str = "https://nostr.download/"; const DEFAULT_FILE_SERVER: &str = "https://nostr.download/";
@@ -195,7 +195,8 @@ impl AppSettings {
cx.set_global(GlobalAppSettings(state)); cx.set_global(GlobalAppSettings(state));
} }
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let entity = cx.entity().downgrade();
let inner = cx.new(|_| Settings::default()); let inner = cx.new(|_| Settings::default());
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -207,8 +208,8 @@ impl AppSettings {
); );
// Run at the end of current cycle // Run at the end of current cycle
cx.defer_in(window, |this, window, cx| { cx.defer(move |cx| {
this.load(window, cx); entity.update(cx, |this, cx| this.load(cx)).ok();
}); });
Self { Self {
@@ -226,7 +227,7 @@ impl AppSettings {
} }
/// Load settings /// Load settings
fn load(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn load(&mut self, cx: &mut Context<Self>) {
let task: Task<Result<Settings, Error>> = cx.background_spawn(async move { let task: Task<Result<Settings, Error>> = cx.background_spawn(async move {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
@@ -238,7 +239,7 @@ impl AppSettings {
Err(anyhow!("Not found")) Err(anyhow!("Not found"))
}); });
cx.spawn_in(window, async move |this, cx| { cx.spawn(async move |this, cx| {
let mut settings = task.await.unwrap_or(Settings::default()); let mut settings = task.await.unwrap_or(Settings::default());
// Move settings still pointed at the old default file server over to the new one // Move settings still pointed at the old default file server over to the new one
@@ -247,9 +248,10 @@ impl AppSettings {
} }
// Update settings // Update settings
this.update_in(cx, |this, window, cx| { this.update(cx, |this, cx| {
this.set_settings(settings, cx); this.set_settings(settings, cx);
this.apply_theme(window, cx); this.apply_theme(None, cx);
cx.refresh_windows();
}) })
.ok(); .ok();
}) })
@@ -281,7 +283,7 @@ impl AppSettings {
}); });
// Apply the new theme // Apply the new theme
self.apply_theme(window, cx); self.apply_theme(Some(window), cx);
} }
/// Reset theme /// Reset theme
@@ -290,22 +292,22 @@ impl AppSettings {
this.theme = None; this.theme = None;
cx.notify(); cx.notify();
}); });
self.apply_theme(window, cx); self.apply_theme(Some(window), cx);
} }
/// Apply theme /// Apply theme
pub fn apply_theme(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub fn apply_theme(&mut self, mut window: Option<&mut Window>, cx: &mut Context<Self>) {
if let Some(name) = self.inner.read(cx).theme.as_ref() { if let Some(name) = self.inner.read(cx).theme.as_ref() {
let mode = self.inner.read(cx).theme_mode; let mode = self.inner.read(cx).theme_mode;
if let Ok(new_theme) = ThemeFamily::from_assets(name) { if let Ok(new_theme) = ThemeFamily::from_assets(name) {
Theme::apply_theme(Rc::new(new_theme), Some(window), cx); Theme::apply_theme(Rc::new(new_theme), window.as_deref_mut(), cx);
Theme::change(mode, Some(window), cx); Theme::change(mode, window, cx);
} else { } else {
log::info!("Failed to load theme: {name}"); log::info!("Failed to load theme: {name}");
} }
} else { } else {
Theme::apply_theme(Rc::new(ThemeFamily::default()), Some(window), cx); Theme::apply_theme(Rc::new(ThemeFamily::default()), window, cx);
} }
} }
+21 -16
View File
@@ -4,7 +4,7 @@ use anyhow::{Error, anyhow};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use browser_signer_proxy::prelude::*; use browser_signer_proxy::prelude::*;
use common::config_dir; use common::config_dir;
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window}; use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
use gpui_tokio::Tokio; use gpui_tokio::Tokio;
use instant::Duration; use instant::Duration;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
@@ -29,7 +29,7 @@ pub use nip4e::*;
pub use nip05::*; pub use nip05::*;
pub use signer::{CoopAuthUrlHandler, UniversalSigner}; pub use signer::{CoopAuthUrlHandler, UniversalSigner};
pub fn init(window: &mut Window, cx: &mut App, cli_key: Option<SecretKey>) { pub fn init(cx: &mut App, cli_key: Option<SecretKey>) {
// rustls uses the `aws_lc_rs` provider by default // rustls uses the `aws_lc_rs` provider by default
// This only errors if the default provider has already // This only errors if the default provider has already
// been installed. We can ignore this `Result`. // been installed. We can ignore this `Result`.
@@ -42,7 +42,7 @@ pub fn init(window: &mut Window, cx: &mut App, cli_key: Option<SecretKey>) {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
gpui_tokio::init(cx); gpui_tokio::init(cx);
NostrRegistry::set_global(cx.new(|cx| NostrRegistry::new(window, cx, cli_key)), cx); NostrRegistry::set_global(cx.new(|cx| NostrRegistry::new(cx, cli_key)), cx);
} }
struct GlobalNostrRegistry(Entity<NostrRegistry>); struct GlobalNostrRegistry(Entity<NostrRegistry>);
@@ -105,7 +105,8 @@ impl NostrRegistry {
} }
/// Create a new nostr instance /// Create a new nostr instance
fn new(window: &mut Window, cx: &mut Context<Self>, cli_key: Option<SecretKey>) -> Self { fn new(cx: &mut Context<Self>, cli_key: Option<SecretKey>) -> Self {
let entity = cx.entity().downgrade();
let signer = UniversalSigner::new(Keys::generate()); let signer = UniversalSigner::new(Keys::generate());
let authenticator = SignerAuthenticator::new(signer.clone()); let authenticator = SignerAuthenticator::new(signer.clone());
@@ -132,19 +133,23 @@ impl NostrRegistry {
}) })
.build(); .build();
// Connect to bootstrap relays after the window is ready // Connect to bootstrap relays once the registry has been returned to the app
cx.defer_in(window, |this, _window, cx| { cx.defer(move |cx| {
this.connect_bootstrap_relays(cx); entity
.update(cx, |this, cx| {
this.connect_bootstrap_relays(cx);
if cfg!(target_arch = "wasm32") { if cfg!(target_arch = "wasm32") {
cx.emit(StateEvent::NoSigner); cx.emit(StateEvent::NoSigner);
} else if let Some(secret) = cli_key { } else if let Some(secret) = cli_key {
// Use CLI-provided key -- same path as get_user_credential // Use CLI-provided key -- same path as get_user_credential
let keys = Keys::new(secret); let keys = Keys::new(secret);
this.set_signer(keys, cx); this.set_signer(keys, cx);
} else { } else {
this.get_user_credential(cx); this.get_user_credential(cx);
} }
})
.ok();
}); });
Self { Self {
+29 -32
View File
@@ -31,6 +31,12 @@ fn main() {
.with_assets(Assets) .with_assets(Assets)
.with_http_client(Arc::new(reqwest_client::ReqwestClient::new())) .with_http_client(Arc::new(reqwest_client::ReqwestClient::new()))
.run(move |cx| { .run(move |cx| {
// Initialize components
ui::init(cx);
// Initialize theme registry
theme::init(cx);
// Load embedded fonts in assets/fonts // Load embedded fonts in assets/fonts
load_embedded_fonts(cx); load_embedded_fonts(cx);
@@ -55,6 +61,29 @@ fn main() {
disabled: false, disabled: false,
}]); }]);
// Initialize settings
settings::init(cx);
// Initialize the nostr client
state::init(cx, cli_key);
// Initialize person registry
person::init(cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
device::init(cx);
// Initialize app registry
chat::init(cx);
// Initialize community registry
community::init(cx);
// Initialize auto update
auto_update::init(cx);
// Set up the window bounds // Set up the window bounds
let bounds = Bounds::centered(None, size(px(960.0), px(720.0)), cx); let bounds = Bounds::centered(None, size(px(960.0), px(720.0)), cx);
@@ -77,43 +106,11 @@ fn main() {
..Default::default() ..Default::default()
}; };
// Open a window with default options
cx.open_window(opts, |window, cx| { cx.open_window(opts, |window, cx| {
// Initialize components
ui::init(cx);
// Initialize theme registry
theme::init(cx);
// Initialize settings
settings::init(window, cx);
// Initialize the nostr client
state::init(window, cx, cli_key);
// Initialize person registry
person::init(window, cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
device::init(window, cx);
// Initialize app registry
chat::init(window, cx);
// Initialize community registry
community::init(window, cx);
// Initialize auto update
auto_update::init(window, cx);
// Root view
cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx)) cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx))
}) })
.expect("Failed to open window. Please restart the application."); .expect("Failed to open window. Please restart the application.");
// Bring the app to the foreground
cx.activate(true); cx.activate(true);
}); });
} }
+26 -28
View File
@@ -47,35 +47,33 @@ pub fn run() -> Result<(), JsValue> {
}; };
app.run(|cx| { app.run(|cx| {
// Open the root window // Initialize components
ui::init(cx);
// Initialize theme registry
theme::init(cx);
// Initialize settings
settings::init(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
device::init(cx);
// Initialize app registry
chat::init(cx);
// Initialize community registry
community::init(cx);
cx.open_window(WindowOptions::default(), |window, cx| { cx.open_window(WindowOptions::default(), |window, cx| {
// Initialize components
ui::init(cx);
// Initialize theme registry
theme::init(cx);
// Initialize settings
settings::init(window, cx);
// Initialize the nostr client
state::init(window, cx, None);
// Initialize person registry
person::init(window, cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
device::init(window, cx);
// Initialize app registry
chat::init(window, cx);
// Initialize community registry
community::init(window, cx);
// Root view
cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx)) cx.new(|cx| Root::new(workspace::init(window, cx).into(), window, cx))
}) })
.expect("Failed to open window. Please restart the application."); .expect("Failed to open window. Please restart the application.");