wip: refactor
This commit is contained in:
46
Cargo.lock
generated
46
Cargo.lock
generated
@@ -1199,7 +1199,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"coop-ui",
|
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
@@ -1214,28 +1213,7 @@ dependencies = [
|
|||||||
"smol",
|
"smol",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
"ui",
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "coop-ui"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"chrono",
|
|
||||||
"gpui",
|
|
||||||
"image",
|
|
||||||
"itertools 0.13.0",
|
|
||||||
"nostr-sdk",
|
|
||||||
"once_cell",
|
|
||||||
"paste",
|
|
||||||
"regex",
|
|
||||||
"rust-embed",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"smallvec",
|
|
||||||
"smol",
|
|
||||||
"unicode-segmentation",
|
|
||||||
"uuid",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5941,6 +5919,28 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"gpui",
|
||||||
|
"image",
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"nostr-sdk",
|
||||||
|
"once_cell",
|
||||||
|
"paste",
|
||||||
|
"regex",
|
||||||
|
"rust-embed",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"smallvec",
|
||||||
|
"smol",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
|
|||||||
3
assets/icons/compose-fill.svg
Normal file
3
assets/icons/compose-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path fill="#000" fill-rule="evenodd" d="M19.432 2.738c.505.54.728 1.327.443 2.133-.606 1.713-1.798 3.124-2.797 4.087a15.74 15.74 0 0 1-1.045.921l.137.1c.93.684 1.416 1.975.757 3.118-1.221 2.12-4.356 5.803-11.192 5.803a.753.753 0 0 1-.15-.015A32.702 32.702 0 0 0 5.5 21.25a.75.75 0 0 1-1.5 0c0-4.43.821-8.93 2.909-12.485 2.106-3.587 5.49-6.182 10.492-6.749a2.404 2.404 0 0 1 2.031.722Z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 522 B |
3
assets/icons/group-fill.svg
Normal file
3
assets/icons/group-fill.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path fill="#000" d="M3.999 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm9.499.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0ZM7.999 12c-1.765 0-3.236.635-4.365 1.72-1.117 1.074-1.868 2.557-2.282 4.225C.932 19.64 2.351 21 3.9 21h8.197c1.55 0 2.968-1.361 2.548-3.055-.413-1.668-1.164-3.151-2.281-4.225-1.13-1.085-2.6-1.72-4.365-1.72Zm6.174.715c1.21 1.337 1.983 3.011 2.414 4.749.231.934.167 1.79-.103 2.536h3.86c1.538 0 2.996-1.365 2.51-3.075C22.06 14.14 20.103 12 16.997 12c-1.08 0-2.023.26-2.825.715Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 595 B |
@@ -9,7 +9,7 @@ name = "coop"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
coop-ui = { path = "../ui" }
|
ui = { path = "../ui" }
|
||||||
|
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
reqwest_client.workspace = true
|
reqwest_client.workspace = true
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use asset::Assets;
|
use asset::Assets;
|
||||||
use coop_ui::Root;
|
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
@@ -14,6 +13,7 @@ use tokio::{
|
|||||||
sync::{mpsc, Mutex},
|
sync::{mpsc, Mutex},
|
||||||
time::sleep,
|
time::sleep,
|
||||||
};
|
};
|
||||||
|
use ui::Root;
|
||||||
|
|
||||||
use constants::{ALL_MESSAGES_SUB_ID, APP_NAME, FAKE_SIG, METADATA_DELAY, NEW_MESSAGE_SUB_ID};
|
use constants::{ALL_MESSAGES_SUB_ID, APP_NAME, FAKE_SIG, METADATA_DELAY, NEW_MESSAGE_SUB_ID};
|
||||||
use states::{
|
use states::{
|
||||||
@@ -81,7 +81,7 @@ async fn main() {
|
|||||||
_ = client.connect().await;
|
_ = client.connect().await;
|
||||||
|
|
||||||
// Signal
|
// Signal
|
||||||
let (signal_tx, mut signal_rx) = mpsc::channel::<Signal>(10000);
|
let (signal_tx, mut signal_rx) = mpsc::channel::<Signal>(4000); // TODO: adjust?
|
||||||
let (mta_tx, mut mta_rx) = mpsc::unbounded_channel::<PublicKey>();
|
let (mta_tx, mut mta_rx) = mpsc::unbounded_channel::<PublicKey>();
|
||||||
|
|
||||||
// Re use sender
|
// Re use sender
|
||||||
@@ -92,6 +92,7 @@ async fn main() {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let sig = Signature::from_str(FAKE_SIG).unwrap();
|
let sig = Signature::from_str(FAKE_SIG).unwrap();
|
||||||
let new_message = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
let new_message = SubscriptionId::new(NEW_MESSAGE_SUB_ID);
|
||||||
|
let all_messages = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
#[allow(clippy::collapsible_match)]
|
#[allow(clippy::collapsible_match)]
|
||||||
@@ -142,8 +143,10 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let RelayMessage::EndOfStoredEvents(subscription_id) = message {
|
} else if let RelayMessage::EndOfStoredEvents(subscription_id) = message {
|
||||||
if let Err(e) = signal_tx.send(Signal::RecvEose(subscription_id)).await {
|
if subscription_id == all_messages {
|
||||||
println!("Send error: {}", e)
|
if let Err(e) = signal_tx.send(Signal::RecvEose(subscription_id)).await {
|
||||||
|
println!("Send error: {}", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,50 +206,32 @@ async fn main() {
|
|||||||
SignalRegistry::set_global(cx, mta_tx_clone);
|
SignalRegistry::set_global(cx, mta_tx_clone);
|
||||||
|
|
||||||
// Initialize components
|
// Initialize components
|
||||||
coop_ui::init(cx);
|
ui::init(cx);
|
||||||
|
|
||||||
// Set quit action
|
// Set quit action
|
||||||
cx.on_action(quit);
|
cx.on_action(quit);
|
||||||
|
|
||||||
/*
|
|
||||||
cx.spawn(|async_cx| async move {
|
cx.spawn(|async_cx| async move {
|
||||||
let accounts = get_all_accounts_from_keyring();
|
let (tx, rx) = smol::channel::unbounded::<Signal>();
|
||||||
|
|
||||||
// Automatically Login if only have 1 account
|
async_cx
|
||||||
if let Some(account) = accounts.into_iter().next() {
|
.background_executor()
|
||||||
if let Ok(keys) = get_keys_by_account(account) {
|
.spawn(async move {
|
||||||
get_client().set_signer(keys).await;
|
while let Some(signal) = signal_rx.recv().await {
|
||||||
|
if let Err(e) = tx.send(signal).await {
|
||||||
_ = async_cx.update_global::<AccountRegistry, _>(|state, _| {
|
println!("Send error: {}", e)
|
||||||
state.set_user(Some(account));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
*/
|
|
||||||
|
|
||||||
cx.spawn(|async_cx| async move {
|
|
||||||
let all_messages = SubscriptionId::new(ALL_MESSAGES_SUB_ID);
|
|
||||||
let mut is_initialized = false;
|
|
||||||
|
|
||||||
while let Some(signal) = signal_rx.recv().await {
|
|
||||||
match signal {
|
|
||||||
Signal::RecvEose(id) => {
|
|
||||||
if id == all_messages {
|
|
||||||
if !is_initialized {
|
|
||||||
_ = async_cx.update_global::<ChatRegistry, _>(|state, _| {
|
|
||||||
state.set_init();
|
|
||||||
});
|
|
||||||
|
|
||||||
is_initialized = true;
|
|
||||||
} else {
|
|
||||||
_ = async_cx.update_global::<ChatRegistry, _>(|state, _| {
|
|
||||||
state.set_reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
while let Ok(signal) = rx.recv().await {
|
||||||
|
match signal {
|
||||||
|
Signal::RecvEose(_) => {
|
||||||
|
_ = async_cx.update_global::<ChatRegistry, _>(|state, _| {
|
||||||
|
state.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
Signal::RecvEvent(event) => {
|
Signal::RecvEvent(event) => {
|
||||||
let metadata = async_cx
|
let metadata = async_cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
@@ -277,7 +262,7 @@ async fn main() {
|
|||||||
|
|
||||||
_ = async_cx.update_global::<MetadataRegistry, _>(|state, _cx| {
|
_ = async_cx.update_global::<MetadataRegistry, _>(|state, _cx| {
|
||||||
state.seen(public_key, metadata);
|
state.seen(public_key, metadata);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub struct Room {
|
|||||||
pub last_seen: Timestamp,
|
pub last_seen: Timestamp,
|
||||||
pub title: Option<SharedString>,
|
pub title: Option<SharedString>,
|
||||||
pub metadata: Option<Metadata>,
|
pub metadata: Option<Metadata>,
|
||||||
|
is_initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
@@ -48,6 +49,7 @@ impl Room {
|
|||||||
last_seen,
|
last_seen,
|
||||||
owner,
|
owner,
|
||||||
metadata,
|
metadata,
|
||||||
|
is_initialized: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +73,12 @@ impl ChatRegistry {
|
|||||||
cx.set_global(Self::new());
|
cx.set_global(Self::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_init(&mut self) {
|
pub fn update(&mut self) {
|
||||||
self.is_initialized = true;
|
if !self.is_initialized {
|
||||||
}
|
self.is_initialized = true;
|
||||||
|
} else {
|
||||||
pub fn set_reload(&mut self) {
|
self.reload = true;
|
||||||
self.reload = true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, event: Event, metadata: Option<Metadata>) {
|
pub fn push(&mut self, event: Event, metadata: Option<Metadata>) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use coop_ui::{
|
use gpui::*;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use prelude::FluentBuilder;
|
||||||
|
use ui::{
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
popup_menu::PopupMenuExt,
|
popup_menu::PopupMenuExt,
|
||||||
Icon, IconName, Sizable,
|
Icon, IconName, Sizable,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use prelude::FluentBuilder;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::IMAGE_SERVICE,
|
constants::IMAGE_SERVICE,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use coop_ui::{
|
|
||||||
dock::{DockArea, DockItem, DockPlacement},
|
|
||||||
theme::Theme,
|
|
||||||
Root, TitleBar,
|
|
||||||
};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use prelude::FluentBuilder;
|
use prelude::FluentBuilder;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use ui::{
|
||||||
|
dock::{DockArea, DockItem, DockPlacement},
|
||||||
|
theme::Theme,
|
||||||
|
Root, TitleBar,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
account::Account, chat::ChatPanel, contact::ContactPanel, onboarding::Onboarding,
|
account::Account, chat::ChatPanel, contact::ContactPanel, onboarding::Onboarding,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use coop_ui::{theme::ActiveTheme, StyledExt};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use prelude::FluentBuilder;
|
use prelude::FluentBuilder;
|
||||||
|
use ui::{theme::ActiveTheme, StyledExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::IMAGE_SERVICE,
|
constants::IMAGE_SERVICE,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use coop_ui::{
|
use gpui::*;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use room::RoomPanel;
|
||||||
|
use ui::{
|
||||||
button::Button,
|
button::Button,
|
||||||
dock::{Panel, PanelEvent, PanelState},
|
dock::{Panel, PanelEvent, PanelState},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use room::RoomPanel;
|
|
||||||
|
|
||||||
use crate::states::chat::Room;
|
use crate::states::chat::Room;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use coop_ui::{
|
use gpui::*;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ui::{
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex, Icon, IconName,
|
v_flex, Icon, IconName,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::message::RoomMessage;
|
use super::message::RoomMessage;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -43,7 +43,7 @@ impl RoomPanel {
|
|||||||
let input = cx.new_view(|cx| {
|
let input = cx.new_view(|cx| {
|
||||||
TextInput::new(cx)
|
TextInput::new(cx)
|
||||||
.appearance(false)
|
.appearance(false)
|
||||||
.text_size(coop_ui::Size::Small)
|
.text_size(ui::Size::Small)
|
||||||
.placeholder("Message...")
|
.placeholder("Message...")
|
||||||
.cleanable()
|
.cleanable()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use coop_ui::StyledExt;
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use prelude::FluentBuilder;
|
use prelude::FluentBuilder;
|
||||||
|
use ui::theme::ActiveTheme;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::IMAGE_SERVICE,
|
constants::IMAGE_SERVICE,
|
||||||
@@ -64,7 +64,7 @@ impl ContactListItem {
|
|||||||
impl Render for ContactListItem {
|
impl Render for ContactListItem {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let fallback = show_npub(self.public_key, 16);
|
let fallback = show_npub(self.public_key, 16);
|
||||||
let mut content = div();
|
let mut content = div().h_10().text_sm();
|
||||||
|
|
||||||
if let Some(metadata) = self.metadata.read(cx).as_ref() {
|
if let Some(metadata) = self.metadata.read(cx).as_ref() {
|
||||||
content = content
|
content = content
|
||||||
@@ -73,15 +73,14 @@ impl Render for ContactListItem {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if let Some(picture) = metadata.picture.clone() {
|
if let Some(picture) = metadata.picture.clone() {
|
||||||
this.flex_shrink_0().child(
|
this.child(
|
||||||
img(format!("{}/?url={}&w=72&h=72&n=-1", IMAGE_SERVICE, picture))
|
img(format!("{}/?url={}&w=72&h=72&n=-1", IMAGE_SERVICE, picture))
|
||||||
.size_6()
|
.size_8()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.object_fit(ObjectFit::Cover),
|
.object_fit(ObjectFit::Cover),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.flex_shrink_0()
|
this.child(img("brand/avatar.png").size_8().rounded_full())
|
||||||
.child(img("brand/avatar.png").size_6().rounded_full())
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
@@ -96,20 +95,18 @@ impl Render for ContactListItem {
|
|||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(img("brand/avatar.png").size_8().rounded_full())
|
||||||
img("brand/avatar.png")
|
|
||||||
.flex_shrink_0()
|
|
||||||
.size_6()
|
|
||||||
.rounded_full(),
|
|
||||||
)
|
|
||||||
.child(fallback)
|
.child(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.scrollable(
|
.w_full()
|
||||||
cx.view().entity_id(),
|
.px_2()
|
||||||
coop_ui::scroll::ScrollbarAxis::Vertical,
|
.rounded_md()
|
||||||
)
|
.hover(|this| {
|
||||||
|
this.bg(cx.theme().muted)
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
})
|
||||||
.child(content)
|
.child(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
use gpui::*;
|
|
||||||
use prelude::FluentBuilder;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::item::ContactListItem;
|
|
||||||
use crate::get_client;
|
|
||||||
|
|
||||||
pub struct ContactList {
|
|
||||||
contacts: Model<Option<Vec<View<ContactListItem>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContactList {
|
|
||||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
|
||||||
let contacts = cx.new_model(|_| None);
|
|
||||||
let async_contacts = contacts.clone();
|
|
||||||
|
|
||||||
let mut async_cx = cx.to_async();
|
|
||||||
|
|
||||||
cx.foreground_executor()
|
|
||||||
.spawn({
|
|
||||||
let client = get_client();
|
|
||||||
|
|
||||||
async move {
|
|
||||||
if let Ok(contacts) = async_cx
|
|
||||||
.background_executor()
|
|
||||||
.spawn(async move { client.get_contact_list(Duration::from_secs(3)).await })
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
let views: Vec<View<ContactListItem>> = contacts
|
|
||||||
.into_iter()
|
|
||||||
.map(|contact| {
|
|
||||||
async_cx
|
|
||||||
.new_view(|cx| ContactListItem::new(contact.public_key, cx))
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
_ = async_cx.update_model(&async_contacts, |model, cx| {
|
|
||||||
*model = Some(views);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self { contacts }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for ContactList {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
div().when_some(self.contacts.read(cx).as_ref(), |this, contacts| {
|
|
||||||
this.children(contacts.clone())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
use coop_ui::{
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use gpui::*;
|
||||||
|
use item::ContactListItem;
|
||||||
|
use prelude::FluentBuilder;
|
||||||
|
use ui::{
|
||||||
button::Button,
|
button::Button,
|
||||||
dock::{Panel, PanelEvent, PanelState},
|
dock::{Panel, PanelEvent, PanelState},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
|
scroll::ScrollbarAxis,
|
||||||
|
v_flex, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
use list::ContactList;
|
use crate::get_client;
|
||||||
|
|
||||||
mod item;
|
mod item;
|
||||||
mod list;
|
|
||||||
|
|
||||||
pub struct ContactPanel {
|
pub struct ContactPanel {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
@@ -15,7 +21,8 @@ pub struct ContactPanel {
|
|||||||
zoomable: bool,
|
zoomable: bool,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
// Contacts
|
// Contacts
|
||||||
list: View<ContactList>,
|
view_id: EntityId,
|
||||||
|
contacts: Model<Option<Vec<View<ContactListItem>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactPanel {
|
impl ContactPanel {
|
||||||
@@ -24,14 +31,46 @@ impl ContactPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn view(cx: &mut ViewContext<Self>) -> Self {
|
fn view(cx: &mut ViewContext<Self>) -> Self {
|
||||||
let list = cx.new_view(ContactList::new);
|
let contacts = cx.new_model(|_| None);
|
||||||
|
let async_contacts = contacts.clone();
|
||||||
|
|
||||||
|
let mut async_cx = cx.to_async();
|
||||||
|
|
||||||
|
cx.foreground_executor()
|
||||||
|
.spawn({
|
||||||
|
let client = get_client();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
if let Ok(contacts) = async_cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move { client.get_contact_list(Duration::from_secs(3)).await })
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let views: Vec<View<ContactListItem>> = contacts
|
||||||
|
.into_iter()
|
||||||
|
.map(|contact| {
|
||||||
|
async_cx
|
||||||
|
.new_view(|cx| ContactListItem::new(contact.public_key, cx))
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
_ = async_cx.update_model(&async_contacts, |model, cx| {
|
||||||
|
*model = Some(views);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name: "Contacts".into(),
|
name: "Contacts".into(),
|
||||||
closeable: true,
|
closeable: true,
|
||||||
zoomable: true,
|
zoomable: true,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
list,
|
view_id: cx.entity_id(),
|
||||||
|
contacts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +114,14 @@ impl FocusableView for ContactPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ContactPanel {
|
impl Render for ContactPanel {
|
||||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||||
div().size_full().child(self.list.clone())
|
v_flex()
|
||||||
|
.scrollable(self.view_id, ScrollbarAxis::Vertical)
|
||||||
|
.w_full()
|
||||||
|
.gap_1()
|
||||||
|
.p_2()
|
||||||
|
.when_some(self.contacts.read(cx).as_ref(), |this, contacts| {
|
||||||
|
this.children(contacts.clone())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use coop_ui::{theme::ActiveTheme, StyledExt};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use prelude::FluentBuilder;
|
use prelude::FluentBuilder;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use ui::{theme::ActiveTheme, StyledExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::IMAGE_SERVICE,
|
constants::IMAGE_SERVICE,
|
||||||
@@ -12,23 +12,18 @@ use crate::{
|
|||||||
views::app::{AddPanel, PanelKind},
|
views::app::{AddPanel, PanelKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct InboxItem {
|
pub struct InboxListItem {
|
||||||
id: SharedString,
|
id: SharedString,
|
||||||
event: Event,
|
event: Event,
|
||||||
metadata: Model<Option<Metadata>>,
|
metadata: Model<Option<Metadata>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InboxItem {
|
impl InboxListItem {
|
||||||
pub fn new(event: Event, cx: &mut ViewContext<'_, Self>) -> Self {
|
pub fn new(event: Event, cx: &mut ViewContext<'_, Self>) -> Self {
|
||||||
let pubkeys: Vec<PublicKey> = event.tags.public_keys().copied().collect();
|
let pubkeys: Vec<PublicKey> = event.tags.public_keys().copied().collect();
|
||||||
let id = get_room_id(&event.pubkey, &pubkeys).into();
|
let id = get_room_id(&event.pubkey, &pubkeys).into();
|
||||||
let metadata = cx.new_model(|_| None);
|
let metadata = cx.new_model(|_| None);
|
||||||
|
|
||||||
drop(pubkeys);
|
|
||||||
|
|
||||||
// Request metadata
|
|
||||||
_ = cx.global::<SignalRegistry>().tx.send(event.pubkey);
|
|
||||||
|
|
||||||
// Reload when received metadata
|
// Reload when received metadata
|
||||||
cx.observe_global::<MetadataRegistry>(|chat, cx| {
|
cx.observe_global::<MetadataRegistry>(|chat, cx| {
|
||||||
chat.load_metadata(cx);
|
chat.load_metadata(cx);
|
||||||
@@ -42,6 +37,10 @@ impl InboxItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn request_metadata(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
_ = cx.global::<SignalRegistry>().tx.send(self.event.pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_metadata(&mut self, cx: &mut ViewContext<Self>) {
|
pub fn load_metadata(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let public_key = self.event.pubkey;
|
let public_key = self.event.pubkey;
|
||||||
let async_metadata = self.metadata.clone();
|
let async_metadata = self.metadata.clone();
|
||||||
@@ -74,12 +73,12 @@ impl InboxItem {
|
|||||||
|
|
||||||
cx.dispatch_action(Box::new(AddPanel {
|
cx.dispatch_action(Box::new(AddPanel {
|
||||||
panel: PanelKind::Room(room),
|
panel: PanelKind::Room(room),
|
||||||
position: coop_ui::dock::DockPlacement::Center,
|
position: ui::dock::DockPlacement::Center,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for InboxItem {
|
impl Render for InboxListItem {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let ago = ago(self.event.created_at.as_u64());
|
let ago = ago(self.event.created_at.as_u64());
|
||||||
let fallback_name = show_npub(self.event.pubkey, 16);
|
let fallback_name = show_npub(self.event.pubkey, 16);
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
use coop_ui::{
|
|
||||||
skeleton::Skeleton, theme::ActiveTheme, v_flex, Collapsible, Icon, IconName, StyledExt,
|
|
||||||
};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use item::InboxItem;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use prelude::FluentBuilder;
|
use prelude::FluentBuilder;
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
|
use ui::{skeleton::Skeleton, theme::ActiveTheme, v_flex, Collapsible, Icon, IconName, StyledExt};
|
||||||
|
|
||||||
|
use super::inbox::item::InboxListItem;
|
||||||
use crate::{get_client, states::chat::ChatRegistry, utils::get_room_id};
|
use crate::{get_client, states::chat::ChatRegistry, utils::get_room_id};
|
||||||
|
|
||||||
pub mod item;
|
pub mod item;
|
||||||
|
|
||||||
pub struct Inbox {
|
pub struct Inbox {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
items: Model<Option<Vec<View<InboxItem>>>>,
|
items: Model<Option<Vec<View<InboxListItem>>>>,
|
||||||
is_loading: bool,
|
is_loading: bool,
|
||||||
is_collapsed: bool,
|
is_collapsed: bool,
|
||||||
}
|
}
|
||||||
@@ -56,7 +54,7 @@ impl Inbox {
|
|||||||
// Create view for new chats only
|
// Create view for new chats only
|
||||||
let new = messages
|
let new = messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|m| cx.new_view(|cx| InboxItem::new(m.event, cx)))
|
.map(|m| cx.new_view(|cx| InboxListItem::new(m.event, cx)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
cx.update_model(&this.items, |a, b| {
|
cx.update_model(&this.items, |a, b| {
|
||||||
@@ -70,8 +68,9 @@ impl Inbox {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.observe_new_views::<InboxItem>(|chat, cx| {
|
cx.observe_new_views::<InboxListItem>(|item, cx| {
|
||||||
chat.load_metadata(cx);
|
item.request_metadata(cx);
|
||||||
|
item.load_metadata(cx);
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
@@ -124,17 +123,15 @@ impl Inbox {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let views: Vec<View<InboxItem>> = events
|
let views: Vec<View<InboxListItem>> = events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|ev| {
|
.filter(|ev| {
|
||||||
let keys = ev.tags.public_keys().copied().collect::<Vec<_>>();
|
let keys = ev.tags.public_keys().copied().collect::<Vec<_>>();
|
||||||
let new_id = get_room_id(&ev.pubkey, &keys);
|
let new_id = get_room_id(&ev.pubkey, &keys);
|
||||||
|
|
||||||
drop(keys);
|
|
||||||
|
|
||||||
!current_rooms.iter().any(|id| id == &new_id)
|
!current_rooms.iter().any(|id| id == &new_id)
|
||||||
})
|
})
|
||||||
.map(|ev| async_cx.new_view(|cx| InboxItem::new(ev, cx)).unwrap())
|
.map(|ev| async_cx.new_view(|cx| InboxListItem::new(ev, cx)).unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
async_cx.update_model(&async_items, |model, cx| {
|
async_cx.update_model(&async_items, |model, cx| {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use coop_ui::{
|
|
||||||
input::{InputEvent, TextInput},
|
|
||||||
label::Label,
|
|
||||||
};
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use ui::{
|
||||||
|
input::{InputEvent, TextInput},
|
||||||
|
label::Label,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{constants::KEYRING_SERVICE, get_client, states::account::AccountRegistry};
|
use crate::{constants::KEYRING_SERVICE, get_client, states::account::AccountRegistry};
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ impl Onboarding {
|
|||||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
||||||
let input = cx.new_view(|cx| {
|
let input = cx.new_view(|cx| {
|
||||||
let mut input = TextInput::new(cx);
|
let mut input = TextInput::new(cx);
|
||||||
input.set_size(coop_ui::Size::Medium, cx);
|
input.set_size(ui::Size::Medium, cx);
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use coop_ui::{
|
use gpui::*;
|
||||||
|
use ui::{
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
dock::{Panel, PanelEvent, PanelState},
|
dock::{Panel, PanelEvent, PanelState},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
scroll::ScrollbarAxis,
|
scroll::ScrollbarAxis,
|
||||||
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
|
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
|
|
||||||
use super::inbox::Inbox;
|
use super::inbox::Inbox;
|
||||||
use crate::views::app::{AddPanel, PanelKind};
|
use crate::views::app::{AddPanel, PanelKind};
|
||||||
@@ -93,9 +93,8 @@ impl Render for Sidebar {
|
|||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.not_centered()
|
.not_centered()
|
||||||
.bold()
|
.icon(Icon::new(IconName::ComposeFill))
|
||||||
.icon(Icon::new(IconName::Plus))
|
.label("New Message")
|
||||||
.label("New")
|
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.open_modal(move |modal, _| modal.child("TODO"));
|
cx.open_modal(move |modal, _| modal.child("TODO"));
|
||||||
}),
|
}),
|
||||||
@@ -105,13 +104,12 @@ impl Render for Sidebar {
|
|||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.not_centered()
|
.not_centered()
|
||||||
.bold()
|
.icon(Icon::new(IconName::GroupFill))
|
||||||
.icon(Icon::new(IconName::Group))
|
|
||||||
.label("Contacts")
|
.label("Contacts")
|
||||||
.on_click(|_, cx| {
|
.on_click(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(AddPanel {
|
cx.dispatch_action(Box::new(AddPanel {
|
||||||
panel: PanelKind::Contact,
|
panel: PanelKind::Contact,
|
||||||
position: coop_ui::dock::DockPlacement::Center,
|
position: ui::dock::DockPlacement::Center,
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use coop_ui::{
|
use gpui::*;
|
||||||
|
use ui::{
|
||||||
button::Button,
|
button::Button,
|
||||||
dock::{Panel, PanelEvent, PanelState},
|
dock::{Panel, PanelEvent, PanelState},
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
theme::{ActiveTheme, Colorize},
|
theme::{ActiveTheme, Colorize},
|
||||||
StyledExt,
|
StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
|
||||||
|
|
||||||
pub struct WelcomePanel {
|
pub struct WelcomePanel {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "coop-ui"
|
name = "ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub enum IconName {
|
|||||||
CircleX,
|
CircleX,
|
||||||
Close,
|
Close,
|
||||||
Copy,
|
Copy,
|
||||||
|
ComposeFill,
|
||||||
Dash,
|
Dash,
|
||||||
Delete,
|
Delete,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
@@ -41,6 +42,7 @@ pub enum IconName {
|
|||||||
GitHub,
|
GitHub,
|
||||||
Globe,
|
Globe,
|
||||||
Group,
|
Group,
|
||||||
|
GroupFill,
|
||||||
Heart,
|
Heart,
|
||||||
HeartOff,
|
HeartOff,
|
||||||
Inbox,
|
Inbox,
|
||||||
@@ -111,6 +113,7 @@ impl IconName {
|
|||||||
Self::CircleX => "icons/circle-x.svg",
|
Self::CircleX => "icons/circle-x.svg",
|
||||||
Self::Close => "icons/close.svg",
|
Self::Close => "icons/close.svg",
|
||||||
Self::Copy => "icons/copy.svg",
|
Self::Copy => "icons/copy.svg",
|
||||||
|
Self::ComposeFill => "icons/compose-fill.svg",
|
||||||
Self::Dash => "icons/dash.svg",
|
Self::Dash => "icons/dash.svg",
|
||||||
Self::Delete => "icons/delete.svg",
|
Self::Delete => "icons/delete.svg",
|
||||||
Self::Ellipsis => "icons/ellipsis.svg",
|
Self::Ellipsis => "icons/ellipsis.svg",
|
||||||
@@ -122,6 +125,7 @@ impl IconName {
|
|||||||
Self::GitHub => "icons/github.svg",
|
Self::GitHub => "icons/github.svg",
|
||||||
Self::Globe => "icons/globe.svg",
|
Self::Globe => "icons/globe.svg",
|
||||||
Self::Group => "icons/group.svg",
|
Self::Group => "icons/group.svg",
|
||||||
|
Self::GroupFill => "icons/group-fill.svg",
|
||||||
Self::Heart => "icons/heart.svg",
|
Self::Heart => "icons/heart.svg",
|
||||||
Self::HeartOff => "icons/heart-off.svg",
|
Self::HeartOff => "icons/heart-off.svg",
|
||||||
Self::Inbox => "icons/inbox.svg",
|
Self::Inbox => "icons/inbox.svg",
|
||||||
|
|||||||
Reference in New Issue
Block a user