chore: re-add missing actions #17
@@ -6,22 +6,13 @@ use settings::SignerKind;
|
|||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
#[action(namespace = chat, no_json)]
|
#[action(namespace = chat, no_json)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Insert(&'static str),
|
|
||||||
ChangeSubject(&'static str),
|
ChangeSubject(&'static str),
|
||||||
ChangeSigner(SignerKind),
|
ChangeSigner(SignerKind),
|
||||||
ToggleBackup,
|
ToggleBackup,
|
||||||
|
|
||||||
|
Insert(&'static str),
|
||||||
|
|
||||||
|
Copy(PublicKey),
|
||||||
|
Relays(PublicKey),
|
||||||
|
Njump(PublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
#[action(namespace = chat, no_json)]
|
|
||||||
pub struct SeenOn(pub EventId);
|
|
||||||
|
|
||||||
/// Define a open public key action
|
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize, Debug)]
|
|
||||||
#[action(namespace = pubkey, no_json)]
|
|
||||||
pub struct OpenPublicKey(pub PublicKey);
|
|
||||||
|
|
||||||
/// Define a copy inline public key action
|
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize, Debug)]
|
|
||||||
#[action(namespace = pubkey, no_json)]
|
|
||||||
pub struct CopyPublicKey(pub PublicKey);
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use ui::button::{Button, ButtonVariants};
|
|||||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||||
use ui::indicator::Indicator;
|
use ui::indicator::Indicator;
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
use ui::menu::{ContextMenuExt, DropdownMenu};
|
use ui::menu::DropdownMenu;
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
use ui::scroll::Scrollbar;
|
use ui::scroll::Scrollbar;
|
||||||
use ui::{
|
use ui::{
|
||||||
@@ -505,10 +505,21 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_message(&self, id: &EventId, cx: &Context<Self>) {
|
fn copy_author(&self, public_key: &PublicKey, cx: &App) {
|
||||||
if let Some(message) = self.message(id) {
|
let content = public_key.to_bech32().unwrap();
|
||||||
cx.write_to_clipboard(ClipboardItem::new_string(message.content.to_string()));
|
let item = ClipboardItem::new_string(content);
|
||||||
|
|
||||||
|
cx.write_to_clipboard(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_message(&self, id: &EventId, cx: &App) {
|
||||||
|
let Some(message) = self.message(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let content = message.content.to_string();
|
||||||
|
let item = ClipboardItem::new_string(content);
|
||||||
|
|
||||||
|
cx.write_to_clipboard(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reply_to(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
fn reply_to(&mut self, id: &EventId, cx: &mut Context<Self>) {
|
||||||
@@ -588,7 +599,7 @@ impl ChatPanel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Person {
|
fn profile(&self, public_key: &PublicKey, cx: &App) -> Person {
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
persons.read(cx).get(public_key, cx)
|
persons.read(cx).get(public_key, cx)
|
||||||
}
|
}
|
||||||
@@ -631,7 +642,51 @@ impl ChatPanel {
|
|||||||
window.push_notification(Notification::error("Failed to toggle backup"), cx);
|
window.push_notification(Notification::error("Failed to toggle backup"), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command::Copy(public_key) => {
|
||||||
|
self.copy_author(public_key, cx);
|
||||||
}
|
}
|
||||||
|
Command::Relays(public_key) => {
|
||||||
|
self.open_relays(public_key, window, cx);
|
||||||
|
}
|
||||||
|
Command::Njump(public_key) => {
|
||||||
|
self.open_njump(public_key, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_relays(&mut self, public_key: &PublicKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let profile = self.profile(public_key, cx);
|
||||||
|
|
||||||
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
|
let relays = profile.messaging_relays();
|
||||||
|
|
||||||
|
this.title("Messaging Relays")
|
||||||
|
.show_close(true)
|
||||||
|
.child(v_flex().gap_1().children({
|
||||||
|
let mut items = vec![];
|
||||||
|
|
||||||
|
for url in relays.iter() {
|
||||||
|
items.push(
|
||||||
|
h_flex()
|
||||||
|
.h_7()
|
||||||
|
.px_2()
|
||||||
|
.gap_2()
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.text_sm()
|
||||||
|
.child(div().size_1p5().rounded_full().bg(gpui::green()))
|
||||||
|
.child(SharedString::from(url.to_string())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_njump(&mut self, public_key: &PublicKey, cx: &mut Context<Self>) {
|
||||||
|
let content = format!("https://njump.me/{}", public_key.to_bech32().unwrap());
|
||||||
|
cx.open_url(&content);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
||||||
@@ -758,18 +813,14 @@ impl ChatPanel {
|
|||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.when(!hide_avatar, |this| {
|
.when(!hide_avatar, |this| {
|
||||||
this.child(
|
this.child(Avatar::new(author.avatar()).dropdown_menu(
|
||||||
div()
|
move |this, _window, _cx| {
|
||||||
.id(SharedString::from(format!("{ix}-avatar")))
|
this.menu("Copy Public Key", Box::new(Command::Copy(public_key)))
|
||||||
.child(Avatar::new(author.avatar()))
|
.menu("View Relays", Box::new(Command::Relays(public_key)))
|
||||||
.context_menu(move |this, _window, _cx| {
|
.separator()
|
||||||
let view = Box::new(OpenPublicKey(public_key));
|
.menu("View on njump.me", Box::new(Command::Njump(public_key)))
|
||||||
let copy = Box::new(CopyPublicKey(public_key));
|
},
|
||||||
|
))
|
||||||
this.menu("View Profile", view)
|
|
||||||
.menu("Copy Public Key", copy)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -807,8 +858,17 @@ impl ChatPanel {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(self.render_border(cx))
|
.child(
|
||||||
.child(self.render_actions(&id, cx))
|
div()
|
||||||
|
.group_hover("", |this| this.bg(cx.theme().element_active))
|
||||||
|
.absolute()
|
||||||
|
.left_0()
|
||||||
|
.top_0()
|
||||||
|
.w(px(2.))
|
||||||
|
.h_full()
|
||||||
|
.bg(cx.theme().border_transparent),
|
||||||
|
)
|
||||||
|
.child(self.render_actions(&id, &public_key, cx))
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Middle,
|
MouseButton::Middle,
|
||||||
cx.listener(move |this, _, _window, cx| {
|
cx.listener(move |this, _, _window, cx| {
|
||||||
@@ -911,7 +971,7 @@ impl ChatPanel {
|
|||||||
window.open_modal(cx, move |this, _window, cx| {
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
this.show_close(true)
|
this.show_close(true)
|
||||||
.title(SharedString::from("Sent Reports"))
|
.title(SharedString::from("Sent Reports"))
|
||||||
.child(v_flex().gap_4().pb_4().w_full().children({
|
.child(v_flex().gap_4().w_full().children({
|
||||||
let mut items = Vec::with_capacity(reports.len());
|
let mut items = Vec::with_capacity(reports.len());
|
||||||
|
|
||||||
for report in reports.iter() {
|
for report in reports.iter() {
|
||||||
@@ -1030,18 +1090,12 @@ impl ChatPanel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_border(&self, cx: &Context<Self>) -> impl IntoElement {
|
fn render_actions(
|
||||||
div()
|
&self,
|
||||||
.group_hover("", |this| this.bg(cx.theme().element_active))
|
id: &EventId,
|
||||||
.absolute()
|
public_key: &PublicKey,
|
||||||
.left_0()
|
cx: &Context<Self>,
|
||||||
.top_0()
|
) -> impl IntoElement {
|
||||||
.w(px(2.))
|
|
||||||
.h_full()
|
|
||||||
.bg(cx.theme().border_transparent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_actions(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.p_0p5()
|
.p_0p5()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
@@ -1082,13 +1136,22 @@ impl ChatPanel {
|
|||||||
)
|
)
|
||||||
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
|
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
|
||||||
.child(
|
.child(
|
||||||
Button::new("seen-on")
|
Button::new("advance")
|
||||||
.icon(IconName::Ellipsis)
|
.icon(IconName::Ellipsis)
|
||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
.dropdown_menu({
|
.dropdown_menu({
|
||||||
let id = id.to_owned();
|
let public_key = *public_key;
|
||||||
move |this, _window, _cx| this.menu("Seen on", Box::new(SeenOn(id)))
|
let _id = *id;
|
||||||
|
move |this, _window, _cx| {
|
||||||
|
this.menu("Copy author", Box::new(Command::Copy(public_key)))
|
||||||
|
/*
|
||||||
|
.menu(
|
||||||
|
"Trace",
|
||||||
|
Box::new(Command::Trace(id)),
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.group_hover("", |this| this.visible())
|
.group_hover("", |this| this.visible())
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, px, AbsoluteLength, App, Div, Hsla, ImageSource, Img, InteractiveElement,
|
AbsoluteLength, App, Div, Hsla, ImageSource, Img, InteractiveElement, Interactivity,
|
||||||
Interactivity, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, StyledImage,
|
IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, StyledImage, Window, div, img,
|
||||||
Window,
|
px,
|
||||||
};
|
};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use crate::{Sizable, Size};
|
use crate::{Selectable, Sizable, Size};
|
||||||
|
|
||||||
/// Returns the size of the avatar based on the given [`Size`].
|
/// Returns the size of the avatar based on the given [`Size`].
|
||||||
pub(super) fn avatar_size(size: Size) -> AbsoluteLength {
|
pub(super) fn avatar_size(size: Size) -> AbsoluteLength {
|
||||||
@@ -37,6 +37,7 @@ pub struct Avatar {
|
|||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
size: Size,
|
size: Size,
|
||||||
border_color: Option<Hsla>,
|
border_color: Option<Hsla>,
|
||||||
|
selected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Avatar {
|
impl Avatar {
|
||||||
@@ -48,6 +49,7 @@ impl Avatar {
|
|||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
size: Size::Medium,
|
size: Size::Medium,
|
||||||
border_color: None,
|
border_color: None,
|
||||||
|
selected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +91,17 @@ impl Styled for Avatar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Selectable for Avatar {
|
||||||
|
fn is_selected(&self) -> bool {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InteractiveElement for Avatar {
|
impl InteractiveElement for Avatar {
|
||||||
fn interactivity(&mut self) -> &mut Interactivity {
|
fn interactivity(&mut self) -> &mut Interactivity {
|
||||||
self.base.interactivity()
|
self.base.interactivity()
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ use gpui::{
|
|||||||
RenderOnce, SharedString, StyleRefinement, Styled, Window,
|
RenderOnce, SharedString, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::Selectable;
|
||||||
|
use crate::avatar::Avatar;
|
||||||
use crate::button::Button;
|
use crate::button::Button;
|
||||||
use crate::menu::PopupMenu;
|
use crate::menu::PopupMenu;
|
||||||
use crate::popover::Popover;
|
use crate::popover::Popover;
|
||||||
use crate::Selectable;
|
|
||||||
|
|
||||||
/// A dropdown menu trait for buttons and other interactive elements
|
/// A dropdown menu trait for buttons and other interactive elements
|
||||||
pub trait DropdownMenu: Styled + Selectable + InteractiveElement + IntoElement + 'static {
|
pub trait DropdownMenu: Styled + Selectable + InteractiveElement + IntoElement + 'static {
|
||||||
@@ -35,6 +36,8 @@ pub trait DropdownMenu: Styled + Selectable + InteractiveElement + IntoElement +
|
|||||||
|
|
||||||
impl DropdownMenu for Button {}
|
impl DropdownMenu for Button {}
|
||||||
|
|
||||||
|
impl DropdownMenu for Avatar {}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {
|
pub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, div, hsla, point, px, Animation, AnimationExt as _, AnyElement, App, Bounds,
|
Animation, AnimationExt as _, AnyElement, App, Bounds, BoxShadow, ClickEvent, Div, FocusHandle,
|
||||||
BoxShadow, ClickEvent, Div, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
|
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
||||||
MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, StyleRefinement, Styled,
|
RenderOnce, SharedString, StyleRefinement, Styled, Window, anchored, div, hsla, point, px,
|
||||||
Window,
|
|
||||||
};
|
};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ use crate::actions::{Cancel, Confirm};
|
|||||||
use crate::animation::cubic_bezier;
|
use crate::animation::cubic_bezier;
|
||||||
use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants as _};
|
use crate::button::{Button, ButtonCustomVariant, ButtonVariant, ButtonVariants as _};
|
||||||
use crate::scroll::ScrollableElement;
|
use crate::scroll::ScrollableElement;
|
||||||
use crate::{h_flex, v_flex, IconName, Root, Sizable, StyledExt, WindowExtension};
|
use crate::{IconName, Root, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
const CONTEXT: &str = "Modal";
|
const CONTEXT: &str = "Modal";
|
||||||
|
|
||||||
@@ -500,6 +499,7 @@ impl RenderOnce for Modal {
|
|||||||
.child(self.content),
|
.child(self.content),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.when_none(&self.footer, |this| this.child(div().pt(padding_left)))
|
||||||
.when_some(self.footer, |this, footer| {
|
.when_some(self.footer, |this, footer| {
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|||||||
Reference in New Issue
Block a user