diff --git a/Cargo.lock b/Cargo.lock index b230840..3074f89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,7 +208,7 @@ dependencies = [ "serde", "serde_repr", "url", - "zbus 5.3.0", + "zbus 5.3.1", ] [[package]] @@ -380,7 +380,7 @@ dependencies = [ [[package]] name = "async-wsocket" version = "0.12.0" -source = "git+https://github.com/yukibtc/async-wsocket?rev=da5da94574f73da1b4d638fd9736298a67139c59#da5da94574f73da1b4d638fd9736298a67139c59" +source = "git+https://github.com/yukibtc/async-wsocket?rev=5fba7927576064ac0698a4ee3df0d26e5cf726dd#5fba7927576064ac0698a4ee3df0d26e5cf726dd" dependencies = [ "async-utility", "futures", @@ -1014,7 +1014,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "indexmap", "rustc-hash 2.1.0", @@ -1090,6 +1090,7 @@ dependencies = [ "gpui", "itertools 0.13.0", "nostr-sdk", + "registry", "reqwest_client", "rust-embed", "serde", @@ -1263,9 +1264,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -1316,7 +1317,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "proc-macro2", "quote", @@ -1518,9 +1519,9 @@ dependencies = [ [[package]] name = "etagere" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c732752b4ea11b36052d417d444d1474e948639fb02ff970efd7f18c180ca7f7" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" dependencies = [ "euclid", "svg_fmt", @@ -2011,7 +2012,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2096,7 +2097,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "proc-macro2", "quote", @@ -2301,7 +2302,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "bytes", @@ -2911,7 +2912,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "bindgen", @@ -2948,9 +2949,9 @@ dependencies = [ [[package]] name = "metal" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ "bitflags 2.8.0", "block", @@ -3090,7 +3091,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.38.0" -source = "git+https://github.com/rust-nostr/nostr#d030b776f521fc2bb032f9490bab89e3bfbb5108" +source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" dependencies = [ "aes", "base64", @@ -3118,7 +3119,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.38.0" -source = "git+https://github.com/rust-nostr/nostr#d030b776f521fc2bb032f9490bab89e3bfbb5108" +source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" dependencies = [ "flatbuffers", "nostr", @@ -3128,19 +3129,18 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.38.0" -source = "git+https://github.com/rust-nostr/nostr#d030b776f521fc2bb032f9490bab89e3bfbb5108" +source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" dependencies = [ "async-utility", "heed", "nostr", "nostr-database", - "tokio", ] [[package]] name = "nostr-relay-pool" version = "0.38.0" -source = "git+https://github.com/rust-nostr/nostr#d030b776f521fc2bb032f9490bab89e3bfbb5108" +source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" dependencies = [ "async-utility", "async-wsocket", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.38.0" -source = "git+https://github.com/rust-nostr/nostr#d030b776f521fc2bb032f9490bab89e3bfbb5108" +source = "git+https://github.com/rust-nostr/nostr#1470b8b00437e586fb86035484f942d6202db83a" dependencies = [ "async-utility", "nostr", @@ -3392,9 +3392,9 @@ dependencies = [ [[package]] name = "objc2-encode" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" @@ -3566,9 +3566,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "option-ext" @@ -4163,7 +4163,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "derive_refineable", ] @@ -4197,6 +4197,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "registry" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "gpui", + "itertools 0.13.0", + "nostr-sdk", + "state", +] + [[package]] name = "reqwest" version = "0.12.8" @@ -4292,7 +4304,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "bytes", @@ -4413,9 +4425,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.8.0", "errno", @@ -4642,7 +4654,7 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "serde", @@ -4967,7 +4979,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "arrayvec", "log", @@ -5417,9 +5429,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.24.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" dependencies = [ "futures-util", "log", @@ -5576,9 +5588,9 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" [[package]] name = "tungstenite" -version = "0.24.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" dependencies = [ "byteorder", "bytes", @@ -5590,7 +5602,7 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 1.0.69", + "thiserror 2.0.11", "utf-8", ] @@ -5663,9 +5675,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] name = "unicode-linebreak" @@ -5789,7 +5801,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#aacd80ee4a65d490cefee4b6413ece78c5611945" +source = "git+https://github.com/zed-industries/zed#b7c6ffa6c2598dceb4bb4804538e636ed8e85700" dependencies = [ "anyhow", "async-fs", @@ -5814,9 +5826,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "getrandom", "serde", @@ -6624,9 +6636,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192a0d989036cd60a1e91a54c9851fb9ad5bd96125d41803eed79d2e2ef74bd7" +checksum = "2494e4b3f44d8363eef79a8a75fc0649efb710eef65a66b5e688a5eb4afe678a" dependencies = [ "async-broadcast", "async-executor", @@ -6653,7 +6665,7 @@ dependencies = [ "windows-sys 0.59.0", "winnow", "xdg-home", - "zbus_macros 5.3.0", + "zbus_macros 5.3.1", "zbus_names 4.1.1", "zvariant 5.2.0", ] @@ -6673,9 +6685,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3685b5c81fce630efc3e143a4ded235b107f1b1cdf186c3f115529e5e5ae4265" +checksum = "445efc01929302aee95e2b25bbb62a301ea8a6369466e4278e58e7d1dfb23631" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 40da91e..f54151f 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -12,6 +12,7 @@ path = "src/main.rs" ui = { path = "../ui" } common = { path = "../common" } state = { path = "../state" } +registry = { path = "../registry" } gpui.workspace = true reqwest_client.workspace = true diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 5d91075..645913c 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -10,8 +10,8 @@ use gpui::{ #[cfg(target_os = "linux")] use gpui::{WindowBackgroundAppearance, WindowDecorations}; use nostr_sdk::prelude::*; +use registry::{app::AppRegistry, chat::ChatRegistry, contact::Contact}; use state::{get_client, initialize_client}; -use states::{app::AppRegistry, chat::ChatRegistry}; use std::{collections::HashSet, str::FromStr, sync::Arc, time::Duration}; use tokio::{ sync::{mpsc, Mutex}, @@ -21,7 +21,6 @@ use ui::Root; use views::app::AppView; mod asset; -mod states; mod views; actions!(main_menu, [Quit]); @@ -188,6 +187,7 @@ async fn main() { // Set quit action cx.on_action(quit); + // Spawn a thread to handle Nostr events cx.spawn(|async_cx| async move { let (tx, rx) = smol::channel::unbounded::(); @@ -219,25 +219,40 @@ async fn main() { }) .detach(); + // Spawn a thread to update Nostr signer cx.spawn(|async_cx| { let task = cx.read_credentials(KEYRING_SERVICE); async move { if let Ok(Some((npub, secret))) = task.await { - let public_key = PublicKey::from_bech32(&npub).expect("Something wrong."); - let hex = String::from_utf8(secret).expect("Something wrong."); - let keys = Keys::parse(&hex).expect("Something wrong."); + let public_key = + PublicKey::from_bech32(&npub).expect("Public Key isn't valid."); - // Update signer - async_cx + let query: anyhow::Result = async_cx .background_executor() - .spawn(async move { client.set_signer(keys).await }) - .detach(); + .spawn(async move { + let hex = String::from_utf8(secret)?; + let keys = Keys::parse(&hex)?; - // Update global state - _ = async_cx.update_global::(|state, cx| { - state.set_user(public_key, cx); - }); + // Update signer + _ = client.set_signer(keys).await; + + // Get metadata + if let Some(metadata) = + client.database().metadata(public_key).await? + { + Ok(metadata) + } else { + Ok(Metadata::new()) + } + }) + .await; + + if let Ok(metadata) = query { + _ = async_cx.update_global::(|state, cx| { + state.set_user(Contact::new(public_key, metadata), cx); + }); + } } else { _ = async_cx.update_global::(|state, _| { state.is_loading = false; diff --git a/crates/app/src/states/mod.rs b/crates/app/src/states/mod.rs deleted file mode 100644 index f8379b1..0000000 --- a/crates/app/src/states/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod app; -pub mod chat; diff --git a/crates/app/src/views/account.rs b/crates/app/src/views/account.rs deleted file mode 100644 index bc7b62d..0000000 --- a/crates/app/src/views/account.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::{get_client, states::app::AppRegistry}; -use common::constants::IMAGE_SERVICE; -use gpui::prelude::FluentBuilder; -use gpui::{ - actions, img, Context, IntoElement, Model, ObjectFit, ParentElement, Render, Styled, - StyledImage, ViewContext, -}; -use nostr_sdk::prelude::*; -use ui::{ - button::{Button, ButtonVariants}, - popup_menu::PopupMenuExt, - Icon, IconName, Sizable, -}; - -actions!(account, [ToDo]); - -pub struct Account { - public_key: PublicKey, - metadata: Model>, -} - -impl Account { - pub fn new(public_key: PublicKey, cx: &mut ViewContext<'_, Self>) -> Self { - let metadata = cx.new_model(|_| None); - let refreshs = cx.global_mut::().refreshs(); - - if let Some(refreshs) = refreshs.upgrade() { - cx.observe(&refreshs, |this, _, cx| { - this.load_metadata(cx); - }) - .detach(); - } - - Self { - public_key, - metadata, - } - } - - pub fn load_metadata(&self, cx: &mut ViewContext) { - let mut async_cx = cx.to_async(); - let async_metadata = self.metadata.clone(); - - cx.foreground_executor() - .spawn({ - let client = get_client(); - let public_key = self.public_key; - - async move { - let metadata = async_cx - .background_executor() - .spawn(async move { client.database().metadata(public_key).await }) - .await; - - if let Ok(metadata) = metadata { - _ = async_cx.update_model(&async_metadata, |model, cx| { - *model = metadata; - cx.notify(); - }); - } - } - }) - .detach(); - } -} - -impl Render for Account { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - Button::new("account") - .ghost() - .xsmall() - .reverse() - .icon(Icon::new(IconName::ChevronDownSmall)) - .map(|this| { - if let Some(metadata) = self.metadata.read(cx).as_ref() { - this.map(|this| { - if let Some(picture) = metadata.picture.clone() { - this.flex_shrink_0().child( - img(format!("{}/?url={}&w=72&h=72&n=-1", IMAGE_SERVICE, picture)) - .size_5() - .rounded_full() - .object_fit(ObjectFit::Cover), - ) - } else { - this.flex_shrink_0() - .child(img("brand/avatar.png").size_5().rounded_full()) - } - }) - } else { - this.flex_shrink_0() - .child(img("brand/avatar.png").size_5().rounded_full()) - } - }) - .popup_menu(move |this, _cx| { - this.menu("Profile", Box::new(ToDo)) - .menu("Contacts", Box::new(ToDo)) - .menu("Settings", Box::new(ToDo)) - .separator() - .menu("Change account", Box::new(ToDo)) - }) - } -} diff --git a/crates/app/src/views/app.rs b/crates/app/src/views/app.rs index 6d95452..39d5cc5 100644 --- a/crates/app/src/views/app.rs +++ b/crates/app/src/views/app.rs @@ -1,20 +1,20 @@ -use super::{ - account::Account, chat::ChatPanel, onboarding::Onboarding, sidebar::Sidebar, - welcome::WelcomePanel, -}; -use crate::states::{app::AppRegistry, chat::ChatRegistry}; +use super::{chat::ChatPanel, onboarding::Onboarding, sidebar::Sidebar, welcome::WelcomePanel}; use gpui::{ - div, impl_internal_actions, px, svg, Axis, Context, Edges, InteractiveElement, IntoElement, - Model, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, - WindowContext, + actions, div, img, impl_internal_actions, px, svg, Axis, Edges, InteractiveElement, + IntoElement, ObjectFit, ParentElement, Render, Styled, StyledImage, View, ViewContext, + VisualContext, WeakView, WindowContext, }; +use registry::{app::AppRegistry, chat::ChatRegistry, contact::Contact}; use serde::Deserialize; use std::sync::Arc; use ui::{ + button::{Button, ButtonVariants}, dock_area::{dock::DockPlacement, DockArea, DockItem}, notification::NotificationType, - theme::{scale::ColorScaleStep, ActiveTheme, Theme}, - ContextModal, Root, TitleBar, + popup_menu::PopupMenuExt, + prelude::FluentBuilder, + theme::{scale::ColorScaleStep, ActiveTheme}, + ContextModal, Icon, IconName, Root, Sizable, TitleBar, }; #[derive(Clone, PartialEq, Eq, Deserialize)] @@ -28,7 +28,10 @@ pub struct AddPanel { pub position: DockPlacement, } +// Dock actions impl_internal_actions!(dock, [AddPanel]); +// Account actions +actions!(account, [OpenProfile, OpenContacts, OpenSettings, Logout]); pub struct DockAreaTab { id: &'static str, @@ -41,98 +44,31 @@ pub const DOCK_AREA: DockAreaTab = DockAreaTab { }; pub struct AppView { - account: Model>>, onboarding: View, dock: View, } impl AppView { pub fn new(cx: &mut ViewContext<'_, Self>) -> AppView { - // Sync theme with system - cx.observe_window_appearance(|_, cx| { - Theme::sync_system_appearance(cx); - }) - .detach(); - - // Account - let account = cx.new_model(|_| None); - let async_account = account.clone(); - - // Onboarding let onboarding = cx.new_view(Onboarding::new); - - // Dock let dock = cx.new_view(|cx| DockArea::new(DOCK_AREA.id, Some(DOCK_AREA.version), cx)); // Get current user from app state - let current_user = cx.global::().current_user(); + let weak_user = cx.global::().user(); - if let Some(current_user) = current_user.upgrade() { - cx.observe(¤t_user, move |view, model, cx| { - if let Some(public_key) = model.read(cx).clone().as_ref() { - Self::init_layout(view.dock.downgrade(), cx); - // TODO: save dock state and load previous state on startup - - let view = cx.new_view(|cx| { - let view = Account::new(*public_key, cx); - // Initial load metadata - view.load_metadata(cx); - - view - }); - - cx.update_model(&async_account, |model, cx| { - *model = Some(view); - cx.notify(); - }); + if let Some(user) = weak_user.upgrade() { + cx.observe(&user, move |view, this, cx| { + if this.read(cx).is_some() { + Self::render_dock(view.dock.downgrade(), cx); } }) .detach(); } - AppView { - account, - onboarding, - dock, - } + AppView { onboarding, dock } } - fn init_layout(dock_area: WeakView, cx: &mut WindowContext) { - let left = DockItem::panel(Arc::new(Sidebar::new(cx))); - let center = Self::init_dock_items(&dock_area, cx); - - _ = dock_area.update(cx, |view, cx| { - view.set_version(DOCK_AREA.version, cx); - view.set_left_dock(left, Some(px(240.)), true, cx); - view.set_center(center, cx); - view.set_dock_collapsible( - Edges { - left: false, - ..Default::default() - }, - cx, - ); - // TODO: support right dock? - // TODO: support bottom dock? - }); - } - - fn init_dock_items(dock_area: &WeakView, cx: &mut WindowContext) -> DockItem { - DockItem::split_with_sizes( - Axis::Vertical, - vec![DockItem::tabs( - vec![Arc::new(WelcomePanel::new(cx))], - None, - dock_area, - cx, - )], - vec![None], - dock_area, - cx, - ) - } - - fn on_action_add_panel(&mut self, action: &AddPanel, cx: &mut ViewContext) { + fn on_panel_action(&mut self, action: &AddPanel, cx: &mut ViewContext) { match &action.panel { PanelKind::Room(id) => { if let Some(weak_room) = cx.global::().room(id, cx) { @@ -152,53 +88,109 @@ impl AppView { } }; } + + fn render_account(&self, account: Contact) -> impl IntoElement { + Button::new("account") + .ghost() + .xsmall() + .reverse() + .icon(Icon::new(IconName::ChevronDownSmall)) + .child( + img(account.avatar()) + .size_5() + .rounded_full() + .object_fit(ObjectFit::Cover), + ) + .popup_menu(move |this, _cx| { + this.menu("Profile", Box::new(OpenProfile)) + .menu("Contacts", Box::new(OpenContacts)) + .menu("Settings", Box::new(OpenSettings)) + .separator() + .menu("Change account", Box::new(Logout)) + }) + } + + fn render_dock(dock_area: WeakView, cx: &mut WindowContext) { + let left = DockItem::panel(Arc::new(Sidebar::new(cx))); + let center = DockItem::split_with_sizes( + Axis::Vertical, + vec![DockItem::tabs( + vec![Arc::new(WelcomePanel::new(cx))], + None, + &dock_area, + cx, + )], + vec![None], + &dock_area, + cx, + ); + + _ = dock_area.update(cx, |view, cx| { + view.set_version(DOCK_AREA.version, cx); + view.set_left_dock(left, Some(px(240.)), true, cx); + view.set_center(center, cx); + view.set_dock_collapsible( + Edges { + left: false, + ..Default::default() + }, + cx, + ); + // TODO: support right dock? + // TODO: support bottom dock? + }); + } } impl Render for AppView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let modal_layer = Root::render_modal_layer(cx); let notification_layer = Root::render_notification_layer(cx); - - let mut content = div().size_full().flex().flex_col(); - - if cx.global::().is_loading { - content = content.child(div()).child( - div().flex_1().flex().items_center().justify_center().child( - svg() - .path("brand/coop.svg") - .size_12() - .text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)), - ), - ) - } else if let Some(account) = self.account.read(cx).as_ref() { - content = content - .child( - TitleBar::new() - // Left side - .child(div()) - // Right side - .child( - div() - .flex() - .items_center() - .justify_end() - .gap_1() - .px_2() - .child(account.clone()), - ), - ) - .child(self.dock.clone()) - .on_action(cx.listener(Self::on_action_add_panel)) - } else { - content = content - .child(TitleBar::new()) - .child(self.onboarding.clone()) - } + let state = cx.global::(); div() .size_full() - .child(content) + .flex() + .flex_col() + // Main + .map(|this| { + if state.is_loading { + this + // Placeholder + .child(div()) + .child( + div().flex_1().flex().items_center().justify_center().child( + svg() + .path("brand/coop.svg") + .size_12() + .text_color(cx.theme().base.step(cx, ColorScaleStep::THREE)), + ), + ) + } else if let Some(contact) = state.current_user(cx) { + this.child( + TitleBar::new() + // Left side + .child(div()) + // Right side + .child( + div() + .flex() + .items_center() + .justify_end() + .gap_1() + .px_2() + .child(self.render_account(contact)), + ), + ) + .child(self.dock.clone()) + .on_action(cx.listener(Self::on_panel_action)) + } else { + this.child(TitleBar::new()).child(self.onboarding.clone()) + } + }) + // Notification .child(div().absolute().top_8().children(notification_layer)) + // Modal .children(modal_layer) } } diff --git a/crates/app/src/views/chat/message.rs b/crates/app/src/views/chat/message.rs index f20ca9d..b0c03ef 100644 --- a/crates/app/src/views/chat/message.rs +++ b/crates/app/src/views/chat/message.rs @@ -1,8 +1,8 @@ -use crate::states::chat::room::Member; use gpui::{ div, img, px, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString, Styled, WindowContext, }; +use registry::contact::Contact; use ui::{ theme::{scale::ColorScaleStep, ActiveTheme}, StyledExt, @@ -10,7 +10,7 @@ use ui::{ #[derive(Clone, Debug, IntoElement)] pub struct Message { - member: Member, + member: Contact, content: SharedString, ago: SharedString, } @@ -26,7 +26,7 @@ impl PartialEq for Message { } impl Message { - pub fn new(member: Member, content: SharedString, ago: SharedString) -> Self { + pub fn new(member: Contact, content: SharedString, ago: SharedString) -> Self { Self { member, content, diff --git a/crates/app/src/views/chat/mod.rs b/crates/app/src/views/chat/mod.rs index 7d72306..3c6376d 100644 --- a/crates/app/src/views/chat/mod.rs +++ b/crates/app/src/views/chat/mod.rs @@ -12,7 +12,9 @@ use gpui::{ use itertools::Itertools; use message::Message; use nostr_sdk::prelude::*; +use registry::room::Room; use smol::fs; +use state::get_client; use tokio::sync::oneshot; use ui::{ button::{Button, ButtonRounded, ButtonVariants}, @@ -27,8 +29,6 @@ use ui::{ v_flex, ContextModal, Icon, IconName, Sizable, }; -use crate::{get_client, states::chat::room::Room}; - mod message; #[derive(Clone)] diff --git a/crates/app/src/views/mod.rs b/crates/app/src/views/mod.rs index 1a8dfbb..4e26b09 100644 --- a/crates/app/src/views/mod.rs +++ b/crates/app/src/views/mod.rs @@ -1,4 +1,3 @@ -mod account; mod chat; mod onboarding; mod sidebar; diff --git a/crates/app/src/views/onboarding/mod.rs b/crates/app/src/views/onboarding/mod.rs index 3fdf485..53b72b2 100644 --- a/crates/app/src/views/onboarding/mod.rs +++ b/crates/app/src/views/onboarding/mod.rs @@ -1,10 +1,10 @@ use common::constants::KEYRING_SERVICE; use gpui::{div, IntoElement, ParentElement, Render, Styled, View, ViewContext, VisualContext}; use nostr_sdk::prelude::*; +use registry::{app::AppRegistry, contact::Contact}; +use state::get_client; use ui::input::{InputEvent, TextInput}; -use crate::{get_client, states::app::AppRegistry}; - pub struct Onboarding { input: View, } @@ -43,10 +43,28 @@ impl Onboarding { async move { if task.await.is_ok() { - _ = client.set_signer(keys).await; - _ = async_cx.update_global::(|state, cx| { - state.set_user(public_key, cx); - }); + let query: anyhow::Result = async_cx + .background_executor() + .spawn(async move { + // Update signer + _ = client.set_signer(keys).await; + + // Get metadata + if let Some(metadata) = + client.database().metadata(public_key).await? + { + Ok(metadata) + } else { + Ok(Metadata::new()) + } + }) + .await; + + if let Ok(metadata) = query { + _ = async_cx.update_global::(|state, cx| { + state.set_user(Contact::new(public_key, metadata), cx); + }); + } } } }) diff --git a/crates/app/src/views/sidebar/compose.rs b/crates/app/src/views/sidebar/compose.rs index 0fe856c..575979b 100644 --- a/crates/app/src/views/sidebar/compose.rs +++ b/crates/app/src/views/sidebar/compose.rs @@ -1,10 +1,3 @@ -use crate::{ - get_client, - states::{ - app::AppRegistry, - chat::room::{Member, Room}, - }, -}; use common::utils::{random_name, room_hash}; use gpui::{ div, img, impl_internal_actions, px, uniform_list, Context, FocusHandle, InteractiveElement, @@ -12,7 +5,9 @@ use gpui::{ View, ViewContext, VisualContext, WindowContext, }; use nostr_sdk::prelude::*; +use registry::{app::AppRegistry, contact::Contact, room::Room}; use serde::Deserialize; +use state::get_client; use std::{collections::HashSet, time::Duration}; use ui::{ button::{Button, ButtonRounded}, @@ -32,7 +27,7 @@ pub struct Compose { title_input: View, message_input: View, user_input: View, - contacts: Model>>, + contacts: Model>>, selected: Model>, focus_handle: FocusHandle, is_loading: bool, @@ -77,15 +72,15 @@ impl Compose { let client = get_client(); async move { - let query: anyhow::Result, anyhow::Error> = async_cx + let query: anyhow::Result, anyhow::Error> = async_cx .background_executor() .spawn(async move { let signer = client.signer().await?; let public_key = signer.get_public_key().await?; let profiles = client.database().contacts(public_key).await?; - let members: Vec = profiles + let members: Vec = profiles .into_iter() - .map(|profile| Member::new(profile.public_key(), profile.metadata())) + .map(|profile| Contact::new(profile.public_key(), profile.metadata())) .collect(); Ok(members) @@ -120,11 +115,9 @@ impl Compose { } pub fn room(&self, cx: &WindowContext) -> Option { - let weak_user = cx.global::().current_user(); - - if let Some(user) = weak_user.upgrade() { - let public_key = user.read(cx).unwrap(); + let current_user = cx.global::().current_user(cx); + if let Some(current_user) = current_user { // Convert selected pubkeys into nostr tags let tags: Vec = self .selected @@ -135,19 +128,19 @@ impl Compose { let tags = Tags::new(tags); // Convert selected pubkeys into members - let members: Vec = self + let members: Vec = self .selected .read(cx) .clone() .into_iter() - .map(|pk| Member::new(pk, Metadata::new())) + .map(|pk| Contact::new(pk, Metadata::new())) .collect(); // Get room's id let id = room_hash(&tags); // Get room's owner (current user) - let owner = Member::new(public_key, Metadata::new()); + let owner = Contact::new(current_user.public_key(), Metadata::new()); // Get room's title let title = self.title_input.read(cx).text().to_string().into(); @@ -193,7 +186,7 @@ impl Compose { _ = async_cx.update_view(&view, |this, cx| { this.contacts.update(cx, |this, cx| { if let Some(members) = this { - members.insert(0, Member::new(public_key, metadata)); + members.insert(0, Contact::new(public_key, metadata)); } cx.notify(); }); diff --git a/crates/app/src/views/sidebar/inbox.rs b/crates/app/src/views/sidebar/inbox.rs index ddbee1d..14103b4 100644 --- a/crates/app/src/views/sidebar/inbox.rs +++ b/crates/app/src/views/sidebar/inbox.rs @@ -1,12 +1,10 @@ -use crate::{ - states::chat::ChatRegistry, - views::app::{AddPanel, PanelKind}, -}; +use crate::views::app::{AddPanel, PanelKind}; use common::utils::message_ago; use gpui::{ div, img, percentage, prelude::FluentBuilder, px, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, ViewContext, }; +use registry::chat::ChatRegistry; use ui::{ dock_area::dock::DockPlacement, skeleton::Skeleton, diff --git a/crates/app/src/views/sidebar/mod.rs b/crates/app/src/views/sidebar/mod.rs index c783afb..90cc7d4 100644 --- a/crates/app/src/views/sidebar/mod.rs +++ b/crates/app/src/views/sidebar/mod.rs @@ -1,10 +1,11 @@ -use crate::{states::chat::ChatRegistry, views::sidebar::inbox::Inbox}; +use crate::views::sidebar::inbox::Inbox; use compose::Compose; use gpui::{ div, px, AnyElement, AppContext, BorrowAppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WindowContext, }; +use registry::chat::ChatRegistry; use ui::{ button::{Button, ButtonRounded, ButtonVariants}, dock_area::{ diff --git a/crates/registry/Cargo.toml b/crates/registry/Cargo.toml new file mode 100644 index 0000000..1a183f5 --- /dev/null +++ b/crates/registry/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "registry" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +common = { path = "../common" } +state = { path = "../state" } + +gpui.workspace = true +nostr-sdk.workspace = true +anyhow.workspace = true +itertools.workspace = true diff --git a/crates/app/src/states/app.rs b/crates/registry/src/app.rs similarity index 50% rename from crates/app/src/states/app.rs rename to crates/registry/src/app.rs index 93c2b9a..7365634 100644 --- a/crates/app/src/states/app.rs +++ b/crates/registry/src/app.rs @@ -1,62 +1,30 @@ -use crate::get_client; use common::constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID}; -use gpui::*; +use gpui::{AppContext, Context, Global, Model, WeakModel, WindowContext}; use nostr_sdk::prelude::*; +use state::get_client; use std::time::Duration; +use crate::contact::Contact; + pub struct AppRegistry { - user: Model>, - refreshs: Model>, - pub(crate) is_loading: bool, + user: Model>, + pub is_loading: bool, } impl Global for AppRegistry {} impl AppRegistry { pub fn set_global(cx: &mut AppContext) { - let refreshs = cx.new_model(|_| Vec::new()); - let user = cx.new_model(|_| None); - let async_user = user.clone(); + let is_loading = true; + let user: Model> = cx.new_model(|_| None); - cx.set_global(Self { - user, - refreshs, - is_loading: true, - }); - - cx.observe(&async_user, |model, cx| { - if let Some(public_key) = model.read(cx).clone().as_ref() { + cx.observe(&user, |this, cx| { + if let Some(contact) = this.read(cx).as_ref() { let client = get_client(); - let public_key = *public_key; - - let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID); - let new_message_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID); - - // Create a filter for getting all gift wrapped events send to current user - let all_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); - - // Subscription options - let opts = SubscribeAutoCloseOptions::default() - .exit_policy(ReqExitPolicy::WaitDurationAfterEOSE(Duration::from_secs(5))); - - // Create a filter for getting new message - let new_message = Filter::new() - .kind(Kind::GiftWrap) - .pubkey(public_key) - .limit(0); + let public_key = contact.public_key(); cx.background_executor() .spawn(async move { - // Subscribe for all messages - _ = client - .subscribe_with_id(all_messages_sub_id, vec![all_messages], Some(opts)) - .await; - - // Subscribe for new message - _ = client - .subscribe_with_id(new_message_sub_id, vec![new_message], None) - .await; - let subscription = Filter::new() .kind(Kind::Metadata) .author(public_key) @@ -72,27 +40,56 @@ impl AppRegistry { // Get contact list _ = client.sync(subscription, &SyncOptions::default()).await; + + let all_messages_sub_id = SubscriptionId::new(ALL_MESSAGES_SUB_ID); + let new_message_sub_id = SubscriptionId::new(NEW_MESSAGE_SUB_ID); + + // Create a filter for getting all gift wrapped events send to current user + let all_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); + + // Subscription options + let opts = SubscribeAutoCloseOptions::default().exit_policy( + ReqExitPolicy::WaitDurationAfterEOSE(Duration::from_secs(5)), + ); + + // Create a filter for getting new message + let new_message = Filter::new() + .kind(Kind::GiftWrap) + .pubkey(public_key) + .limit(0); + + // Subscribe for all messages + _ = client + .subscribe_with_id(all_messages_sub_id, vec![all_messages], Some(opts)) + .await; + + // Subscribe for new message + _ = client + .subscribe_with_id(new_message_sub_id, vec![new_message], None) + .await; }) .detach(); } }) .detach(); + + cx.set_global(Self { user, is_loading }); } - pub fn set_user(&mut self, public_key: PublicKey, cx: &mut AppContext) { - self.user.update(cx, |model, cx| { - *model = Some(public_key); + pub fn user(&self) -> WeakModel> { + self.user.downgrade() + } + + pub fn set_user(&mut self, contact: Contact, cx: &mut AppContext) { + self.user.update(cx, |this, cx| { + *this = Some(contact); cx.notify(); }); self.is_loading = false; } - pub fn current_user(&self) -> WeakModel> { - self.user.downgrade() - } - - pub fn refreshs(&self) -> WeakModel> { - self.refreshs.downgrade() + pub fn current_user(&self, cx: &WindowContext) -> Option { + self.user.read(cx).clone() } } diff --git a/crates/app/src/states/chat/mod.rs b/crates/registry/src/chat.rs similarity index 94% rename from crates/app/src/states/chat/mod.rs rename to crates/registry/src/chat.rs index bdbf2d0..09d301b 100644 --- a/crates/app/src/states/chat/mod.rs +++ b/crates/registry/src/chat.rs @@ -2,16 +2,14 @@ use common::utils::{compare, room_hash}; use gpui::{AppContext, Context, Global, Model, WeakModel}; use itertools::Itertools; use nostr_sdk::prelude::*; -use room::Room; +use state::get_client; use std::cmp::Reverse; -use crate::get_client; - -pub mod room; +use crate::room::Room; pub struct Inbox { - pub(crate) rooms: Vec>, - pub(crate) is_loading: bool, + pub rooms: Vec>, + pub is_loading: bool, } impl Inbox { @@ -23,6 +21,12 @@ impl Inbox { } } +impl Default for Inbox { + fn default() -> Self { + Self::new() + } +} + pub struct ChatRegistry { inbox: Model, } @@ -31,12 +35,11 @@ impl Global for ChatRegistry {} impl ChatRegistry { pub fn set_global(cx: &mut AppContext) { - let inbox = cx.new_model(|_| Inbox::new()); + let inbox = cx.new_model(|_| Inbox::default()); cx.observe_new_models::(|this, cx| { // Get all pubkeys to load metadata - let mut pubkeys: Vec = this.members.iter().map(|m| m.public_key()).collect(); - pubkeys.push(this.owner.public_key()); + let pubkeys = this.get_all_keys(); cx.spawn(|weak_model, mut async_cx| async move { let query: Result, Error> = async_cx diff --git a/crates/registry/src/contact.rs b/crates/registry/src/contact.rs new file mode 100644 index 0000000..0cfa048 --- /dev/null +++ b/crates/registry/src/contact.rs @@ -0,0 +1,63 @@ +use common::{constants::IMAGE_SERVICE, utils::shorted_public_key}; +use nostr_sdk::prelude::*; + +#[derive(Debug, Clone)] +pub struct Contact { + public_key: PublicKey, + metadata: Metadata, +} + +impl PartialEq for Contact { + fn eq(&self, other: &Self) -> bool { + self.public_key() == other.public_key() + } +} + +impl Contact { + pub fn new(public_key: PublicKey, metadata: Metadata) -> Self { + Self { + public_key, + metadata, + } + } + + /// Get contact's public key + pub fn public_key(&self) -> PublicKey { + self.public_key + } + + /// Set contact's metadata + pub fn metadata(&mut self, metadata: &Metadata) { + self.metadata = metadata.clone() + } + + /// Get contact's avatar + pub fn avatar(&self) -> String { + if let Some(picture) = &self.metadata.picture { + format!( + "{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1", + IMAGE_SERVICE, picture + ) + } else { + "brand/avatar.png".into() + } + } + + /// Get contact's name + /// Fallback to public key as shorted format + pub fn name(&self) -> String { + if let Some(display_name) = &self.metadata.display_name { + if !display_name.is_empty() { + return display_name.clone(); + } + } + + if let Some(name) = &self.metadata.name { + if !name.is_empty() { + return name.clone(); + } + } + + shorted_public_key(self.public_key) + } +} diff --git a/crates/registry/src/lib.rs b/crates/registry/src/lib.rs new file mode 100644 index 0000000..9e95f98 --- /dev/null +++ b/crates/registry/src/lib.rs @@ -0,0 +1,4 @@ +pub mod app; +pub mod chat; +pub mod contact; +pub mod room; diff --git a/crates/app/src/states/chat/room.rs b/crates/registry/src/room.rs similarity index 60% rename from crates/app/src/states/chat/room.rs rename to crates/registry/src/room.rs index 13d700d..34f387b 100644 --- a/crates/app/src/states/chat/room.rs +++ b/crates/registry/src/room.rs @@ -1,72 +1,15 @@ -use common::{ - constants::IMAGE_SERVICE, - utils::{compare, random_name, room_hash, shorted_public_key}, -}; +use common::utils::{compare, random_name, room_hash}; use gpui::SharedString; use nostr_sdk::prelude::*; -#[derive(Debug, Clone)] -pub struct Member { - public_key: PublicKey, - metadata: Metadata, -} - -impl PartialEq for Member { - fn eq(&self, other: &Self) -> bool { - self.public_key() == other.public_key() - } -} - -impl Member { - pub fn new(public_key: PublicKey, metadata: Metadata) -> Self { - Self { - public_key, - metadata, - } - } - - pub fn public_key(&self) -> PublicKey { - self.public_key - } - - pub fn avatar(&self) -> String { - if let Some(picture) = &self.metadata.picture { - format!( - "{}/?url={}&w=100&h=100&fit=cover&mask=circle&n=-1", - IMAGE_SERVICE, picture - ) - } else { - "brand/avatar.png".into() - } - } - - pub fn name(&self) -> String { - if let Some(display_name) = &self.metadata.display_name { - if !display_name.is_empty() { - return display_name.clone(); - } - } - - if let Some(name) = &self.metadata.name { - if !name.is_empty() { - return name.clone(); - } - } - - shorted_public_key(self.public_key) - } - - pub fn update(&mut self, metadata: &Metadata) { - self.metadata = metadata.clone() - } -} +use crate::contact::Contact; #[derive(Debug)] pub struct Room { pub id: u64, pub title: Option, - pub owner: Member, // Owner always match current user - pub members: Vec, // Extract from event's tags + pub owner: Contact, // Owner always match current user + pub members: Vec, // Extract from event's tags pub last_seen: Timestamp, pub is_group: bool, pub new_messages: Vec, // Hold all new messages @@ -87,8 +30,8 @@ impl PartialEq for Room { impl Room { pub fn new( id: u64, - owner: Member, - members: Vec, + owner: Contact, + members: Vec, title: Option, last_seen: Timestamp, ) -> Self { @@ -110,16 +53,17 @@ impl Room { } } + /// Convert nostr event to room pub fn parse(event: &Event) -> Room { let id = room_hash(&event.tags); let last_seen = event.created_at; - let owner = Member::new(event.pubkey, Metadata::default()); - let members: Vec = event + let owner = Contact::new(event.pubkey, Metadata::default()); + let members: Vec = event .tags .public_keys() .copied() - .map(|public_key| Member::new(public_key, Metadata::default())) + .map(|public_key| Contact::new(public_key, Metadata::default())) .collect(); let title = if let Some(tag) = event.tags.find(TagKind::Title) { @@ -131,19 +75,21 @@ impl Room { Self::new(id, owner, members, title, last_seen) } + /// Set contact's metadata by public key pub fn set_metadata(&mut self, public_key: PublicKey, metadata: Metadata) { if self.owner.public_key() == public_key { - self.owner.update(&metadata); + self.owner.metadata(&metadata); } for member in self.members.iter_mut() { if member.public_key() == public_key { - member.update(&metadata); + member.metadata(&metadata); } } } - pub fn member(&self, public_key: &PublicKey) -> Option { + /// Get room's member by public key + pub fn member(&self, public_key: &PublicKey) -> Option { if &self.owner.public_key() == public_key { Some(self.owner.clone()) } else { @@ -154,6 +100,7 @@ impl Room { } } + /// Get room's display name pub fn name(&self) -> String { if self.members.len() <= 2 { self.members @@ -174,6 +121,7 @@ impl Room { } } + /// Get all public keys from room's contacts pub fn get_all_keys(&self) -> Vec { let mut pubkeys: Vec<_> = self.members.iter().map(|m| m.public_key()).collect(); pubkeys.push(self.owner.public_key()); diff --git a/crates/ui/src/dock_area/tab_panel.rs b/crates/ui/src/dock_area/tab_panel.rs index 4fef5cd..f8b358f 100644 --- a/crates/ui/src/dock_area/tab_panel.rs +++ b/crates/ui/src/dock_area/tab_panel.rs @@ -669,9 +669,8 @@ impl TabPanel { self.active_panel() .map(|panel| { - div() + v_flex() .id("tab-content") - .group("") .overflow_hidden() .flex_1() .p_1() diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 60ac53b..4f6d0ec 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -1,7 +1,7 @@ use crate::{ modal::Modal, notification::{Notification, NotificationList}, - theme::{scale::ColorScaleStep, ActiveTheme}, + theme::{scale::ColorScaleStep, ActiveTheme, Theme}, window_border, }; use gpui::{ @@ -162,6 +162,11 @@ struct ActiveModal { impl Root { pub fn new(view: AnyView, cx: &mut ViewContext) -> Self { + cx.observe_window_appearance(|_, cx| { + Theme::sync_system_appearance(cx); + }) + .detach(); + Self { previous_focus_handle: None, active_modals: Vec::new(),