use std::sync::Arc; use assets::CustomIconName; use dock::{DockArea, DockItem}; use gpui::prelude::*; use gpui::{Context, Entity, Render, SharedString, Subscription, Window, div, px}; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::{ActiveTheme, Root, Sizable, StyledExt, Theme, TitleBar, h_flex, v_flex}; use signed_state::{Backend, BackendEvent}; use crate::views::SidebarPanel; use crate::views::sidebar::passphrase_dialog; /// Root view of the app: title bar, dock area, status bar. pub struct Workspace { dock: Entity, status: SharedString, _subscriptions: Vec, _passphrase_subscription: Subscription, } impl Workspace { pub fn new(window: &mut Window, cx: &mut Context) -> Self { let dock = cx.new(|cx| { DockArea::new("dock", Some(1), window, cx).panel_style(dock::PanelStyle::TabBar) }); let weak_dock = dock.downgrade(); let sidebar = cx.new(|cx| SidebarPanel::new(weak_dock.clone(), cx)); let weak_sidebar = sidebar.downgrade(); dock.update(cx, |dock_area, cx| { dock_area.set_left_dock( DockItem::panel(Arc::new(sidebar)), Some(px(240.)), true, window, cx, ); }); let backend = Backend::global(cx); let connected = backend.read(cx).is_connected(); let sync_progress = backend.read(cx).sync_progress(); let status = if let Some((total, current)) = sync_progress { format!("Syncing repositories... {current}/{total}").into() } else if connected { "Connected".into() } else { "Connecting...".into() }; let mut subscriptions = vec![]; subscriptions.push(cx.observe_window_appearance(window, |_this, window, cx| { Theme::sync_system_appearance(Some(window), cx); })); subscriptions.push(cx.subscribe(&backend, |this, _backend, event, cx| { match event { BackendEvent::SyncProgress { total, current } => { this.status = format!("Syncing repositories... {current}/{total}").into() } BackendEvent::Synced => this.status = "Connected".into(), BackendEvent::Connected => this.status = "Connected".into(), BackendEvent::Error(error) => this.status = error.clone().into(), _ => return, } cx.notify(); })); // Ask for the passphrase when the stored identity is NIP-49 // encrypted. Subscribed via the window, since opening a dialog // needs one. let passphrase_subscription = window.subscribe(&backend, cx, |_backend, event, window, cx| { if matches!(event, BackendEvent::PassphraseRequired) { passphrase_dialog::open(window, cx); } }); // The event may have fired before this window existed (the backend // is initialized before the first window opens); fall back to the // backend state in that case. if backend.read(cx).passphrase_required() { passphrase_dialog::open(window, cx); } // Open the explore panel after the sidebar has been initialized. cx.defer_in(window, move |_, window, cx| { weak_sidebar .update(cx, |this, cx| { this.open_explore(window, cx); }) .ok(); }); Self { dock, status, _subscriptions: subscriptions, _passphrase_subscription: passphrase_subscription, } } } impl Render for Workspace { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let dialog_layer = Root::render_dialog_layer(window, cx); let notification_layer = Root::render_notification_layer(window, cx); div() // All `img` elements below (avatars, …) load through the shared // bounded LRU cache instead of the window-global asset cache, // so images can be freed when views close or when the cache is // full. .image_cache(crate::image_cache::global(cx)) .id("workspace") .v_flex() .size_full() .child( v_flex() .size_full() // Title Bar .child( TitleBar::new() // Left .child(div()) // Right .child(h_flex().px_2().map(|this| { if self.status == "Connected" { this.child( Button::new("relay") .icon(CustomIconName::GlobalOn) .small() .ghost(), ) } else { this.child( div() .text_xs() .text_color(cx.theme().muted_foreground) .child(self.status.clone()), ) } })), ) // Dock Area .child(self.dock.clone()), ) // Notifications .children(notification_layer) // Modals .children(dialog_layer) } }