feat: add support for speech to text #68
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M12.2126 10.7642L7.07919 15.8829C6.51216 16.4483 6.51216 17.365 7.07919 17.9304C7.64621 18.4958 8.56554 18.4958 9.13257 17.9304L19.3995 7.69303C19.9664 7.12769 20.25 6.38673 20.25 5.64575C20.25 4.90465 19.9665 4.16353 19.3995 3.59809C18.8324 3.03271 18.0893 2.75001 17.3461 2.75C16.6029 2.74999 15.8597 3.03269 15.2927 3.59809L5.02581 13.8354C4.17522 14.6836 3.74995 15.7952 3.75 16.9068C3.75005 18.0183 4.17532 19.1298 5.02581 19.9779C5.87635 20.826 6.99113 21.25 8.10591 21.25C9.22067 21.25 10.3354 20.8259 11.1859 19.9779L16.3194 14.8592" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 697 B |
@@ -1,39 +0,0 @@
|
|||||||
use gpui::AssetSource;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn icons_resolve_from_the_vendored_folder() {
|
|
||||||
// Icons coop's code and the kit components draw, served by the vendored
|
|
||||||
// folder (signed's set, filled with the kit bundle where coop needs more).
|
|
||||||
let vendored = [
|
|
||||||
"icons/search.svg",
|
|
||||||
"icons/close.svg",
|
|
||||||
"icons/panel-left.svg",
|
|
||||||
"icons/window-minimize.svg",
|
|
||||||
"icons/settings-2.svg",
|
|
||||||
"icons/x.svg",
|
|
||||||
"icons/compass.svg",
|
|
||||||
"icons/radio-tower.svg",
|
|
||||||
];
|
|
||||||
for path in vendored {
|
|
||||||
let own = assets::Assets
|
|
||||||
.load(path)
|
|
||||||
.unwrap_or_else(|e| panic!("{path} errored in own embed: {e}"));
|
|
||||||
assert!(own.is_some(), "own embed is missing {path}");
|
|
||||||
|
|
||||||
let combined = assets::CombinedAssets.load(path).unwrap();
|
|
||||||
assert!(combined.is_some(), "CombinedAssets fails to serve {path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coop's own files still load through the combined source.
|
|
||||||
let own = [
|
|
||||||
"brand/coop.svg",
|
|
||||||
"brand/bg1.jpg",
|
|
||||||
"fonts/Inter/Inter-Medium.ttf",
|
|
||||||
];
|
|
||||||
for path in own {
|
|
||||||
assert!(
|
|
||||||
matches!(assets::CombinedAssets.load(path), Ok(Some(_))),
|
|
||||||
"CombinedAssets fails to serve coop asset {path}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+26
-14
@@ -1390,21 +1390,26 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reply_id = *id;
|
let reply_id = *id;
|
||||||
let copy_id = *id;
|
|
||||||
let dropdown_id = *id;
|
let dropdown_id = *id;
|
||||||
let public_key = *public_key;
|
let public_key = *public_key;
|
||||||
|
|
||||||
actions
|
let actions = actions.reply(cx.listener(move |this, _event, _window, cx| {
|
||||||
.reply(cx.listener(move |this, _event, _window, cx| {
|
this.reply_to(&reply_id, cx);
|
||||||
this.reply_to(&reply_id, cx);
|
}));
|
||||||
}))
|
|
||||||
.copy(cx.listener(move |this, _event, _window, cx| {
|
let actions = if let Some(message) = self.message(id) {
|
||||||
this.copy_message(©_id, cx);
|
actions.copy(
|
||||||
}))
|
SharedString::from(format!("message-copy-{id}")),
|
||||||
.dropdown(move |menu, _window, _cx| {
|
message.preview(),
|
||||||
menu.menu("Copy author", Box::new(Command::Copy(public_key)))
|
)
|
||||||
.menu("Seen on", Box::new(Command::Trace(dropdown_id)))
|
} else {
|
||||||
})
|
actions
|
||||||
|
};
|
||||||
|
|
||||||
|
actions.dropdown(move |menu, _window, _cx| {
|
||||||
|
menu.menu("Copy author", Box::new(Command::Copy(public_key)))
|
||||||
|
.menu("Seen on", Box::new(Command::Trace(dropdown_id)))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_attachment(&self, url: &Url, cx: &Context<Self>) -> impl IntoElement {
|
fn render_attachment(&self, url: &Url, cx: &Context<Self>) -> impl IntoElement {
|
||||||
@@ -1879,7 +1884,7 @@ impl Render for ChatPanel {
|
|||||||
.items_end()
|
.items_end()
|
||||||
.child(
|
.child(
|
||||||
Button::new("upload")
|
Button::new("upload")
|
||||||
.icon(IconName::Plus)
|
.icon(IconName::Paperclip)
|
||||||
.tooltip("Upload media")
|
.tooltip("Upload media")
|
||||||
.loading(self.uploading)
|
.loading(self.uploading)
|
||||||
.disabled(self.uploading)
|
.disabled(self.uploading)
|
||||||
@@ -1888,7 +1893,14 @@ impl Render for ChatPanel {
|
|||||||
this.upload(window, cx);
|
this.upload(window, cx);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(Textarea::new(&self.input).appearance(false).flex_1())
|
.child(
|
||||||
|
Textarea::new(&self.input)
|
||||||
|
.appearance(false)
|
||||||
|
.small()
|
||||||
|
.min_h_8()
|
||||||
|
.py_1()
|
||||||
|
.flex_1(),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.pl_1()
|
.pl_1()
|
||||||
|
|||||||
@@ -1055,8 +1055,8 @@ impl GroupPanel {
|
|||||||
.items_end()
|
.items_end()
|
||||||
.child(
|
.child(
|
||||||
Button::new("upload")
|
Button::new("upload")
|
||||||
.icon(IconName::Upload)
|
.icon(ui::IconName::Paperclip)
|
||||||
.tooltip("Upload file")
|
.tooltip("Upload media")
|
||||||
.loading(self.uploading)
|
.loading(self.uploading)
|
||||||
.disabled(self.uploading)
|
.disabled(self.uploading)
|
||||||
.ghost()
|
.ghost()
|
||||||
@@ -1064,12 +1064,21 @@ impl GroupPanel {
|
|||||||
this.upload(window, cx);
|
this.upload(window, cx);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(Textarea::new(&self.input).appearance(false).flex_1())
|
.child(
|
||||||
|
Textarea::new(&self.input)
|
||||||
|
.appearance(false)
|
||||||
|
.bg(cx.theme().input)
|
||||||
|
.small()
|
||||||
|
.min_h_8()
|
||||||
|
.py_1()
|
||||||
|
.flex_1(),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("send")
|
Button::new("send")
|
||||||
.icon(ui::IconName::PaperPlaneFill)
|
.icon(ui::IconName::PaperPlaneFill)
|
||||||
.tooltip("Send")
|
.tooltip("Send")
|
||||||
.ghost()
|
.ghost()
|
||||||
|
.disabled(self.uploading)
|
||||||
.on_click(cx.listener(|this, _event, window, cx| {
|
.on_click(cx.listener(|this, _event, window, cx| {
|
||||||
this.send(window, cx);
|
this.send(window, cx);
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub enum IconName {
|
|||||||
Link,
|
Link,
|
||||||
Lock,
|
Lock,
|
||||||
Message,
|
Message,
|
||||||
|
Paperclip,
|
||||||
PaperPlaneFill,
|
PaperPlaneFill,
|
||||||
Plus,
|
Plus,
|
||||||
PlusChat,
|
PlusChat,
|
||||||
@@ -51,6 +52,7 @@ impl IconNamed for IconName {
|
|||||||
Self::Link => "icons/link.svg",
|
Self::Link => "icons/link.svg",
|
||||||
Self::Lock => "icons/lock.svg",
|
Self::Lock => "icons/lock.svg",
|
||||||
Self::Message => "icons/message.svg",
|
Self::Message => "icons/message.svg",
|
||||||
|
Self::Paperclip => "icons/paperclip.svg",
|
||||||
Self::PaperPlaneFill => "icons/paper-plane-fill.svg",
|
Self::PaperPlaneFill => "icons/paper-plane-fill.svg",
|
||||||
Self::Plus => "icons/plus.svg",
|
Self::Plus => "icons/plus.svg",
|
||||||
Self::PlusChat => "icons/plus-chat.svg",
|
Self::PlusChat => "icons/plus-chat.svg",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
use gpui_kit::assets::IconName;
|
use gpui_kit::assets::IconName;
|
||||||
use gpui_kit::component::button::{Button, ButtonVariants as _};
|
use gpui_kit::component::button::{Button, ButtonVariants as _};
|
||||||
|
use gpui_kit::component::clipboard::Clipboard;
|
||||||
use gpui_kit::component::menu::{DropdownMenu as _, PopupMenu, PopupMenuItem};
|
use gpui_kit::component::menu::{DropdownMenu as _, PopupMenu, PopupMenuItem};
|
||||||
use gpui_kit::component::{
|
use gpui_kit::component::{
|
||||||
ActiveTheme, InteractiveElementExt as _, Sizable as _, StyledExt as _, h_flex, v_flex,
|
ActiveTheme, InteractiveElementExt as _, Sizable as _, StyledExt as _, h_flex, v_flex,
|
||||||
@@ -602,7 +603,7 @@ pub struct MessageActions {
|
|||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
reactions: SmallVec<[(&'static str, SharedClickListener); 4]>,
|
reactions: SmallVec<[(&'static str, SharedClickListener); 4]>,
|
||||||
reply: Option<SharedClickListener>,
|
reply: Option<SharedClickListener>,
|
||||||
copy: Option<SharedClickListener>,
|
copy: Option<(ElementId, SharedString)>,
|
||||||
dropdown: Option<DropdownBuilder>,
|
dropdown: Option<DropdownBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,9 +635,10 @@ impl MessageActions {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the copy button.
|
/// Add the copy button for the given value.
|
||||||
pub fn copy(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
|
/// The id must be unique per bar, the clipboard component keys its copied state on it.
|
||||||
self.copy = Some(Rc::new(handler));
|
pub fn copy(mut self, id: impl Into<ElementId>, value: impl Into<SharedString>) -> Self {
|
||||||
|
self.copy = Some((id.into(), value.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,15 +709,8 @@ impl RenderOnce for MessageActions {
|
|||||||
.on_click(move |event, window, cx| reply(event, window, cx)),
|
.on_click(move |event, window, cx| reply(event, window, cx)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when_some(copy, |this, copy| {
|
.when_some(copy, |this, (id, value)| {
|
||||||
this.child(
|
this.child(Clipboard::new(id).value(value).tooltip("Copy").small())
|
||||||
Button::new("copy")
|
|
||||||
.icon(IconName::Copy)
|
|
||||||
.tooltip("Copy")
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.on_click(move |event, window, cx| copy(event, window, cx)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.when_some(dropdown, |this, dropdown| {
|
.when_some(dropdown, |this, dropdown| {
|
||||||
this.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
|
this.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use gpui::{
|
|||||||
Window, div, px, uniform_list,
|
Window, div, px, uniform_list,
|
||||||
};
|
};
|
||||||
use gpui_kit::assets::IconName;
|
use gpui_kit::assets::IconName;
|
||||||
use gpui_kit::base::StyledExt as _;
|
|
||||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||||
use gpui_kit::component::dock::{DockArea, DockPlacement, PanelHandle};
|
use gpui_kit::component::dock::{DockArea, DockPlacement, PanelHandle};
|
||||||
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
use gpui_kit::component::input::{Input, InputEvent, InputState};
|
||||||
@@ -205,6 +204,7 @@ impl NewChat {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,15 +369,13 @@ impl NewChat {
|
|||||||
.p_0p5()
|
.p_0p5()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.bg(cx.theme().secondary)
|
.bg(cx.theme().secondary)
|
||||||
.text_xs()
|
|
||||||
.font_medium()
|
|
||||||
.hover(|this| this.bg(cx.theme().primary))
|
.hover(|this| this.bg(cx.theme().primary))
|
||||||
.child(
|
.child(
|
||||||
Avatar::new(profile.avatar())
|
Avatar::new(profile.avatar())
|
||||||
.seed(profile.avatar_seed())
|
.seed(profile.avatar_seed())
|
||||||
.xsmall(),
|
.xsmall(),
|
||||||
)
|
)
|
||||||
.child(div().max_w_32().truncate().child(profile.name()))
|
.child(div().max_w_32().truncate().text_xs().child(profile.name()))
|
||||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||||
this.toggle_member(&public_key, cx);
|
this.toggle_member(&public_key, cx);
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use anyhow::Error;
|
|||||||
use chat::{ChatRegistry, Room, RoomKind};
|
use chat::{ChatRegistry, Room, RoomKind};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, ClipboardItem, Context, Div, Entity, IntoElement, ParentElement, Render,
|
App, AppContext, Context, Div, Entity, IntoElement, ParentElement, Render, SharedString,
|
||||||
SharedString, Styled, Task, Window, div, retain_all,
|
Styled, Task, Window, div, retain_all,
|
||||||
};
|
};
|
||||||
use gpui_kit::assets::IconName;
|
use gpui_kit::assets::IconName;
|
||||||
use gpui_kit::base::StyledExt as _;
|
use gpui_kit::base::StyledExt as _;
|
||||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
use gpui_kit::component::button::{Button, ButtonVariants};
|
||||||
|
use gpui_kit::component::clipboard::Clipboard;
|
||||||
use gpui_kit::component::dock::DockPlacement;
|
use gpui_kit::component::dock::DockPlacement;
|
||||||
use gpui_kit::component::input::{Input, InputState, Textarea, TextareaState};
|
use gpui_kit::component::input::{Input, InputState, Textarea, TextareaState};
|
||||||
use gpui_kit::component::notification::Notification;
|
use gpui_kit::component::notification::Notification;
|
||||||
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
use gpui_kit::component::{ActiveTheme, Disableable, Sizable, WindowExt, h_flex, v_flex};
|
||||||
use instant::Duration;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
@@ -49,8 +49,6 @@ struct ProfileDialog {
|
|||||||
website_input: Entity<InputState>,
|
website_input: Entity<InputState>,
|
||||||
/// Uploading state
|
/// Uploading state
|
||||||
uploading: bool,
|
uploading: bool,
|
||||||
/// Copied states
|
|
||||||
copied: bool,
|
|
||||||
/// Updating state
|
/// Updating state
|
||||||
updating: bool,
|
updating: bool,
|
||||||
/// Tasks
|
/// Tasks
|
||||||
@@ -92,7 +90,6 @@ impl ProfileDialog {
|
|||||||
bio_input,
|
bio_input,
|
||||||
website_input,
|
website_input,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
copied: false,
|
|
||||||
updating: false,
|
updating: false,
|
||||||
tasks: vec![],
|
tasks: vec![],
|
||||||
}
|
}
|
||||||
@@ -141,35 +138,6 @@ impl ProfileDialog {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy(&mut self, value: String, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let item = ClipboardItem::new_string(value);
|
|
||||||
cx.write_to_clipboard(item);
|
|
||||||
|
|
||||||
self.set_copied(true, window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_copied(&mut self, status: bool, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
self.copied = status;
|
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
if status {
|
|
||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
|
||||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
// Reset the copied state after a delay
|
|
||||||
cx.update(|window, cx| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_copied(false, window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_uploading(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_uploading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
self.uploading = status;
|
self.uploading = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -368,7 +336,8 @@ impl Render for ProfileDialog {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.bg(cx.theme().sidebar)
|
.rounded(cx.theme().radius_lg)
|
||||||
|
.bg(cx.theme().muted)
|
||||||
.child(Avatar::new(avatar).seed(profile.avatar_seed()).large())
|
.child(Avatar::new(avatar).seed(profile.avatar_seed()).large())
|
||||||
.when(self.editing, |this| {
|
.when(self.editing, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
@@ -393,25 +362,13 @@ impl Render for ProfileDialog {
|
|||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
.bg(cx.theme().sidebar)
|
.bg(cx.theme().muted)
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(shorten_pkey)
|
.child(shorten_pkey)
|
||||||
.child(
|
.child(Clipboard::new("copy").value_fn({
|
||||||
Button::new("copy")
|
let public_key = self.public_key;
|
||||||
.icon({
|
move |_, _| public_key.to_bech32().unwrap_or_default().into()
|
||||||
if self.copied {
|
})),
|
||||||
IconName::CircleCheck
|
|
||||||
} else {
|
|
||||||
IconName::Copy
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.xsmall()
|
|
||||||
.secondary()
|
|
||||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
|
||||||
let pk = this.public_key.to_bech32().unwrap();
|
|
||||||
this.copy(pk, window, cx)
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when(self.editing, |this| {
|
.when(self.editing, |this| {
|
||||||
@@ -544,7 +501,7 @@ fn field(
|
|||||||
};
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_1p5()
|
.gap_1()
|
||||||
.child(label(text, cx))
|
.child(label(text, cx))
|
||||||
.child(div().text_xs().text_color(color).child(content))
|
.child(div().text_sm().text_color(color).child(content))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ impl RelayManager {
|
|||||||
|
|
||||||
fn render_tab_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_tab_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
TabBar::new("relays-tabs")
|
TabBar::new("relays-tabs")
|
||||||
.segmented()
|
.pill()
|
||||||
.selected_index(self.active_tab)
|
.selected_index(self.active_tab)
|
||||||
.on_click(cx.listener(|this, ix: &usize, _window, cx| {
|
.on_click(cx.listener(|this, ix: &usize, _window, cx| {
|
||||||
this.active_tab = *ix;
|
this.active_tab = *ix;
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||||
IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, div,
|
ParentElement, Render, SharedString, Styled, Task, Window, div,
|
||||||
};
|
};
|
||||||
use gpui_kit::assets::IconName;
|
|
||||||
use gpui_kit::base::StyledExt as _;
|
use gpui_kit::base::StyledExt as _;
|
||||||
use gpui_kit::component::button::{Button, ButtonVariants};
|
use gpui_kit::component::clipboard::Clipboard;
|
||||||
use gpui_kit::component::dock::{Panel, PanelEvent};
|
use gpui_kit::component::dock::{Panel, PanelEvent};
|
||||||
use gpui_kit::component::input::{Input, InputState};
|
use gpui_kit::component::input::{Input, InputState};
|
||||||
use gpui_kit::component::separator::Separator;
|
use gpui_kit::component::separator::Separator;
|
||||||
use gpui_kit::component::{ActiveTheme, Sizable, v_flex};
|
use gpui_kit::component::{ActiveTheme, v_flex};
|
||||||
use instant::Duration;
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use state::USER_KEYRING;
|
use state::USER_KEYRING;
|
||||||
|
|
||||||
@@ -32,9 +30,6 @@ pub struct BackupPanel {
|
|||||||
/// Secret key input
|
/// Secret key input
|
||||||
nsec_input: Entity<InputState>,
|
nsec_input: Entity<InputState>,
|
||||||
|
|
||||||
/// Copied status
|
|
||||||
copied: bool,
|
|
||||||
|
|
||||||
/// Background tasks
|
/// Background tasks
|
||||||
tasks: Vec<Task<Result<(), Error>>>,
|
tasks: Vec<Task<Result<(), Error>>>,
|
||||||
}
|
}
|
||||||
@@ -54,7 +49,6 @@ impl BackupPanel {
|
|||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
npub_input,
|
npub_input,
|
||||||
nsec_input,
|
nsec_input,
|
||||||
copied: false,
|
|
||||||
tasks: vec![],
|
tasks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,31 +75,6 @@ impl BackupPanel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_secret(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let value = self.nsec_input.read(cx).value();
|
|
||||||
let item = ClipboardItem::new_string(value.to_string());
|
|
||||||
cx.write_to_clipboard(item);
|
|
||||||
|
|
||||||
// Set the copied status to true
|
|
||||||
self.set_copied(true, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_copied(&mut self, status: bool, cx: &mut Context<Self>) {
|
|
||||||
self.copied = status;
|
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
|
||||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
// Clear the error message after a delay
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_copied(false, cx);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl gpui_kit::component::dock::BasePanel for BackupPanel {
|
impl gpui_kit::component::dock::BasePanel for BackupPanel {
|
||||||
@@ -174,20 +143,12 @@ impl Render for BackupPanel {
|
|||||||
.child(Input::new(&self.nsec_input).disabled(true)),
|
.child(Input::new(&self.nsec_input).disabled(true)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("copy")
|
Clipboard::new("copy-secret")
|
||||||
.icon(IconName::Copy)
|
.accessibility_label("Copy secret key")
|
||||||
.label({
|
.value_fn({
|
||||||
if self.copied {
|
let nsec_input = self.nsec_input.clone();
|
||||||
"Copied"
|
move |_, cx| nsec_input.read(cx).value()
|
||||||
} else {
|
}),
|
||||||
"Copy secret key"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.primary()
|
|
||||||
.small()
|
|
||||||
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
|
||||||
this.copy_secret(cx);
|
|
||||||
})),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ impl Sidebar {
|
|||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.text_xs()
|
||||||
.text_color(cx.theme().sidebar_foreground)
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
.child(label),
|
.child(label),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user