update ui

This commit is contained in:
2026-11-07 10:52:08 +07:00
parent 53aa54a034
commit 0ec8499777
5 changed files with 350 additions and 261 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<path d="M21.25 10.75V17.25C21.25 18.3546 20.3546 19.25 19.25 19.25H4.75C3.64543 19.25 2.75 18.3546 2.75 17.25V10.75M21.25 10.75H18.75M21.25 10.75V6.75C21.25 5.64543 20.3546 4.75 19.25 4.75H4.75C3.64543 4.75 2.75 5.64543 2.75 6.75V10.75M2.75 10.75H5.25M5.25 10.75H7.13177C7.53452 10.75 7.92789 10.8716 8.26039 11.0989L9.43406 11.9011C9.76655 12.1284 10.1599 12.25 10.5627 12.25H13.4373C13.8401 12.25 14.2334 12.1284 14.5659 11.9011L15.7396 11.0989C16.0721 10.8716 16.4655 10.75 16.8682 10.75H18.75M5.25 10.75V7.75C5.25 7.47386 5.47386 7.25 5.75 7.25H18.25C18.5261 7.25 18.75 7.47386 18.75 7.75V10.75" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 748 B

+270 -242
View File
@@ -1,9 +1,12 @@
use ::nip60::{HistoryEntry, MintQuoteEntry, WalletEvent, WalletRegistry}; use std::rc::Rc;
use ::nip60::{HistoryEntry, WalletEvent, WalletRegistry};
use common::TimestampExt; use common::TimestampExt;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, AnyElement, App, AppContext, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable,
ParentElement, Render, SharedString, Styled, Subscription, Window, div, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window, div, px,
uniform_list,
}; };
use gpui_kit::assets::IconName; use gpui_kit::assets::IconName;
use gpui_kit::base::StyledExt as _; use gpui_kit::base::StyledExt as _;
@@ -12,40 +15,27 @@ 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::notification::Notification; use gpui_kit::component::notification::Notification;
use gpui_kit::component::scroll::ScrollableElement as _;
use gpui_kit::component::separator::Separator; use gpui_kit::component::separator::Separator;
use gpui_kit::component::{ActiveTheme, Icon, Sizable, WindowExt, h_flex, v_flex}; use gpui_kit::component::{ActiveTheme, Icon, Sizable, WindowExt, h_flex, v_flex};
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use smallvec::SmallVec; use smallvec::SmallVec;
pub fn init(window: &mut Window, cx: &mut App) -> Entity<WalletPanel> { pub fn init(window: &mut Window, cx: &mut App) -> Entity<WalletPanel> {
cx.new(|cx| WalletPanel::new(window, cx)) cx.new(|cx| WalletPanel::new(window, cx))
} }
type SubmitFn = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool>;
pub struct WalletPanel { pub struct WalletPanel {
name: SharedString, name: SharedString,
focus_handle: FocusHandle, focus_handle: FocusHandle,
/// Mint URL input for the setup flow
mint_input: Entity<InputState>,
/// Amount input for sending a cashu token
send_input: Entity<InputState>,
/// Amount input for the funding invoice
invoice_input: Entity<InputState>,
/// Pasted token input for receiving ecash
token_input: Entity<InputState>,
/// The token produced by the last send, shown for copying /// The token produced by the last send, shown for copying
sent_token: Option<SharedString>, sent_token: Option<SharedString>,
_subscriptions: SmallVec<[Subscription; 1]>, _subscriptions: SmallVec<[Subscription; 1]>,
} }
impl WalletPanel { impl WalletPanel {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let mint_input = cx.new(|cx| InputState::new(window, cx).placeholder("https://mint…"));
let send_input = cx.new(|cx| InputState::new(window, cx).placeholder("Amount (sats)"));
let invoice_input = cx.new(|cx| InputState::new(window, cx).placeholder("Amount (sats)"));
let token_input = cx.new(|cx| InputState::new(window, cx).placeholder("cashu…"));
let wallet = WalletRegistry::global(cx); let wallet = WalletRegistry::global(cx);
let mut subscriptions = SmallVec::new(); let mut subscriptions = SmallVec::new();
subscriptions.push( subscriptions.push(
@@ -68,83 +58,20 @@ impl WalletPanel {
Self { Self {
name: "Wallet".into(), name: "Wallet".into(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
mint_input,
send_input,
invoice_input,
token_input,
sent_token: None, sent_token: None,
_subscriptions: subscriptions, _subscriptions: subscriptions,
} }
} }
fn parse_amount(&self, input: &Entity<InputState>, cx: &Context<Self>) -> Option<u64> {
match input.read(cx).value().trim().parse::<u64>() {
Ok(amount) if amount > 0 => Some(amount),
_ => None,
}
}
fn setup(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let mint = self.mint_input.read(cx).value().trim().to_string();
if mint.is_empty() {
window.push_notification(Notification::error("Enter a mint URL"), cx);
return;
}
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.setup(mint, cx);
});
}
fn send_token(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(amount) = self.parse_amount(&self.send_input, cx) else {
window.push_notification(Notification::error("Enter an amount in sats"), cx);
return;
};
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.send_token(amount, cx);
});
}
fn create_invoice(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(amount) = self.parse_amount(&self.invoice_input, cx) else {
window.push_notification(Notification::error("Enter an amount in sats"), cx);
return;
};
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.create_mint_quote(amount, cx);
});
}
fn claim_quote(&mut self, quote_id: String, cx: &mut Context<Self>) {
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.claim_mint_quote(quote_id, cx);
});
}
fn receive_token(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let token = self.token_input.read(cx).value().trim().to_string();
if token.is_empty() {
window.push_notification(Notification::error("Paste a cashu token"), cx);
return;
}
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.receive_token(token, cx);
});
}
fn render_balance(&self, cx: &Context<Self>) -> impl IntoElement { fn render_balance(&self, cx: &Context<Self>) -> impl IntoElement {
let balance = WalletRegistry::global(cx).read(cx).balance(); let balance = WalletRegistry::global(cx).read(cx).balance();
h_flex() v_flex()
.gap_2() .items_center()
.items_end() .gap_1()
.child( .child(
div() div()
.text_2xl() .text_xl()
.child(SharedString::from(balance.to_string())), .child(SharedString::from(balance.to_string())),
) )
.child( .child(
@@ -155,41 +82,24 @@ impl WalletPanel {
) )
} }
fn render_setup(&self, cx: &mut Context<Self>) -> impl IntoElement { fn render_actions(&self) -> impl IntoElement {
let has_wallet = WalletRegistry::global(cx).read(cx).has_wallet(); h_flex()
.justify_center()
v_flex() .gap_2()
.gap_1p5()
.w_full()
.child( .child(
div() Button::new("send")
.text_xs() .label("Send")
.font_semibold() .primary()
.text_color(cx.theme().muted_foreground) .on_click(|_, window, cx| open_send_dialog(window, cx)),
.child(SharedString::from(if has_wallet {
"Add a mint:"
} else {
"Set up your wallet with a mint:"
})),
) )
.child( .child(
h_flex() Button::new("receive")
.gap_1() .label("Receive")
.w_full() .on_click(|_, window, cx| open_receive_dialog(window, cx)),
.child(Input::new(&self.mint_input).cleanable(true))
.child(
Button::new("setup")
.label(if has_wallet { "Add" } else { "Set up" })
.primary()
.small()
.on_click(cx.listener(move |this, _ev, window, cx| {
this.setup(window, cx);
})),
),
) )
} }
fn render_send(&self, cx: &Context<Self>) -> impl IntoElement { fn render_sent_token(&self, cx: &Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.gap_1p5() .gap_1p5()
.w_full() .w_full()
@@ -198,21 +108,7 @@ impl WalletPanel {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.text_color(cx.theme().muted_foreground) .text_color(cx.theme().muted_foreground)
.child("Send ecash:"), .child("Sent token:"),
)
.child(
h_flex()
.gap_1()
.w_full()
.child(Input::new(&self.send_input).cleanable(true))
.child(
Button::new("send")
.label("Send")
.small()
.on_click(cx.listener(move |this, _ev, window, cx| {
this.send_token(window, cx);
})),
),
) )
.when_some(self.sent_token.as_ref(), |this, token| { .when_some(self.sent_token.as_ref(), |this, token| {
this.child( this.child(
@@ -239,110 +135,43 @@ impl WalletPanel {
}) })
} }
fn render_receive(&self, cx: &Context<Self>) -> impl IntoElement { fn render_history(
let quotes = WalletRegistry::global(cx).read(cx).mint_quotes().to_vec(); &mut self,
history: Vec<HistoryEntry>,
let mut section = cx: &mut Context<Self>,
v_flex() ) -> impl IntoElement {
.gap_1p5()
.w_full()
.child(
div()
.text_xs()
.font_semibold()
.text_color(cx.theme().muted_foreground)
.child("Receive:"),
)
.child(
h_flex()
.gap_1()
.w_full()
.child(Input::new(&self.invoice_input).cleanable(true))
.child(Button::new("invoice").label("Invoice").small().on_click(
cx.listener(move |this, _ev, window, cx| {
this.create_invoice(window, cx);
}),
)),
);
for quote in quotes {
section = section.child(self.render_quote(&quote, cx));
}
section.child(
h_flex()
.gap_1()
.w_full()
.child(Input::new(&self.token_input).cleanable(true))
.child(
Button::new("receive")
.label("Redeem")
.small()
.on_click(cx.listener(move |this, _ev, window, cx| {
this.receive_token(window, cx);
})),
),
)
}
fn render_quote(&self, quote: &MintQuoteEntry, cx: &Context<Self>) -> impl IntoElement {
h_flex()
.gap_1()
.w_full()
.child(
div()
.flex_1()
.min_w_0()
.truncate()
.text_color(cx.theme().muted_foreground)
.child(SharedString::from(quote.request.clone())),
)
.child(
Clipboard::new(SharedString::from(format!("copy_{}", quote.quote_id)))
.accessibility_label("Copy lightning invoice")
.value_fn({
let request = SharedString::from(quote.request.clone());
move |_, _| request.clone()
}),
)
.child(
Button::new(SharedString::from(format!("claim_{}", quote.quote_id)))
.label("Claim")
.small()
.on_click({
let quote_id = quote.quote_id.clone();
cx.listener(move |this, _ev, _window, cx| {
this.claim_quote(quote_id.clone(), cx);
})
}),
)
}
fn render_history(&self, cx: &Context<Self>) -> impl IntoElement {
let history = WalletRegistry::global(cx).read(cx).history().to_vec();
let mut section = v_flex().gap_1().w_full().child(
div()
.text_xs()
.font_semibold()
.text_color(cx.theme().muted_foreground)
.child("History:"),
);
if history.is_empty() { if history.is_empty() {
section = section.child( return div()
div() .text_xs()
.text_xs() .text_color(cx.theme().muted_foreground)
.text_color(cx.theme().muted_foreground) .child("No transactions yet.")
.child("No transactions yet."), .into_any_element();
);
} }
for entry in history { uniform_list(
section = section.child(self.render_history_entry(&entry, cx)); "wallet-history",
} history.len(),
cx.processor(move |this, range, _window, cx| {
this.render_history_rows(range, &history, cx)
}),
)
.flex_1()
.min_h_0()
.into_any_element()
}
section fn render_history_rows(
&self,
range: std::ops::Range<usize>,
history: &[HistoryEntry],
cx: &Context<Self>,
) -> Vec<AnyElement> {
range
.map(|ix| {
self.render_history_entry(&history[ix], cx)
.into_any_element()
})
.collect()
} }
fn render_history_entry(&self, entry: &HistoryEntry, cx: &Context<Self>) -> impl IntoElement { fn render_history_entry(&self, entry: &HistoryEntry, cx: &Context<Self>) -> impl IntoElement {
@@ -370,6 +199,202 @@ impl WalletPanel {
} }
} }
/// Ask for an amount and send ecash.
fn open_send_dialog(window: &mut Window, cx: &mut App) {
let registry = WalletRegistry::global(cx);
let receiver_input = cx.new(|cx| InputState::new(window, cx).placeholder("npub… (optional)"));
let amount_input = cx.new(|cx| InputState::new(window, cx).placeholder("Amount (sats)"));
let send: SubmitFn = {
let receiver_input = receiver_input.clone();
let amount_input = amount_input.clone();
Rc::new(move |_, window, cx| {
let Some(amount) = parse_amount(&amount_input, cx) else {
window.push_notification(Notification::error("Enter an amount in sats"), cx);
return false;
};
let receiver = receiver_input.read(cx).value().trim().to_string();
let recipient = match receiver.as_str() {
"" => None,
value => match PublicKey::parse(value) {
Ok(recipient) => Some(recipient),
Err(_) => {
window.push_notification(
Notification::error("Receiver must be an npub or a public key"),
cx,
);
return false;
}
},
};
match recipient {
Some(recipient) => registry.update(cx, |registry, cx| {
registry.send_nutzap(recipient, amount, None, None, cx);
}),
None => registry.update(cx, |registry, cx| {
registry.send_token(amount, cx);
}),
}
true
})
};
window.open_dialog(cx, move |this, _window, _cx| {
this.width(px(380.))
.title("Send eCash")
.on_ok({
let send = send.clone();
move |event, window, cx| send(event, window, cx)
})
.child(
v_flex()
.gap_3()
.child(
v_flex()
.gap_1p5()
.child(
div()
.text_xs()
.font_semibold()
.child("To (npub, optional):"),
)
.child(Input::new(&receiver_input).cleanable(true)),
)
.child(Input::new(&amount_input).cleanable(true))
.child(
h_flex()
.gap_1()
.justify_end()
.child(
Button::new("cancel")
.label("Cancel")
.ghost()
.on_click(|_, window, cx| window.close_dialog(cx)),
)
.child(Button::new("send").label("Send").primary().on_click({
let send = send.clone();
move |event, window, cx| {
if send(event, window, cx) {
window.close_dialog(cx);
}
}
})),
),
)
});
}
/// Create a lightning invoice or redeem a pasted cashu token
fn open_receive_dialog(window: &mut Window, cx: &mut App) {
let amount_input = cx.new(|cx| InputState::new(window, cx).placeholder("Amount (sats)"));
let token_input = cx.new(|cx| InputState::new(window, cx).placeholder("cashu…"));
let create_invoice: SubmitFn = {
let input = amount_input.clone();
Rc::new(move |_, window, cx| {
let Some(amount) = parse_amount(&input, cx) else {
window.push_notification(Notification::error("Enter an amount in sats"), cx);
return false;
};
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.create_mint_quote(amount, cx);
});
true
})
};
let redeem: SubmitFn = {
let input = token_input.clone();
Rc::new(move |_, window, cx| {
let token = input.read(cx).value().trim().to_string();
if token.is_empty() {
window.push_notification(Notification::error("Paste a cashu token"), cx);
return false;
}
WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.receive_token(token, cx);
});
true
})
};
window.open_dialog(cx, move |this, _window, _cx| {
this.width(px(420.))
.title("Receive")
.on_ok({
let token_input = token_input.clone();
let create_invoice = create_invoice.clone();
let redeem = redeem.clone();
move |_event, window, cx| {
if !token_input.read(cx).value().trim().is_empty() {
redeem(_event, window, cx)
} else {
create_invoice(_event, window, cx)
}
}
})
.child(
v_flex()
.gap_3()
.child(
v_flex()
.gap_1p5()
.child(div().text_xs().font_semibold().child("Lightning invoice:"))
.child(
h_flex()
.gap_1()
.w_full()
.child(Input::new(&amount_input).cleanable(true))
.child(Button::new("invoice").label("Invoice").on_click({
let create_invoice = create_invoice.clone();
move |event, window, cx| {
if create_invoice(event, window, cx) {
window.close_dialog(cx);
}
}
})),
),
)
.child(Separator::horizontal())
.child(
v_flex()
.gap_1p5()
.child(div().text_xs().font_semibold().child("Cashu token:"))
.child(
h_flex()
.gap_1()
.w_full()
.child(Input::new(&token_input).cleanable(true))
.child(Button::new("redeem").label("Redeem").on_click({
let redeem = redeem.clone();
move |event, window, cx| {
if redeem(event, window, cx) {
window.close_dialog(cx);
}
}
})),
),
),
)
});
}
fn parse_amount(input: &Entity<InputState>, cx: &App) -> Option<u64> {
match input.read(cx).value().trim().parse::<u64>() {
Ok(amount) if amount > 0 => Some(amount),
_ => None,
}
}
impl gpui_kit::component::dock::BasePanel for WalletPanel { impl gpui_kit::component::dock::BasePanel for WalletPanel {
fn panel_name(&self) -> &'static str { fn panel_name(&self) -> &'static str {
"Wallet" "Wallet"
@@ -400,23 +425,26 @@ impl Focusable for WalletPanel {
impl Render for WalletPanel { impl Render for WalletPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_wallet = WalletRegistry::global(cx).read(cx).has_wallet(); let history = WalletRegistry::global(cx).read(cx).history().to_vec();
v_flex() v_flex()
.size_full()
.p_3() .p_3()
.gap_3() .gap_3()
.w_full()
.overflow_y_scrollbar()
.text_xs() .text_xs()
.when(!has_wallet, |this| this.child(self.render_setup(cx))) .child(self.render_balance(cx))
.when(has_wallet, |this| { .child(self.render_actions())
this.child(self.render_balance(cx)) .when_some(self.sent_token.as_ref(), |this, _token| {
.child(Separator::horizontal()) this.child(self.render_sent_token(cx))
.child(self.render_send(cx))
.child(self.render_receive(cx))
.child(Separator::horizontal())
.child(self.render_history(cx))
.child(self.render_setup(cx))
}) })
.child(Separator::horizontal())
.child(
div()
.text_xs()
.font_semibold()
.text_color(cx.theme().muted_foreground)
.child("History:"),
)
.child(self.render_history(history, cx))
} }
} }
+59
View File
@@ -41,6 +41,8 @@ pub struct Preferences {
radius_input: Entity<InputState>, radius_input: Entity<InputState>,
radius_lg_input: Entity<InputState>, radius_lg_input: Entity<InputState>,
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
mint_input: Entity<InputState>,
#[cfg(not(target_arch = "wasm32"))]
_speech_poll: Task<()>, _speech_poll: Task<()>,
/// Keeps the control subscriptions alive while the dialog is open /// Keeps the control subscriptions alive while the dialog is open
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
@@ -90,6 +92,9 @@ impl Preferences {
.max(RADIUS_RANGE.1 as f64) .max(RADIUS_RANGE.1 as f64)
}); });
#[cfg(not(target_arch = "wasm32"))]
let mint_input = cx.new(|cx| InputState::new(window, cx).placeholder("https://mint…"));
let subscriptions = vec![ let subscriptions = vec![
Self::wire_number_input(&font_size_input, FONT_SIZE_RANGE, cx, |value, cx| { Self::wire_number_input(&font_size_input, FONT_SIZE_RANGE, cx, |value, cx| {
if let Some(font_size) = value { if let Some(font_size) = value {
@@ -140,6 +145,8 @@ impl Preferences {
radius_input, radius_input,
radius_lg_input, radius_lg_input,
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
mint_input,
#[cfg(not(target_arch = "wasm32"))]
_speech_poll, _speech_poll,
_subscriptions: subscriptions, _subscriptions: subscriptions,
} }
@@ -185,6 +192,56 @@ impl Preferences {
} }
} }
/// Create the NIP-60 wallet or add a mint to it
#[cfg(not(target_arch = "wasm32"))]
fn setup_mint(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let mint = self.mint_input.read(cx).value().trim().to_string();
if mint.is_empty() {
window.push_notification(Notification::error("Enter a mint URL"), cx);
return;
}
nip60::WalletRegistry::global(cx).update(cx, |registry, cx| {
registry.setup(mint, cx);
});
}
/// The NIP-60 wallet block; absent from the web build
fn render_wallet(
&mut self,
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))] cx: &mut Context<Self>,
) -> AnyElement {
#[cfg(target_arch = "wasm32")]
{
return div().into_any_element();
}
#[cfg(not(target_arch = "wasm32"))]
{
const WALLET: &str = "Cashu mint for your NIP-60 wallet. Set up creates the wallet, Add extends it with another mint.";
let has_wallet = nip60::WalletRegistry::global(cx).read(cx).has_wallet();
settings_block(
"Wallet",
WALLET,
h_flex()
.gap_1()
.child(div().flex_1().child(Input::new(&self.mint_input)))
.child(
Button::new("setup-mint")
.label(if has_wallet { "Add" } else { "Set up" })
.ghost()
.size_8()
.on_click(cx.listener(move |this, _ev, window, cx| {
this.setup_mint(window, cx);
})),
),
cx,
)
.into_any_element()
}
}
/// Set the appearance mode (system, light or dark) /// Set the appearance mode (system, light or dark)
fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) { fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) {
AppSettings::update_appearance_mode(mode, cx); AppSettings::update_appearance_mode(mode, cx);
@@ -546,5 +603,7 @@ impl Render for Preferences {
), ),
cx, cx,
)) ))
.child(Separator::horizontal())
.child(self.render_wallet(cx))
} }
} }
+5 -4
View File
@@ -61,7 +61,6 @@ enum Command {
ShowContactList, ShowContactList,
ShowInbox, ShowInbox,
ShowBrowse, ShowBrowse,
#[cfg(not(target_arch = "wasm32"))]
ShowWallet, ShowWallet,
NewChat, NewChat,
NewGroup, NewGroup,
@@ -333,10 +332,12 @@ impl Workspace {
let panel = browse::init(self.dock.downgrade(), window, cx); let panel = browse::init(self.dock.downgrade(), window, cx);
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx); self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
} }
#[cfg(not(target_arch = "wasm32"))]
Command::ShowWallet => { Command::ShowWallet => {
let panel = nip60_ui::wallet::init(window, cx); #[cfg(not(target_arch = "wasm32"))]
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx); {
let panel = nip60_ui::wallet::init(window, cx);
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
}
} }
Command::NewChat => { Command::NewChat => {
new_chat::open(self.dock.downgrade(), window, cx); new_chat::open(self.dock.downgrade(), window, cx);
+13 -15
View File
@@ -39,23 +39,9 @@ impl Sidebar {
let requests_open = self.requests_open; let requests_open = self.requests_open;
let contacts_open = self.contacts_open && !requests_open; let contacts_open = self.contacts_open && !requests_open;
let rows = Rc::new(self.rows_for(cx)); let rows = Rc::new(self.rows_for(cx));
// Whether to show the create button for the contact picker // Whether to show the create button for the contact picker
let create_button = contacts_open && !self.selected_contacts.is_empty(); let create_button = contacts_open && !self.selected_contacts.is_empty();
#[cfg(not(target_arch = "wasm32"))]
let wallet_row = Some(
Nav::new("wallet")
.prefix(Icon::new(gpui_kit::assets::IconName::Wallet).small())
.label("Wallet")
.text_sm()
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(crate::Command::ShowWallet), cx)
}),
);
#[cfg(target_arch = "wasm32")]
let wallet_row: Option<Nav> = None;
let (list_id, title) = if contacts_open { let (list_id, title) = if contacts_open {
("sidebar-contacts", Some("Contacts")) ("sidebar-contacts", Some("Contacts"))
} else if requests_open { } else if requests_open {
@@ -73,12 +59,25 @@ impl Sidebar {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.relative() .relative()
.border_l_1()
.border_color(cx.theme().sidebar_border)
.child( .child(
v_flex() v_flex()
.px_2() .px_2()
.gap_1() .gap_1()
.items_start() .items_start()
.justify_start() .justify_start()
.when(cfg!(not(target_arch = "wasm32")), |this| {
this.child(
Nav::new("wallet")
.prefix(Icon::new(gpui_kit::assets::IconName::Wallet).small())
.label("Wallet")
.text_sm()
.on_click(|_, window, cx| {
window.dispatch_action(Box::new(crate::Command::ShowWallet), cx)
}),
)
})
.child( .child(
Nav::new("browse") Nav::new("browse")
.prefix(Icon::new(IconName::Compass).small()) .prefix(Icon::new(IconName::Compass).small())
@@ -88,7 +87,6 @@ impl Sidebar {
window.dispatch_action(Box::new(Command::ShowBrowse), cx) window.dispatch_action(Box::new(Command::ShowBrowse), cx)
}), }),
) )
.when_some(wallet_row, |this, row| this.child(row))
.child( .child(
Nav::new("reqs") Nav::new("reqs")
.prefix(Icon::new(IconName::Invite).small()) .prefix(Icon::new(IconName::Invite).small())