feat: screening (#96)

* .

* .

* refactor

* .

* screening

* add report user function

* add danger and warning styles

* update deps

* update

* fix line height

* .
This commit is contained in:
reya
2025-07-23 12:45:01 +07:00
committed by GitHub
parent 00b40db82c
commit a631dd90d2
24 changed files with 3706 additions and 3252 deletions

View File

@@ -41,8 +41,17 @@ impl DisplayProfile for Profile {
}
}
let Ok(pubkey) = self.public_key().to_bech32();
format!("{}:{}", &pubkey[0..4], &pubkey[pubkey.len() - 4..]).into()
shorten_pubkey(self.public_key(), 4)
}
}
pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> SharedString {
let Ok(pubkey) = public_key.to_bech32();
format!(
"{}:{}",
&pubkey[0..(len + 1)],
&pubkey[pubkey.len() - len..]
)
.into()
}

View File

@@ -25,6 +25,7 @@ use ui::modal::ModalButtonProps;
use ui::{ContextModal, IconName, Root, Sizable, StyledExt, TitleBar};
use crate::views::chat::{self, Chat};
use crate::views::screening::Screening;
use crate::views::user_profile::UserProfile;
use crate::views::{
login, new_account, onboarding, preferences, sidebar, startup, user_profile, welcome,
@@ -73,7 +74,7 @@ pub struct ChatSpace {
dock: Entity<DockArea>,
toolbar: bool,
#[allow(unused)]
subscriptions: SmallVec<[Subscription; 5]>,
subscriptions: SmallVec<[Subscription; 6]>,
}
impl ChatSpace {
@@ -172,13 +173,20 @@ impl ChatSpace {
}
}));
// Automatically run on_load function from UserProfile
// Automatically run on_load function when UserProfile is created
subscriptions.push(cx.observe_new::<UserProfile>(|this, window, cx| {
if let Some(window) = window {
this.on_load(window, cx);
}
}));
// Automatically run on_load function when Screening is created
subscriptions.push(cx.observe_new::<Screening>(|this, window, cx| {
if let Some(window) = window {
this.on_load(window, cx);
}
}));
// Subscribe to open chat room requests
subscriptions.push(cx.subscribe_in(
&registry,

View File

@@ -405,10 +405,11 @@ async fn handle_nostr_notifications(
.kind(Kind::InboxRelays)
.limit(1);
if client.subscribe(filter, Some(opts)).await.is_ok() {
if let Ok(output) = client.subscribe(filter, Some(opts)).await {
log::info!(
"Subscribed to get DM relays: {}",
event.pubkey.to_bech32().unwrap()
"Subscribed to get DM relays: {} - Relays: {:?}",
event.pubkey.to_bech32().unwrap(),
output.success
)
}
}

View File

@@ -6,6 +6,7 @@ pub mod new_account;
pub mod onboarding;
pub mod preferences;
pub mod relays;
pub mod screening;
pub mod sidebar;
pub mod startup;
pub mod user_profile;

View File

@@ -51,7 +51,7 @@ impl Preferences {
fn open_edit_profile(&self, window: &mut Window, cx: &mut Context<Self>) {
let edit_profile = edit_profile::init(window, cx);
let title = SharedString::new(t!("preferences.modal_profile_title"));
let title = SharedString::new(t!("profile.title"));
window.open_modal(cx, move |modal, _window, _cx| {
modal

View File

@@ -0,0 +1,288 @@
use common::display::{shorten_pubkey, DisplayProfile};
use common::nip05::nip05_verify;
use global::nostr_client;
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, rems, App, AppContext, Context, Entity, IntoElement, ParentElement, Render,
SharedString, Styled, Task, Window,
};
use gpui_tokio::Tokio;
use i18n::t;
use identity::Identity;
use nostr_sdk::prelude::*;
use registry::Registry;
use settings::AppSettings;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
Screening::new(public_key, window, cx)
}
pub struct Screening {
public_key: PublicKey,
followed: bool,
connections: usize,
verified: bool,
}
impl Screening {
pub fn new(public_key: PublicKey, _window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|_| Self {
public_key,
followed: false,
connections: 0,
verified: false,
})
}
pub fn on_load(&mut self, window: &mut Window, cx: &mut Context<Self>) {
// Skip if user isn't logged in
let Some(identity) = Identity::read_global(cx).public_key() else {
return;
};
let public_key = self.public_key;
let check_trust_score: Task<(bool, usize)> = cx.background_spawn(async move {
let client = nostr_client();
let follow = Filter::new()
.kind(Kind::ContactList)
.author(identity)
.pubkey(public_key)
.limit(1);
let connection = Filter::new()
.kind(Kind::ContactList)
.pubkey(public_key)
.limit(1);
let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1;
let connects = client.database().count(connection).await.unwrap_or(0);
(is_follow, connects)
});
let verify_nip05 = if let Some(address) = self.address(cx) {
Some(Tokio::spawn(cx, async move {
nip05_verify(public_key, &address).await.unwrap_or(false)
}))
} else {
None
};
cx.spawn_in(window, async move |this, cx| {
let (followed, connections) = check_trust_score.await;
this.update(cx, |this, cx| {
this.followed = followed;
this.connections = connections;
cx.notify();
})
.ok();
// Update the NIP05 verification status if user has NIP05 address
if let Some(task) = verify_nip05 {
if let Ok(verified) = task.await {
this.update(cx, |this, cx| {
this.verified = verified;
cx.notify();
})
.ok();
}
}
})
.detach();
}
fn profile(&self, cx: &Context<Self>) -> Profile {
let registry = Registry::read_global(cx);
registry.get_person(&self.public_key, cx)
}
fn address(&self, cx: &Context<Self>) -> Option<String> {
self.profile(cx).metadata().nip05
}
fn open_njump(&mut self, _window: &mut Window, cx: &mut App) {
let Ok(bech32) = self.public_key.to_bech32();
cx.open_url(&format!("https://njump.me/{bech32}"));
}
fn report(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let public_key = self.public_key;
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = nostr_client();
let builder = EventBuilder::report(
vec![Tag::public_key_report(public_key, Report::Impersonation)],
"scam/impersonation",
);
let _ = client.send_event_builder(builder).await?;
Ok(())
});
cx.spawn_in(window, async move |_, cx| {
if task.await.is_ok() {
cx.update(|window, cx| {
window.close_modal(cx);
window.push_notification(t!("screening.report_msg"), cx);
})
.ok();
}
})
.detach();
}
}
impl Render for Screening {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let proxy = AppSettings::get_global(cx).settings.proxy_user_avatars;
let profile = self.profile(cx);
let shorten_pubkey = shorten_pubkey(profile.public_key(), 8);
v_flex()
.w_full()
.px_4()
.pt_8()
.pb_4()
.gap_4()
.child(
v_flex()
.gap_3()
.items_center()
.justify_center()
.text_center()
.child(Avatar::new(profile.avatar_url(proxy)).size(rems(4.)))
.child(
div()
.font_semibold()
.line_height(relative(1.25))
.child(profile.display_name()),
),
)
.child(
h_flex()
.gap_1()
.child(
div()
.p_1()
.flex_1()
.h_7()
.flex()
.items_center()
.justify_center()
.rounded_full()
.bg(cx.theme().elevated_surface_background)
.text_sm()
.truncate()
.text_ellipsis()
.text_center()
.line_height(relative(1.))
.child(shorten_pubkey),
)
.child(
Button::new("njump")
.label(t!("profile.njump"))
.secondary()
.small()
.rounded(ButtonRounded::Full)
.on_click(cx.listener(move |this, _e, window, cx| {
this.open_njump(window, cx);
})),
)
.child(
Button::new("report")
.tooltip(t!("screening.report"))
.icon(IconName::Info)
.danger_alt()
.small()
.rounded(ButtonRounded::Full)
.on_click(cx.listener(move |this, _e, window, cx| {
this.report(window, cx);
})),
),
)
.child(
v_flex()
.gap_2()
.when_some(self.address(cx), |this, address| {
this.child(h_flex().gap_2().map(|this| {
if self.verified {
this.text_sm()
.child(
Icon::new(IconName::CheckCircleFill)
.small()
.flex_shrink_0()
.text_color(cx.theme().icon_accent),
)
.child(div().flex_1().child(SharedString::new(t!(
"screening.verified",
address = address
))))
} else {
this.text_sm()
.child(
Icon::new(IconName::CheckCircleFill)
.small()
.text_color(cx.theme().icon_muted),
)
.child(div().flex_1().child(SharedString::new(t!(
"screening.not_verified",
address = address
))))
}
}))
})
.child(h_flex().gap_2().map(|this| {
if !self.followed {
this.text_sm()
.child(
Icon::new(IconName::CheckCircleFill)
.small()
.text_color(cx.theme().icon_muted),
)
.child(SharedString::new(t!("screening.not_contact")))
} else {
this.text_sm()
.child(
Icon::new(IconName::CheckCircleFill)
.small()
.text_color(cx.theme().icon_accent),
)
.child(SharedString::new(t!("screening.contact")))
}
}))
.child(
h_flex()
.gap_2()
.text_sm()
.child(
Icon::new(IconName::CheckCircleFill)
.small()
.flex_shrink_0()
.text_color({
if self.connections > 0 {
cx.theme().icon_accent
} else {
cx.theme().icon_muted
}
}),
)
.map(|this| {
if self.connections > 0 {
this.child(SharedString::new(t!(
"screening.total_connections",
u = self.connections
)))
} else {
this.child(SharedString::new(t!("screening.no_connections")))
}
}),
),
)
}
}

View File

@@ -1,122 +0,0 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, rems, App, ClickEvent, Div, InteractiveElement, IntoElement, ParentElement as _,
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
};
use settings::AppSettings;
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::StyledExt;
#[derive(IntoElement)]
pub struct DisplayRoom {
ix: usize,
base: Div,
img: Option<SharedString>,
label: Option<SharedString>,
description: Option<SharedString>,
#[allow(clippy::type_complexity)]
handler: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
}
impl DisplayRoom {
pub fn new(ix: usize) -> Self {
Self {
ix,
base: div().h_9().w_full().px_1p5(),
img: None,
label: None,
description: None,
handler: Rc::new(|_, _, _| {}),
}
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
self.description = Some(description.into());
self
}
pub fn img(mut self, img: impl Into<SharedString>) -> Self {
self.img = Some(img.into());
self
}
pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.handler = Rc::new(handler);
self
}
}
impl RenderOnce for DisplayRoom {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let handler = self.handler.clone();
let hide_avatar = AppSettings::get_global(cx).settings.hide_user_avatars;
self.base
.id(self.ix)
.flex()
.items_center()
.gap_2()
.text_sm()
.rounded(cx.theme().radius)
.when(!hide_avatar, |this| {
this.child(
div()
.flex_shrink_0()
.size_6()
.rounded_full()
.overflow_hidden()
.map(|this| {
if let Some(path) = self.img {
this.child(Avatar::new(path).size(rems(1.5)))
} else {
this.child(
img("brand/avatar.png")
.rounded_full()
.size_6()
.into_any_element(),
)
}
}),
)
})
.child(
div()
.flex_1()
.flex()
.items_center()
.justify_between()
.when_some(self.label, |this, label| {
this.child(
div()
.flex_1()
.line_clamp(1)
.text_ellipsis()
.font_medium()
.child(label),
)
})
.when_some(self.description, |this, description| {
this.child(
div()
.flex_shrink_0()
.text_xs()
.text_color(cx.theme().text_placeholder)
.child(description),
)
}),
)
.hover(|this| this.bg(cx.theme().elevated_surface_background))
.on_click(move |ev, window, cx| handler(ev, window, cx))
}
}

View File

@@ -0,0 +1,174 @@
use std::rc::Rc;
use gpui::prelude::FluentBuilder;
use gpui::{
div, img, rems, App, ClickEvent, Div, InteractiveElement, IntoElement,
ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
};
use i18n::t;
use nostr_sdk::prelude::*;
use registry::room::RoomKind;
use settings::AppSettings;
use theme::ActiveTheme;
use ui::actions::OpenProfile;
use ui::avatar::Avatar;
use ui::context_menu::ContextMenuExt;
use ui::modal::ModalButtonProps;
use ui::{h_flex, ContextModal, StyledExt};
use crate::views::screening;
#[derive(IntoElement)]
pub struct RoomListItem {
ix: usize,
base: Div,
public_key: PublicKey,
name: Option<SharedString>,
avatar: Option<SharedString>,
created_at: Option<SharedString>,
kind: Option<RoomKind>,
#[allow(clippy::type_complexity)]
handler: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
}
impl RoomListItem {
pub fn new(ix: usize, public_key: PublicKey) -> Self {
Self {
ix,
public_key,
base: h_flex().h_9().w_full().px_1p5(),
name: None,
avatar: None,
created_at: None,
kind: None,
handler: Rc::new(|_, _, _| {}),
}
}
pub fn name(mut self, name: impl Into<SharedString>) -> Self {
self.name = Some(name.into());
self
}
pub fn created_at(mut self, created_at: impl Into<SharedString>) -> Self {
self.created_at = Some(created_at.into());
self
}
pub fn avatar(mut self, avatar: impl Into<SharedString>) -> Self {
self.avatar = Some(avatar.into());
self
}
pub fn kind(mut self, kind: RoomKind) -> Self {
self.kind = Some(kind);
self
}
pub fn on_click(
mut self,
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.handler = Rc::new(handler);
self
}
}
impl RenderOnce for RoomListItem {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let public_key = self.public_key;
let kind = self.kind;
let handler = self.handler.clone();
let hide_avatar = AppSettings::get_global(cx).settings.hide_user_avatars;
let screening = AppSettings::get_global(cx).settings.screening;
self.base
.id(self.ix)
.gap_2()
.text_sm()
.rounded(cx.theme().radius)
.when(!hide_avatar, |this| {
this.child(
div()
.flex_shrink_0()
.size_6()
.rounded_full()
.overflow_hidden()
.map(|this| {
if let Some(img) = self.avatar {
this.child(Avatar::new(img).size(rems(1.5)))
} else {
this.child(
img("brand/avatar.png")
.rounded_full()
.size_6()
.into_any_element(),
)
}
}),
)
})
.child(
div()
.flex_1()
.flex()
.items_center()
.justify_between()
.when_some(self.name, |this, name| {
this.child(
div()
.flex_1()
.line_clamp(1)
.text_ellipsis()
.truncate()
.font_medium()
.child(name),
)
})
.when_some(self.created_at, |this, ago| {
this.child(
div()
.flex_shrink_0()
.text_xs()
.text_color(cx.theme().text_placeholder)
.child(ago),
)
}),
)
.context_menu(move |this, _window, _cx| {
// TODO: add share chat room
this.menu(t!("profile.view"), Box::new(OpenProfile(public_key)))
})
.hover(|this| this.bg(cx.theme().elevated_surface_background))
.on_click(move |event, window, cx| {
let handler = handler.clone();
if let Some(kind) = kind {
if kind != RoomKind::Ongoing && screening {
let screening = screening::init(public_key, window, cx);
window.open_modal(cx, move |this, _window, _cx| {
let handler_clone = handler.clone();
this.confirm()
.child(screening.clone())
.button_props(
ModalButtonProps::default()
.cancel_text(t!("screening.ignore"))
.ok_text(t!("screening.response")),
)
.on_ok(move |event, window, cx| {
handler_clone(event, window, cx);
// true to close the modal
true
})
});
} else {
handler(event, window, cx)
}
} else {
handler(event, window, cx)
}
})
}
}

View File

@@ -5,8 +5,6 @@ use std::time::Duration;
use anyhow::{anyhow, Error};
use common::debounced_delay::DebouncedDelay;
use common::display::DisplayProfile;
use common::nip05::nip05_verify;
use element::DisplayRoom;
use global::constants::{BOOTSTRAP_RELAYS, DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
use global::nostr_client;
use gpui::prelude::FluentBuilder;
@@ -20,6 +18,7 @@ use gpui_tokio::Tokio;
use i18n::t;
use identity::Identity;
use itertools::Itertools;
use list_item::RoomListItem;
use nostr_sdk::prelude::*;
use registry::room::{Room, RoomKind};
use registry::{Registry, RoomEmitter};
@@ -37,7 +36,7 @@ use ui::{ContextModal, IconName, Selectable, Sizable, StyledExt};
use crate::views::compose;
mod element;
mod list_item;
const FIND_DELAY: u64 = 600;
const FIND_LIMIT: usize = 10;
@@ -58,7 +57,6 @@ pub struct Sidebar {
// Rooms
indicator: Entity<Option<RoomKind>>,
active_filter: Entity<RoomKind>,
trusted_only: bool,
// GPUI
focus_handle: FocusHandle,
image_cache: Entity<RetainAllImageCache>,
@@ -129,7 +127,6 @@ impl Sidebar {
image_cache: RetainAllImageCache::new(cx),
find_debouncer: DebouncedDelay::new(),
finding: false,
trusted_only: false,
cancel_handle,
indicator,
active_filter,
@@ -153,10 +150,16 @@ impl Sidebar {
}
async fn create_temp_room(identity: PublicKey, public_key: PublicKey) -> Result<Room, Error> {
let client = nostr_client();
let keys = Keys::generate();
let builder = EventBuilder::private_msg_rumor(public_key, "");
let event = builder.build(identity).sign(&keys).await?;
let room = Room::new(&event).kind(RoomKind::Ongoing);
// Request to get user's metadata
Self::request_metadata(client, public_key).await?;
// Create a temporary room
let room = Room::new(&event).rearrange_by(identity);
Ok(room)
}
@@ -165,7 +168,6 @@ impl Sidebar {
let client = nostr_client();
let timeout = Duration::from_secs(2);
let mut rooms: BTreeSet<Room> = BTreeSet::new();
let mut processed: BTreeSet<PublicKey> = BTreeSet::new();
let filter = Filter::new()
.kind(Kind::Metadata)
@@ -177,24 +179,13 @@ impl Sidebar {
.await
{
// Process to verify the search results
for event in events.into_iter() {
if processed.contains(&event.pubkey) {
for event in events.into_iter().unique_by(|event| event.pubkey) {
// Skip if author is match current user
if event.pubkey == identity {
continue;
}
processed.insert(event.pubkey);
let metadata = Metadata::from_json(event.content).unwrap_or_default();
// Skip if NIP-05 is not found
let Some(target) = metadata.nip05.as_ref() else {
continue;
};
// Skip if NIP-05 is not valid or failed to verify
if !nip05_verify(event.pubkey, target).await.unwrap_or(false) {
continue;
};
// Return a temporary room
if let Ok(room) = Self::create_temp_room(identity, event.pubkey).await {
rooms.insert(room);
}
@@ -299,14 +290,8 @@ impl Sidebar {
let address = query.to_owned();
let task = Tokio::spawn(cx, async move {
let client = nostr_client();
if let Ok(profile) = common::nip05::nip05_profile(&address).await {
let public_key = profile.public_key;
// Request for user metadata
Self::request_metadata(client, public_key).await.ok();
// Return a temporary room
Self::create_temp_room(identity, public_key).await
Self::create_temp_room(identity, profile.public_key).await
} else {
Err(anyhow!(t!("sidebar.addr_error")))
}
@@ -362,11 +347,6 @@ impl Sidebar {
};
let task: Task<Result<Room, Error>> = cx.background_spawn(async move {
let client = nostr_client();
// Request metadata for this user
Self::request_metadata(client, public_key).await?;
// Create a gift wrap event to represent as room
Self::create_temp_room(identity, public_key).await
});
@@ -544,11 +524,6 @@ impl Sidebar {
});
}
fn set_trusted_only(&mut self, cx: &mut Context<Self>) {
self.trusted_only = !self.trusted_only;
cx.notify();
}
fn open_room(&mut self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
let room = if let Some(room) = Registry::read_global(cx).room(&id, cx) {
room
@@ -695,20 +670,19 @@ impl Sidebar {
for ix in range {
if let Some(room) = rooms.get(ix) {
let this = room.read(cx);
let id = this.id;
let ago = this.ago();
let label = this.display_name(cx);
let img = this.display_image(proxy, cx);
let handler = cx.listener(move |this, _, window, cx| {
this.open_room(id, window, cx);
let handler = cx.listener({
let id = this.id;
move |this, _, window, cx| {
this.open_room(id, window, cx);
}
});
items.push(
DisplayRoom::new(ix)
.img(img)
.label(label)
.description(ago)
RoomListItem::new(ix, this.members[0])
.avatar(this.display_image(proxy, cx))
.name(this.display_name(cx))
.created_at(this.ago())
.kind(this.kind)
.on_click(handler),
)
}
@@ -761,7 +735,7 @@ impl Render for Sidebar {
if self.active_filter.read(cx) == &RoomKind::Ongoing {
registry.ongoing_rooms(cx)
} else {
registry.request_rooms(self.trusted_only, cx)
registry.request_rooms(cx)
}
};
@@ -872,28 +846,10 @@ impl Render for Sidebar {
.rounded(ButtonRounded::Full)
.selected(!self.filter(&RoomKind::Ongoing, cx))
.on_click(cx.listener(|this, _, _, cx| {
this.set_filter(RoomKind::Unknown, cx);
this.set_filter(RoomKind::default(), cx);
})),
),
)
.when(!self.filter(&RoomKind::Ongoing, cx), |this| {
this.child(
Button::new("trusted")
.tooltip(t!("sidebar.trusted_contacts_tooltip"))
.map(|this| {
if self.trusted_only {
this.icon(IconName::FilterFill)
} else {
this.icon(IconName::Filter)
}
})
.small()
.transparent()
.on_click(cx.listener(|this, _, _, cx| {
this.set_trusted_only(cx);
})),
)
}),
),
)
.when(registry.loading, |this| {
this.child(
@@ -918,7 +874,7 @@ impl Render for Sidebar {
)
.when(registry.loading, |this| {
this.child(
div().absolute().bottom_4().px_4().child(
div().absolute().bottom_4().px_4().w_full().child(
div()
.p_1()
.w_full()

View File

@@ -188,10 +188,13 @@ impl Render for UserProfile {
.when(!self.followed, |this| {
this.child(
div()
.flex_none()
.w_32()
.p_1()
.rounded_full()
.bg(cx.theme().surface_background)
.bg(cx.theme().elevated_surface_background)
.text_xs()
.font_semibold()
.child(SharedString::new(t!("profile.unknown"))),
)
}),
@@ -211,7 +214,7 @@ impl Render for UserProfile {
.gap_1()
.child(
div()
.p_1p5()
.p_2()
.h_9()
.rounded_md()
.bg(cx.theme().elevated_surface_background)
@@ -246,15 +249,18 @@ impl Render for UserProfile {
.text_color(cx.theme().text_muted)
.child(SharedString::new(t!("profile.label_bio"))),
)
.when_some(profile.metadata().about, |this, bio| {
this.child(
div()
.p_1p5()
.rounded_md()
.bg(cx.theme().elevated_surface_background)
.child(bio),
)
}),
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().elevated_surface_background)
.child(
profile
.metadata()
.about
.unwrap_or(t!("profile.no_bio").to_string()),
),
),
)
.child(
Button::new("open-njump")

View File

@@ -58,7 +58,7 @@ pub fn nostr_client() -> &'static Client {
.automatic_authentication(true)
// Sleep after idle for 5 seconds
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(5),
timeout: Duration::from_secs(10),
});
ClientBuilder::default().database(lmdb).opts(opts).build()

View File

@@ -188,16 +188,10 @@ impl Registry {
}
/// Get all request rooms.
pub fn request_rooms(&self, trusted_only: bool, cx: &App) -> Vec<Entity<Room>> {
pub fn request_rooms(&self, cx: &App) -> Vec<Entity<Room>> {
self.rooms
.iter()
.filter(|room| {
if trusted_only {
room.read(cx).kind == RoomKind::Trusted
} else {
room.read(cx).kind != RoomKind::Ongoing
}
})
.filter(|room| room.read(cx).kind != RoomKind::Ongoing)
.cloned()
.collect()
}
@@ -274,7 +268,6 @@ impl Registry {
let events = send_events.merge(recv_events);
let mut rooms: BTreeSet<Room> = BTreeSet::new();
let mut trusted_keys: BTreeSet<PublicKey> = BTreeSet::new();
// Process each event and group by room hash
for event in events
@@ -291,20 +284,7 @@ impl Registry {
let mut public_keys = event.tags.public_keys().copied().collect_vec();
public_keys.push(event.pubkey);
let mut is_trust = trusted_keys.contains(&event.pubkey);
if !is_trust {
// Check if room's author is seen in any contact list
let filter = Filter::new().kind(Kind::ContactList).pubkey(event.pubkey);
// If room's author is seen at least once, mark as trusted
is_trust = client.database().count(filter).await.unwrap_or(0) >= 1;
if is_trust {
trusted_keys.insert(event.pubkey);
}
}
// Check if current_user has sent a message to this room at least once
// Check if the current user has sent at least one message to this room
let filter = Filter::new()
.kind(Kind::PrivateDirectMessage)
.author(public_key)
@@ -313,20 +293,13 @@ impl Registry {
// If current user has sent a message at least once, mark as ongoing
let is_ongoing = client.database().count(filter).await.unwrap_or(1) >= 1;
// Create a new room
let room = Room::new(&event).rearrange_by(public_key);
if is_ongoing {
rooms.insert(
Room::new(&event)
.kind(RoomKind::Ongoing)
.rearrange_by(public_key),
);
} else if is_trust {
rooms.insert(
Room::new(&event)
.kind(RoomKind::Trusted)
.rearrange_by(public_key),
);
rooms.insert(room.kind(RoomKind::Ongoing));
} else {
rooms.insert(Room::new(&event).rearrange_by(public_key));
rooms.insert(room);
}
}
@@ -425,7 +398,7 @@ impl Registry {
self.sort(cx);
} else {
let room = Room::new(&event)
.kind(RoomKind::Unknown)
.kind(RoomKind::default())
.rearrange_by(identity);
// Push the new room to the front of the list
@@ -433,7 +406,7 @@ impl Registry {
// Notify the UI about the new room
cx.defer_in(window, move |_this, _window, cx| {
cx.emit(RoomEmitter::Request(RoomKind::Unknown));
cx.emit(RoomEmitter::Request(RoomKind::default()));
});
}
}

View File

@@ -30,9 +30,8 @@ pub struct SendError {
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RoomKind {
Ongoing,
Trusted,
#[default]
Unknown,
Request,
}
#[derive(Debug)]
@@ -104,7 +103,7 @@ impl Room {
subject,
picture,
members,
kind: RoomKind::Unknown,
kind: RoomKind::default(),
}
}

View File

@@ -25,6 +25,7 @@ pub struct Settings {
pub proxy_user_avatars: bool,
pub hide_user_avatars: bool,
pub backup_messages: bool,
pub screening: bool,
pub auto_login: bool,
}
@@ -62,10 +63,11 @@ impl AppSettings {
fn new(cx: &mut Context<Self>) -> Self {
let settings = Settings {
media_server: Url::parse("https://nostrmedia.com").expect("it's fine"),
media_server: Url::parse("https://nostrmedia.com").unwrap(),
proxy_user_avatars: true,
hide_user_avatars: false,
backup_messages: true,
screening: true,
auto_login: false,
};

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,8 @@ use std::ops::{Deref, DerefMut};
use colors::{brand, hsl, neutral};
use gpui::{px, App, Global, Hsla, Pixels, SharedString, Window, WindowAppearance};
use crate::colors::{danger, warning};
mod colors;
mod scale;
@@ -12,154 +14,91 @@ pub fn init(cx: &mut App) {
#[derive(Debug, Clone, Copy, Default)]
pub struct ThemeColor {
/// Border color. Used for most borders, is usually a high contrast color.
pub border: Hsla,
/// Border color. Used for deemphasized borders, like a visual divider between two sections
pub border_variant: Hsla,
/// Border color. Used for focused elements, like keyboard focused list item.
pub border_focused: Hsla,
/// Border color. Used for selected elements, like an active search filter or selected checkbox.
pub border_selected: Hsla,
/// Border color. Used for transparent borders. Used for placeholder borders when an element gains a border on state change.
pub border_transparent: Hsla,
/// Border color. Used for disabled elements, like a disabled input or button.
pub border_disabled: Hsla,
/// Background color. Used for elevated surfaces, like a context menu, popup, or dialog.
pub elevated_surface_background: Hsla,
/// Background color. Used for grounded surfaces like a panel or tab.
pub surface_background: Hsla,
/// Background color. Used for the app background and blank panels or windows.
// Surface colors
pub background: Hsla,
/// Text color. Used for the foreground of an element.
pub element_foreground: Hsla,
/// Background color. Used for the background of an element that should have a different background than the surface it's on.
///
/// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
///
/// For an element that should have the same background as the surface it's on, use `ghost_element_background`.
pub element_background: Hsla,
/// Background color. Used for the hover state of an element that should have a different background than the surface it's on.
///
/// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
pub element_hover: Hsla,
/// Background color. Used for the active state of an element that should have a different background than the surface it's on.
///
/// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed.
pub element_active: Hsla,
/// Background color. Used for the selected state of an element that should have a different background than the surface it's on.
///
/// Selected states are triggered by the element being selected (or "activated") by the user.
///
/// This could include a selected checkbox, a toggleable button that is toggled on, etc.
pub element_selected: Hsla,
/// Background Color. Used for the disabled state of a element that should have a different background than the surface it's on.
///
/// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
pub element_disabled: Hsla,
/// Text color. Used for the foreground of an secondary element.
pub secondary_foreground: Hsla,
/// Background color. Used for the background of an secondary element that should have a different background than the surface it's on.
///
/// Secondary elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
///
/// For an element that should have the same background as the surface it's on, use `ghost_element_background`.
pub secondary_background: Hsla,
/// Background color. Used for the hover state of an secondary element that should have a different background than the surface it's on.
///
/// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
pub secondary_hover: Hsla,
/// Background color. Used for the active state of an secondary element that should have a different background than the surface it's on.
///
/// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed.
pub secondary_active: Hsla,
/// Background color. Used for the selected state of an secondary element that should have a different background than the surface it's on.
///
/// Selected states are triggered by the element being selected (or "activated") by the user.
///
/// This could include a selected checkbox, a toggleable button that is toggled on, etc.
pub secondary_selected: Hsla,
/// Background Color. Used for the disabled state of an secondary element that should have a different background than the surface it's on.
///
/// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
pub secondary_disabled: Hsla,
/// Background color. Used for the area that shows where a dragged element will be dropped.
pub drop_target_background: Hsla,
/// Used for the background of a ghost element that should have the same background as the surface it's on.
///
/// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons...
///
/// For an element that should have a different background than the surface it's on, use `element_background`.
pub ghost_element_background: Hsla,
/// Background Color. Used for the hover state of a ghost element that should have the same background as the surface it's on.
///
/// Hover states are triggered by the mouse entering an element, or a finger touching an element on a touch screen.
pub ghost_element_hover: Hsla,
/// Background Color. Used for the active state of a ghost element that should have the same background as the surface it's on.
///
/// Active states are triggered by the mouse button being pressed down on an element, or the Return button or other activator being pressed.
pub ghost_element_active: Hsla,
/// Background Color. Used for the selected state of a ghost element that should have the same background as the surface it's on.
///
/// Selected states are triggered by the element being selected (or "activated") by the user.
///
/// This could include a selected checkbox, a toggleable button that is toggled on, etc.
pub ghost_element_selected: Hsla,
/// Background Color. Used for the disabled state of a ghost element that should have the same background as the surface it's on.
///
/// Disabled states are shown when a user cannot interact with an element, like a disabled button or input.
pub ghost_element_disabled: Hsla,
/// Text color. Default text color used for most text.
pub text: Hsla,
/// Text color. Color of muted or deemphasized text. It is a subdued version of the standard text color.
pub text_muted: Hsla,
/// Text color. Color of the placeholder text typically shown in input fields to guide the user to enter valid data.
pub text_placeholder: Hsla,
/// Text color. Color used for emphasis or highlighting certain text, like an active filter or a matched character in a search.
pub text_accent: Hsla,
/// Fill color. Used for the default fill color of an icon.
pub icon: Hsla,
/// Fill color. Used for the muted or deemphasized fill color of an icon.
///
/// This might be used to show an icon in an inactive pane, or to deemphasize a series of icons to give them less visual weight.
pub icon_muted: Hsla,
/// Fill color. Used for the accent fill color of an icon.
///
/// This might be used to show when a toggleable icon button is selected.
pub icon_accent: Hsla,
/// The color of the scrollbar thumb.
pub scrollbar_thumb_background: Hsla,
/// The color of the scrollbar thumb when hovered over.
pub scrollbar_thumb_hover_background: Hsla,
/// The border color of the scrollbar thumb.
pub scrollbar_thumb_border: Hsla,
/// The background color of the scrollbar track.
pub scrollbar_track_background: Hsla,
/// The border color of the scrollbar track.
pub scrollbar_track_border: Hsla,
/// Background color. Used for the background of a panel
pub surface_background: Hsla,
pub elevated_surface_background: Hsla,
pub panel_background: Hsla,
/// Border color. Used for outline border.
pub ring: Hsla,
/// Background color. Used for inactive tab.
pub tab_inactive_background: Hsla,
/// Background color. Used for hovered tab.
pub tab_hover_background: Hsla,
/// Background color. Used for active tab.
pub tab_active_background: Hsla,
/// Background color. Used for Title Bar.
pub title_bar: Hsla,
/// Border color. Used for Title Bar.
pub title_bar_border: Hsla,
/// Background color. Used for modal's overlay.
pub overlay: Hsla,
/// Background color. Used for cursor.
pub cursor: Hsla,
/// Window border color.
///
/// # Platform specific:
///
/// This is only works on Linux, other platforms we can't change the window border color.
pub title_bar: Hsla,
pub title_bar_border: Hsla,
pub window_border: Hsla,
// Border colors
pub border: Hsla,
pub border_variant: Hsla,
pub border_focused: Hsla,
pub border_selected: Hsla,
pub border_transparent: Hsla,
pub border_disabled: Hsla,
pub ring: Hsla,
// Text colors
pub text: Hsla,
pub text_muted: Hsla,
pub text_placeholder: Hsla,
pub text_accent: Hsla,
// Icon colors
pub icon: Hsla,
pub icon_muted: Hsla,
pub icon_accent: Hsla,
// Element colors
pub element_foreground: Hsla,
pub element_background: Hsla,
pub element_hover: Hsla,
pub element_active: Hsla,
pub element_selected: Hsla,
pub element_disabled: Hsla,
// Secondary element colors
pub secondary_foreground: Hsla,
pub secondary_background: Hsla,
pub secondary_hover: Hsla,
pub secondary_active: Hsla,
pub secondary_selected: Hsla,
pub secondary_disabled: Hsla,
// Danger element colors
pub danger_foreground: Hsla,
pub danger_background: Hsla,
pub danger_hover: Hsla,
pub danger_active: Hsla,
pub danger_selected: Hsla,
pub danger_disabled: Hsla,
// Warning element colors
pub warning_foreground: Hsla,
pub warning_background: Hsla,
pub warning_hover: Hsla,
pub warning_active: Hsla,
pub warning_selected: Hsla,
pub warning_disabled: Hsla,
// Ghost element colors
pub ghost_element_background: Hsla,
pub ghost_element_hover: Hsla,
pub ghost_element_active: Hsla,
pub ghost_element_selected: Hsla,
pub ghost_element_disabled: Hsla,
// Tab colors
pub tab_inactive_background: Hsla,
pub tab_hover_background: Hsla,
pub tab_active_background: Hsla,
// Scrollbar colors
pub scrollbar_thumb_background: Hsla,
pub scrollbar_thumb_hover_background: Hsla,
pub scrollbar_thumb_border: Hsla,
pub scrollbar_track_background: Hsla,
pub scrollbar_track_border: Hsla,
// Interactive colors
pub drop_target_background: Hsla,
pub cursor: Hsla,
pub selection: Hsla,
}
/// The default colors for the theme.
@@ -171,55 +110,79 @@ impl ThemeColor {
/// Themes that do not specify all colors are refined off of these defaults.
pub fn light() -> Self {
Self {
background: neutral().light().step_1(),
surface_background: neutral().light().step_2(),
elevated_surface_background: neutral().light().step_3(),
panel_background: gpui::white(),
overlay: neutral().light_alpha().step_3(),
title_bar: gpui::transparent_black(),
title_bar_border: gpui::transparent_black(),
window_border: hsl(240.0, 5.9, 78.0),
border: neutral().light().step_6(),
border_variant: neutral().light().step_5(),
border_focused: brand().light().step_7(),
border_selected: brand().light().step_7(),
border_transparent: gpui::transparent_black(),
border_disabled: neutral().light().step_3(),
elevated_surface_background: neutral().light().step_3(),
surface_background: neutral().light().step_2(),
background: neutral().light().step_1(),
ring: brand().light().step_8(),
text: neutral().light().step_12(),
text_muted: neutral().light().step_11(),
text_placeholder: neutral().light().step_10(),
text_accent: brand().light().step_11(),
icon: neutral().light().step_11(),
icon_muted: neutral().light().step_10(),
icon_accent: brand().light().step_11(),
element_foreground: brand().light().step_12(),
element_background: brand().light().step_9(),
element_hover: brand().light_alpha().step_10(),
element_active: brand().light().step_10(),
element_selected: brand().light().step_11(),
element_disabled: brand().light_alpha().step_3(),
secondary_foreground: brand().light().step_12(),
secondary_background: brand().light().step_3(),
secondary_hover: brand().light_alpha().step_4(),
secondary_active: brand().light().step_5(),
secondary_selected: brand().light().step_5(),
secondary_disabled: brand().light_alpha().step_3(),
drop_target_background: brand().light_alpha().step_2(),
danger_foreground: danger().light().step_1(),
danger_background: danger().light().step_9(),
danger_hover: danger().light_alpha().step_10(),
danger_active: danger().light().step_10(),
danger_selected: danger().light().step_11(),
danger_disabled: danger().light_alpha().step_3(),
warning_foreground: warning().light().step_12(),
warning_background: warning().light().step_9(),
warning_hover: warning().light_alpha().step_10(),
warning_active: warning().light().step_10(),
warning_selected: warning().light().step_11(),
warning_disabled: warning().light_alpha().step_3(),
ghost_element_background: gpui::transparent_black(),
ghost_element_hover: neutral().light_alpha().step_3(),
ghost_element_active: neutral().light_alpha().step_4(),
ghost_element_active: neutral().light_alpha().step_5(),
ghost_element_selected: neutral().light_alpha().step_5(),
ghost_element_disabled: neutral().light_alpha().step_2(),
text: neutral().light().step_12(),
text_muted: neutral().light().step_11(),
text_placeholder: neutral().light().step_10(),
text_accent: brand().light().step_11(),
icon: neutral().light().step_11(),
icon_muted: neutral().light().step_10(),
icon_accent: brand().light().step_11(),
tab_inactive_background: neutral().light().step_3(),
tab_hover_background: neutral().light().step_4(),
tab_active_background: neutral().light().step_5(),
scrollbar_thumb_background: neutral().light_alpha().step_3(),
scrollbar_thumb_hover_background: neutral().light_alpha().step_4(),
scrollbar_thumb_border: gpui::transparent_black(),
scrollbar_track_background: gpui::transparent_black(),
scrollbar_track_border: neutral().light().step_5(),
panel_background: gpui::white(),
ring: brand().light().step_8(),
tab_active_background: neutral().light().step_5(),
tab_hover_background: neutral().light().step_4(),
tab_inactive_background: neutral().light().step_3(),
title_bar: gpui::transparent_black(),
title_bar_border: gpui::transparent_black(),
overlay: neutral().light_alpha().step_3(),
drop_target_background: brand().light_alpha().step_2(),
cursor: hsl(200., 100., 50.),
window_border: hsl(240.0, 5.9, 78.0),
selection: hsl(200., 100., 50.).alpha(0.25),
}
}
@@ -228,55 +191,79 @@ impl ThemeColor {
/// Themes that do not specify all colors are refined off of these defaults.
pub fn dark() -> Self {
Self {
background: neutral().dark().step_1(),
surface_background: neutral().dark().step_2(),
elevated_surface_background: neutral().dark().step_3(),
panel_background: gpui::black(),
overlay: neutral().dark_alpha().step_3(),
title_bar: gpui::transparent_black(),
title_bar_border: gpui::transparent_black(),
window_border: hsl(240.0, 3.7, 28.0),
border: neutral().dark().step_6(),
border_variant: neutral().dark().step_5(),
border_focused: brand().dark().step_7(),
border_selected: brand().dark().step_7(),
border_transparent: gpui::transparent_black(),
border_disabled: neutral().dark().step_3(),
elevated_surface_background: neutral().dark().step_3(),
surface_background: neutral().dark().step_2(),
background: neutral().dark().step_1(),
ring: brand().dark().step_8(),
text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(),
text_placeholder: neutral().dark().step_10(),
text_accent: brand().dark().step_11(),
icon: neutral().dark().step_11(),
icon_muted: neutral().dark().step_10(),
icon_accent: brand().dark().step_11(),
element_foreground: brand().dark().step_1(),
element_background: brand().dark().step_9(),
element_hover: brand().dark_alpha().step_10(),
element_active: brand().dark().step_10(),
element_selected: brand().dark().step_11(),
element_disabled: brand().dark_alpha().step_3(),
secondary_foreground: brand().dark().step_12(),
secondary_background: brand().dark().step_3(),
secondary_hover: brand().dark_alpha().step_4(),
secondary_active: brand().dark().step_5(),
secondary_selected: brand().dark().step_5(),
secondary_disabled: brand().dark_alpha().step_3(),
drop_target_background: brand().dark_alpha().step_2(),
danger_foreground: danger().dark().step_1(),
danger_background: danger().dark().step_9(),
danger_hover: danger().dark_alpha().step_10(),
danger_active: danger().dark().step_10(),
danger_selected: danger().dark().step_11(),
danger_disabled: danger().dark_alpha().step_3(),
warning_foreground: warning().dark().step_12(),
warning_background: warning().dark().step_9(),
warning_hover: warning().dark_alpha().step_10(),
warning_active: warning().dark().step_10(),
warning_selected: warning().dark().step_11(),
warning_disabled: warning().dark_alpha().step_3(),
ghost_element_background: gpui::transparent_black(),
ghost_element_hover: neutral().dark_alpha().step_3(),
ghost_element_active: neutral().dark_alpha().step_4(),
ghost_element_selected: neutral().dark_alpha().step_5(),
ghost_element_disabled: neutral().dark_alpha().step_2(),
text: neutral().dark().step_12(),
text_muted: neutral().dark().step_11(),
text_placeholder: neutral().dark().step_10(),
text_accent: brand().dark().step_11(),
icon: neutral().dark().step_11(),
icon_muted: neutral().dark().step_10(),
icon_accent: brand().dark().step_11(),
tab_inactive_background: neutral().dark().step_3(),
tab_hover_background: neutral().dark().step_4(),
tab_active_background: neutral().dark().step_5(),
scrollbar_thumb_background: neutral().dark_alpha().step_3(),
scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(),
scrollbar_thumb_border: gpui::transparent_black(),
scrollbar_track_background: gpui::transparent_black(),
scrollbar_track_border: neutral().dark().step_5(),
panel_background: gpui::black(),
ring: brand().dark().step_8(),
tab_active_background: neutral().dark().step_5(),
tab_hover_background: neutral().dark().step_4(),
tab_inactive_background: neutral().dark().step_3(),
title_bar: gpui::transparent_black(),
title_bar_border: gpui::transparent_black(),
overlay: neutral().dark_alpha().step_3(),
drop_target_background: brand().dark_alpha().step_2(),
cursor: hsl(200., 100., 50.),
window_border: hsl(240.0, 3.7, 28.0),
selection: hsl(200., 100., 50.).alpha(0.25),
}
}
}

View File

@@ -36,6 +36,21 @@ pub trait ButtonVariants: Sized {
self.with_variant(ButtonVariant::Secondary)
}
/// With the danger style for the Button.
fn danger(self) -> Self {
self.with_variant(ButtonVariant::Danger)
}
/// With the danger alternate style for the Button.
fn danger_alt(self) -> Self {
self.with_variant(ButtonVariant::DangerAlt)
}
/// With the warning style for the Button.
fn warning(self) -> Self {
self.with_variant(ButtonVariant::Warning)
}
/// With the ghost style for the Button.
fn ghost(self) -> Self {
self.with_variant(ButtonVariant::Ghost)
@@ -88,6 +103,9 @@ impl ButtonCustomVariant {
pub enum ButtonVariant {
Primary,
Secondary,
Danger,
DangerAlt,
Warning,
Ghost,
Transparent,
Custom(ButtonCustomVariant),
@@ -394,11 +412,19 @@ struct ButtonVariantStyle {
}
impl ButtonVariant {
fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = self.bg_color(window, cx);
let fg = self.text_color(window, cx);
ButtonVariantStyle { bg, fg }
}
fn bg_color(&self, _window: &Window, cx: &App) -> Hsla {
match self {
ButtonVariant::Primary => cx.theme().element_background,
ButtonVariant::Secondary => cx.theme().elevated_surface_background,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Danger => cx.theme().danger_background,
ButtonVariant::Warning => cx.theme().warning_background,
ButtonVariant::Custom(colors) => colors.color,
_ => cx.theme().ghost_element_background,
}
@@ -408,23 +434,22 @@ impl ButtonVariant {
match self {
ButtonVariant::Primary => cx.theme().element_foreground,
ButtonVariant::Secondary => cx.theme().text_muted,
ButtonVariant::Danger => cx.theme().danger_foreground,
ButtonVariant::DangerAlt => cx.theme().danger_background,
ButtonVariant::Warning => cx.theme().warning_foreground,
ButtonVariant::Transparent => cx.theme().text_placeholder,
ButtonVariant::Ghost => cx.theme().text_muted,
ButtonVariant::Custom(colors) => colors.foreground,
}
}
fn normal(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = self.bg_color(window, cx);
let fg = self.text_color(window, cx);
ButtonVariantStyle { bg, fg }
}
fn hovered(&self, window: &Window, cx: &App) -> ButtonVariantStyle {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_hover,
ButtonVariant::Secondary => cx.theme().secondary_hover,
ButtonVariant::Danger => cx.theme().danger_hover,
ButtonVariant::DangerAlt => gpui::transparent_black(),
ButtonVariant::Warning => cx.theme().warning_hover,
ButtonVariant::Ghost => cx.theme().ghost_element_hover,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.hover,
@@ -444,6 +469,9 @@ impl ButtonVariant {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_active,
ButtonVariant::Secondary => cx.theme().secondary_active,
ButtonVariant::Danger => cx.theme().danger_active,
ButtonVariant::DangerAlt => gpui::transparent_black(),
ButtonVariant::Warning => cx.theme().warning_active,
ButtonVariant::Ghost => cx.theme().ghost_element_active,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active,
@@ -462,6 +490,9 @@ impl ButtonVariant {
let bg = match self {
ButtonVariant::Primary => cx.theme().element_selected,
ButtonVariant::Secondary => cx.theme().secondary_selected,
ButtonVariant::Danger => cx.theme().danger_selected,
ButtonVariant::DangerAlt => gpui::transparent_black(),
ButtonVariant::Warning => cx.theme().warning_selected,
ButtonVariant::Ghost => cx.theme().ghost_element_selected,
ButtonVariant::Transparent => gpui::transparent_black(),
ButtonVariant::Custom(colors) => colors.active,

View File

@@ -70,6 +70,7 @@ pub enum IconName {
ThumbsUp,
Upload,
UsersThreeFill,
OpenUrl,
WindowClose,
WindowMaximize,
WindowMinimize,
@@ -140,6 +141,7 @@ impl IconName {
Self::ThumbsUp => "icons/thumbs-up.svg",
Self::Upload => "icons/upload.svg",
Self::UsersThreeFill => "icons/users-three-fill.svg",
Self::OpenUrl => "icons/open-url.svg",
Self::WindowClose => "icons/window-close.svg",
Self::WindowMaximize => "icons/window-maximize.svg",
Self::WindowMinimize => "icons/window-minimize.svg",

View File

@@ -700,7 +700,7 @@ impl Element for TextElement {
// Paint selections
if let Some(path) = prepaint.selection_path.take() {
window.paint_path(path, cx.theme().element_disabled);
window.paint_path(path, cx.theme().selection);
}
// Paint text

File diff suppressed because it is too large Load Diff