feat: simple dock layout
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use std::env;
|
||||
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
||||
use anyhow::Error;
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::NostrRegistry;
|
||||
@@ -17,8 +18,11 @@ pub struct Account {
|
||||
/// The public key of the account
|
||||
public_key: Option<PublicKey>,
|
||||
|
||||
/// Event subscriptions
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
|
||||
/// Tasks for asynchronous operations
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
_tasks: SmallVec<[Task<Result<(), Error>>; 1]>,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
@@ -51,11 +55,12 @@ impl Account {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let account = args.get(1).and_then(|s| Keys::parse(s).ok());
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
if let Some(keys) = account {
|
||||
tasks.push(
|
||||
// Background
|
||||
// Set signer in background
|
||||
cx.spawn(async move |this, cx| {
|
||||
let public_key = keys.public_key();
|
||||
|
||||
@@ -72,13 +77,22 @@ impl Account {
|
||||
this.public_key = Some(public_key);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
subscriptions.push(
|
||||
// Listen for public key set
|
||||
cx.observe_self(move |this, cx| {
|
||||
if let Some(public_key) = this.public_key {
|
||||
this.init(public_key, cx);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Self {
|
||||
public_key: None,
|
||||
_subscriptions: subscriptions,
|
||||
_tasks: tasks,
|
||||
}
|
||||
}
|
||||
@@ -93,4 +107,37 @@ impl Account {
|
||||
// This method is only called when user is logged in, so unwrap safely
|
||||
self.public_key.unwrap()
|
||||
}
|
||||
|
||||
fn init(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
// Construct a filter to get the user's metadata
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::Metadata)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to the user metadata
|
||||
client.subscribe(filter, Some(opts)).await?;
|
||||
|
||||
// Construct a filter to get the user's contact list
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ContactList)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to the user's contact list
|
||||
client.subscribe(filter, Some(opts)).await?;
|
||||
|
||||
log::info!("Subscribed to user metadata and contact list");
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
self._tasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,3 +19,6 @@ pub const SEARCH_RELAYS: [&str; 3] = [
|
||||
|
||||
/// Default relay for Nostr Connect
|
||||
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
||||
|
||||
/// Default width of the sidebar.
|
||||
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.;
|
||||
|
||||
@@ -14,6 +14,8 @@ use crate::workspace::Workspace;
|
||||
|
||||
mod actions;
|
||||
mod menus;
|
||||
mod panels;
|
||||
mod sidebar;
|
||||
mod themes;
|
||||
mod title_bar;
|
||||
mod workspace;
|
||||
|
||||
1
crates/lume/src/panels/mod.rs
Normal file
1
crates/lume/src/panels/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod startup;
|
||||
41
crates/lume/src/panels/startup.rs
Normal file
41
crates/lume/src/panels/startup.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use gpui::{
|
||||
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Render, Window,
|
||||
};
|
||||
use gpui_component::dock::{Panel, PanelEvent};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Startup> {
|
||||
cx.new(|cx| Startup::new(window, cx))
|
||||
}
|
||||
|
||||
pub struct Startup {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Startup {
|
||||
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for Startup {
|
||||
fn panel_name(&self) -> &'static str {
|
||||
"Startup"
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for Startup {}
|
||||
|
||||
impl Focusable for Startup {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Startup {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().child("Startup")
|
||||
}
|
||||
}
|
||||
41
crates/lume/src/sidebar/mod.rs
Normal file
41
crates/lume/src/sidebar/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use gpui::{
|
||||
div, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Render, Window,
|
||||
};
|
||||
use gpui_component::dock::{Panel, PanelEvent};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||
cx.new(|cx| Sidebar::new(window, cx))
|
||||
}
|
||||
|
||||
pub struct Sidebar {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Sidebar {
|
||||
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Panel for Sidebar {
|
||||
fn panel_name(&self) -> &'static str {
|
||||
"Sidebar"
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for Sidebar {}
|
||||
|
||||
impl Focusable for Sidebar {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Sidebar {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().child("Sidebar")
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use gpui_component::TitleBar;
|
||||
|
||||
use crate::menus;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct AppTitleBar {
|
||||
/// The app menu bar
|
||||
app_menu_bar: Entity<AppMenuBar>,
|
||||
@@ -35,6 +36,7 @@ impl AppTitleBar {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn child<F, E>(mut self, f: F) -> Self
|
||||
where
|
||||
E: IntoElement,
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use common::CLIENT_NAME;
|
||||
use std::sync::Arc;
|
||||
|
||||
use account::Account;
|
||||
use common::{CLIENT_NAME, DEFAULT_SIDEBAR_WIDTH};
|
||||
use gpui::{
|
||||
div, AppContext, Context, Entity, InteractiveElement, IntoElement, ParentElement, Render,
|
||||
Styled, Subscription, Window,
|
||||
div, px, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
||||
Render, Styled, Subscription, Window,
|
||||
};
|
||||
use gpui_component::dock::DockArea;
|
||||
use gpui_component::dock::{DockArea, DockItem};
|
||||
use gpui_component::{v_flex, Root, Theme};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::panels::startup;
|
||||
use crate::sidebar;
|
||||
use crate::title_bar::AppTitleBar;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -25,6 +30,8 @@ impl Workspace {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let dock = cx.new(|cx| DockArea::new("dock", None, window, cx));
|
||||
let title_bar = cx.new(|cx| AppTitleBar::new(CLIENT_NAME, window, cx));
|
||||
let account = Account::global(cx);
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
|
||||
subscriptions.push(
|
||||
@@ -34,12 +41,47 @@ impl Workspace {
|
||||
}),
|
||||
);
|
||||
|
||||
subscriptions.push(
|
||||
// Observe account entity changes
|
||||
cx.observe_in(&account, window, move |this, state, window, cx| {
|
||||
if state.read(cx).has_account() {
|
||||
this.init_app_layout(window, cx);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Self {
|
||||
dock,
|
||||
title_bar,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_app_layout(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let weak_dock = self.dock.downgrade();
|
||||
|
||||
let sidebar = Arc::new(sidebar::init(window, cx));
|
||||
let startup = Arc::new(startup::init(window, cx));
|
||||
|
||||
// Construct left dock (sidebar)
|
||||
let left = DockItem::panel(sidebar);
|
||||
|
||||
// Construct center dock
|
||||
let center = DockItem::split_with_sizes(
|
||||
Axis::Vertical,
|
||||
vec![DockItem::tabs(vec![startup], &weak_dock, window, cx)],
|
||||
vec![None],
|
||||
&weak_dock,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
// Update dock layout
|
||||
self.dock.update(cx, |this, cx| {
|
||||
this.set_left_dock(left, Some(px(DEFAULT_SIDEBAR_WIDTH)), true, window, cx);
|
||||
this.set_center(center, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
|
||||
@@ -9,6 +9,7 @@ common = { path = "../common" }
|
||||
|
||||
nostr-sdk.workspace = true
|
||||
nostr-lmdb.workspace = true
|
||||
nostr-gossip-memory.workspace = true
|
||||
|
||||
gpui.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use common::{config_dir, BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
||||
use nostr_gossip_memory::prelude::*;
|
||||
use nostr_lmdb::NostrLmdb;
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
@@ -61,7 +62,11 @@ impl NostrRegistry {
|
||||
});
|
||||
|
||||
// Construct the nostr client
|
||||
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
|
||||
let client = ClientBuilder::default()
|
||||
.database(lmdb)
|
||||
.gossip(NostrGossipMemory::unbounded())
|
||||
.opts(opts)
|
||||
.build();
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user