add backup panel
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m47s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m52s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m47s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m52s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="7.75" r="4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.0014 12.25C7.80812 12.25 5.3732 14.9227 4.69664 18.2626C4.47735 19.3452 5.39684 20.25 6.50141 20.25H10.2515" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><circle cx="19.0992" cy="15.4" r="0.9" fill="currentColor"/><path d="M18.8496 12.5498C20.5617 12.5498 21.9502 13.9383 21.9502 15.6504C21.95 17.3623 20.5616 18.75 18.8496 18.75C18.5179 18.75 18.199 18.6961 17.8994 18.5996L15.75 20.75H13.75V18.75L15.8994 16.5996C15.8032 16.3004 15.75 15.9816 15.75 15.6504C15.75 13.9384 17.1377 12.55 18.8496 12.5498Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M12.9996 12.8145C12.675 12.7719 12.3415 12.75 11.9996 12.75C8.55174 12.75 5.94978 14.981 4.9305 18.114C4.56744 19.23 5.50919 20.25 6.68275 20.25H13.9996M15.7496 6.5C15.7496 8.57107 14.0706 10.25 11.9996 10.25C9.92851 10.25 8.24958 8.57107 8.24958 6.5C8.24958 4.42893 9.92851 2.75 11.9996 2.75C14.0706 2.75 15.7496 4.42893 15.7496 6.5ZM15.7496 14C15.7496 12.7574 16.7569 11.75 17.9996 11.75C19.2422 11.75 20.2496 12.7574 20.2496 14C20.2496 14.7801 19.8526 15.4675 19.2496 15.8711V17L18.7496 17.9356L19.2496 18.9678V20L17.9996 21L16.7496 20V15.8711C16.1466 15.4675 15.7496 14.7801 15.7496 14Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 771 B |
194
crates/coop/src/panels/backup.rs
Normal file
194
crates/coop/src/panels/backup.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use gpui::{
|
||||
div, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use state::KEYRING;
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||
use ui::input::{InputState, TextInput};
|
||||
use ui::{divider, v_flex, IconName, Sizable, StyledExt};
|
||||
|
||||
const MSG: &str = "Store your account keys in a safe location. \
|
||||
You can restore your account or move to another client anytime you want.";
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<BackupPanel> {
|
||||
cx.new(|cx| BackupPanel::new(window, cx))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackupPanel {
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
|
||||
/// Public key input
|
||||
npub_input: Entity<InputState>,
|
||||
|
||||
/// Secret key input
|
||||
nsec_input: Entity<InputState>,
|
||||
|
||||
/// Copied status
|
||||
copied: bool,
|
||||
|
||||
/// Background tasks
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
}
|
||||
|
||||
impl BackupPanel {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let npub_input = cx.new(|cx| InputState::new(window, cx).disabled(true));
|
||||
let nsec_input = cx.new(|cx| InputState::new(window, cx).disabled(true));
|
||||
|
||||
// Run at the end of current cycle
|
||||
cx.defer_in(window, |this, window, cx| {
|
||||
this.load(window, cx);
|
||||
});
|
||||
|
||||
Self {
|
||||
name: "Backup".into(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
npub_input,
|
||||
nsec_input,
|
||||
copied: false,
|
||||
tasks: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let keyring = cx.read_credentials(KEYRING);
|
||||
|
||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some((_, secret)) = keyring.await? {
|
||||
let secret = SecretKey::from_slice(&secret)?;
|
||||
let keys = Keys::new(secret);
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.npub_input.update(cx, |this, cx| {
|
||||
this.set_value(keys.public_key().to_bech32().unwrap(), window, cx);
|
||||
});
|
||||
|
||||
this.nsec_input.update(cx, |this, cx| {
|
||||
this.set_value(keys.secret_key().to_bech32().unwrap(), window, cx);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn copy_secret_key(&mut self, cx: &mut Context<Self>) {
|
||||
let value = self.nsec_input.read(cx).value();
|
||||
let item = ClipboardItem::new_string(value.to_string());
|
||||
|
||||
// Copy to clipboard
|
||||
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 Panel for BackupPanel {
|
||||
fn panel_id(&self) -> SharedString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn title(&self, _cx: &App) -> AnyElement {
|
||||
self.name.clone().into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for BackupPanel {}
|
||||
|
||||
impl Focusable for BackupPanel {
|
||||
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BackupPanel {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.p_3()
|
||||
.gap_3()
|
||||
.w_full()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::from(MSG)),
|
||||
)
|
||||
.child(divider(cx))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.text_sm()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::from("Public Key:")),
|
||||
)
|
||||
.child(TextInput::new(&self.npub_input).small().bordered(false)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::from("Secret Key:")),
|
||||
)
|
||||
.child(TextInput::new(&self.nsec_input).small().bordered(false)),
|
||||
)
|
||||
.child(
|
||||
Button::new("copy")
|
||||
.icon(IconName::Copy)
|
||||
.label({
|
||||
if self.copied {
|
||||
"Copied"
|
||||
} else {
|
||||
"Copy secret key"
|
||||
}
|
||||
})
|
||||
.primary()
|
||||
.small()
|
||||
.font_semibold()
|
||||
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||
this.copy_secret_key(cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -144,16 +144,17 @@ impl MessagingRelayPanel {
|
||||
self.error = Some(error.into());
|
||||
cx.notify();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
self.tasks.push(cx.spawn_in(window, 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.error = None;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn set_updating(&mut self, updating: bool, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod backup;
|
||||
pub mod connect;
|
||||
pub mod encryption_key;
|
||||
pub mod greeter;
|
||||
|
||||
@@ -163,16 +163,17 @@ impl RelayListPanel {
|
||||
self.error = Some(error.into());
|
||||
cx.notify();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
self.tasks.push(cx.spawn_in(window, 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.error = None;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
fn set_updating(&mut self, updating: bool, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -20,7 +20,7 @@ use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
||||
use ui::menu::DropdownMenu;
|
||||
use ui::{h_flex, v_flex, IconName, Root, Sizable, WindowExtension};
|
||||
|
||||
use crate::panels::{encryption_key, greeter, messaging_relays, relay_list};
|
||||
use crate::panels::{backup, encryption_key, greeter, messaging_relays, profile, relay_list};
|
||||
use crate::sidebar;
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
|
||||
@@ -30,11 +30,17 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
|
||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[action(namespace = workspace, no_json)]
|
||||
enum Command {
|
||||
ReloadRelayList,
|
||||
OpenRelayPanel,
|
||||
ReloadInbox,
|
||||
OpenInboxPanel,
|
||||
OpenEncryptionPanel,
|
||||
ToggleTheme,
|
||||
|
||||
RefreshRelayList,
|
||||
RefreshMessagingRelays,
|
||||
|
||||
ShowRelayList,
|
||||
ShowMessaging,
|
||||
ShowEncryption,
|
||||
ShowProfile,
|
||||
ShowSettings,
|
||||
ShowBackup,
|
||||
}
|
||||
|
||||
pub struct Workspace {
|
||||
@@ -181,7 +187,32 @@ impl Workspace {
|
||||
|
||||
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match command {
|
||||
Command::OpenEncryptionPanel => {
|
||||
Command::ShowProfile => {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let signer = nostr.read(cx).signer();
|
||||
|
||||
if let Some(public_key) = signer.public_key() {
|
||||
self.dock.update(cx, |this, cx| {
|
||||
this.add_panel(
|
||||
Arc::new(profile::init(public_key, window, cx)),
|
||||
DockPlacement::Right,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Command::ShowBackup => {
|
||||
self.dock.update(cx, |this, cx| {
|
||||
this.add_panel(
|
||||
Arc::new(backup::init(window, cx)),
|
||||
DockPlacement::Right,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
Command::ShowEncryption => {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let signer = nostr.read(cx).signer();
|
||||
|
||||
@@ -196,7 +227,7 @@ impl Workspace {
|
||||
});
|
||||
}
|
||||
}
|
||||
Command::OpenInboxPanel => {
|
||||
Command::ShowMessaging => {
|
||||
self.dock.update(cx, |this, cx| {
|
||||
this.add_panel(
|
||||
Arc::new(messaging_relays::init(window, cx)),
|
||||
@@ -206,7 +237,7 @@ impl Workspace {
|
||||
);
|
||||
});
|
||||
}
|
||||
Command::OpenRelayPanel => {
|
||||
Command::ShowRelayList => {
|
||||
self.dock.update(cx, |this, cx| {
|
||||
this.add_panel(
|
||||
Arc::new(relay_list::init(window, cx)),
|
||||
@@ -216,18 +247,19 @@ impl Workspace {
|
||||
);
|
||||
});
|
||||
}
|
||||
Command::ReloadInbox => {
|
||||
Command::RefreshRelayList => {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
nostr.update(cx, |this, cx| {
|
||||
this.ensure_relay_list(cx);
|
||||
});
|
||||
}
|
||||
Command::ReloadRelayList => {
|
||||
Command::RefreshMessagingRelays => {
|
||||
let chat = ChatRegistry::global(cx);
|
||||
chat.update(cx, |this, cx| {
|
||||
this.ensure_messaging_relays(cx);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +284,29 @@ impl Workspace {
|
||||
.compact()
|
||||
.transparent()
|
||||
.dropdown_menu(move |this, _window, _cx| {
|
||||
this.label(profile.name())
|
||||
this.min_w(px(256.))
|
||||
.label(profile.name())
|
||||
.separator()
|
||||
.menu("Profile", Box::new(ClosePanel))
|
||||
.menu("Backup", Box::new(ClosePanel))
|
||||
.menu("Themes", Box::new(ClosePanel))
|
||||
.menu("Settings", Box::new(ClosePanel))
|
||||
.menu_with_icon(
|
||||
"Profile",
|
||||
IconName::Profile,
|
||||
Box::new(Command::ShowProfile),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Backup",
|
||||
IconName::UserKey,
|
||||
Box::new(Command::ShowBackup),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Themes",
|
||||
IconName::Sun,
|
||||
Box::new(Command::ToggleTheme),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Settings",
|
||||
IconName::Settings,
|
||||
Box::new(Command::ShowSettings),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -298,7 +347,7 @@ impl Workspace {
|
||||
.small()
|
||||
.ghost()
|
||||
.on_click(|_ev, window, cx| {
|
||||
window.dispatch_action(Box::new(Command::OpenEncryptionPanel), cx);
|
||||
window.dispatch_action(Box::new(Command::ShowEncryption), cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -333,7 +382,7 @@ impl Workspace {
|
||||
this.min_w(px(260.))
|
||||
.label("Messaging Relays")
|
||||
.menu_element_with_disabled(
|
||||
Box::new(Command::OpenRelayPanel),
|
||||
Box::new(Command::ShowRelayList),
|
||||
true,
|
||||
move |_window, cx| {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
@@ -380,12 +429,12 @@ impl Workspace {
|
||||
.menu_with_icon(
|
||||
"Reload",
|
||||
IconName::Refresh,
|
||||
Box::new(Command::ReloadInbox),
|
||||
Box::new(Command::RefreshMessagingRelays),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Update relays",
|
||||
IconName::Settings,
|
||||
Box::new(Command::OpenInboxPanel),
|
||||
Box::new(Command::ShowMessaging),
|
||||
)
|
||||
}),
|
||||
),
|
||||
@@ -421,7 +470,7 @@ impl Workspace {
|
||||
this.min_w(px(260.))
|
||||
.label("Relays")
|
||||
.menu_element_with_disabled(
|
||||
Box::new(Command::OpenRelayPanel),
|
||||
Box::new(Command::ShowRelayList),
|
||||
true,
|
||||
move |_window, cx| {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
@@ -465,12 +514,12 @@ impl Workspace {
|
||||
.menu_with_icon(
|
||||
"Reload",
|
||||
IconName::Refresh,
|
||||
Box::new(Command::ReloadRelayList),
|
||||
Box::new(Command::RefreshRelayList),
|
||||
)
|
||||
.menu_with_icon(
|
||||
"Update relay list",
|
||||
IconName::Settings,
|
||||
Box::new(Command::OpenRelayPanel),
|
||||
Box::new(Command::ShowRelayList),
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -25,61 +25,3 @@ pub async fn upload(server: Url, path: PathBuf, cx: &AsyncApp) -> Result<Url, Er
|
||||
.await
|
||||
.map_err(|e| anyhow!("Upload error: {e}"))?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mime_type_detection() {
|
||||
// Test common file extensions
|
||||
assert_eq!(
|
||||
from_path("image.jpg").first_or_octet_stream().to_string(),
|
||||
"image/jpeg"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("document.pdf")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/pdf"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("page.html").first_or_octet_stream().to_string(),
|
||||
"text/html"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("data.json").first_or_octet_stream().to_string(),
|
||||
"application/json"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("script.js").first_or_octet_stream().to_string(),
|
||||
"text/javascript"
|
||||
);
|
||||
assert_eq!(
|
||||
from_path("style.css").first_or_octet_stream().to_string(),
|
||||
"text/css"
|
||||
);
|
||||
|
||||
// Test unknown extension falls back to octet-stream
|
||||
assert_eq!(
|
||||
from_path("unknown.xyz").first_or_octet_stream().to_string(),
|
||||
"chemical/x-xyz"
|
||||
);
|
||||
|
||||
// Test no extension falls back to octet-stream
|
||||
assert_eq!(
|
||||
from_path("file_without_extension")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
|
||||
// Test truly unknown extension
|
||||
assert_eq!(
|
||||
from_path("unknown.unknown123")
|
||||
.first_or_octet_stream()
|
||||
.to_string(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,16 +544,12 @@ impl InputState {
|
||||
/// Set the text of the input field.
|
||||
///
|
||||
/// And the selection_range will be reset to 0..0.
|
||||
pub fn set_value(
|
||||
&mut self,
|
||||
value: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn set_value<T>(&mut self, value: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
self.history.ignore = true;
|
||||
let was_disabled = self.disabled;
|
||||
self.replace_text(value, window, cx);
|
||||
self.disabled = was_disabled;
|
||||
self.history.ignore = false;
|
||||
|
||||
// Ensure cursor to start when set text
|
||||
@@ -565,48 +561,50 @@ impl InputState {
|
||||
|
||||
// Move scroll to top
|
||||
self.scroll_handle.set_offset(point(px(0.), px(0.)));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Insert text at the current cursor position.
|
||||
///
|
||||
/// And the cursor will be moved to the end of inserted text.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn insert<T>(&mut self, text: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
let was_disabled = self.disabled;
|
||||
self.disabled = false;
|
||||
let text: SharedString = text.into();
|
||||
let range_utf16 = self.range_to_utf16(&(self.cursor()..self.cursor()));
|
||||
self.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
|
||||
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||
self.disabled = was_disabled;
|
||||
}
|
||||
|
||||
/// Replace text at the current cursor position.
|
||||
///
|
||||
/// And the cursor will be moved to the end of replaced text.
|
||||
pub fn replace(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn replace<T>(&mut self, text: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
let was_disabled = self.disabled;
|
||||
self.disabled = false;
|
||||
let text: SharedString = text.into();
|
||||
self.replace_text_in_range_silent(None, &text, window, cx);
|
||||
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||
self.disabled = was_disabled;
|
||||
}
|
||||
|
||||
fn replace_text(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn replace_text<T>(&mut self, text: T, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
let was_disabled = self.disabled;
|
||||
self.disabled = false;
|
||||
let text: SharedString = text.into();
|
||||
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
|
||||
self.replace_text_in_range_silent(Some(range), &text, window, cx);
|
||||
self.disabled = was_disabled;
|
||||
}
|
||||
|
||||
/// Set with password masked state.
|
||||
|
||||
@@ -93,8 +93,7 @@ impl RenderOnce for MenuItemElement {
|
||||
.id(self.id)
|
||||
.group(&self.group_name)
|
||||
.gap_x_1()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.p_1()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text)
|
||||
.relative()
|
||||
|
||||
@@ -1073,7 +1073,7 @@ impl PopupMenu {
|
||||
|
||||
let selected = self.selected_index == Some(ix);
|
||||
const EDGE_PADDING: Pixels = px(4.);
|
||||
const INNER_PADDING: Pixels = px(8.);
|
||||
const INNER_PADDING: Pixels = px(4.);
|
||||
|
||||
let is_submenu = matches!(item, PopupMenuItem::Submenu { .. });
|
||||
let group_name = format!("{}:item-{}", cx.entity().entity_id(), ix);
|
||||
@@ -1143,7 +1143,7 @@ impl PopupMenu {
|
||||
.flex_1()
|
||||
.min_h(item_height)
|
||||
.items_center()
|
||||
.gap_x_1()
|
||||
.gap_x_2()
|
||||
.children(Self::render_icon(
|
||||
has_left_icon,
|
||||
is_left_check,
|
||||
|
||||
Reference in New Issue
Block a user