update ui
This commit is contained in:
+270
-242
@@ -1,9 +1,12 @@
|
||||
use ::nip60::{HistoryEntry, MintQuoteEntry, WalletEvent, WalletRegistry};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ::nip60::{HistoryEntry, WalletEvent, WalletRegistry};
|
||||
use common::TimestampExt;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Render, SharedString, Styled, Subscription, Window, div,
|
||||
AnyElement, App, AppContext, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Window, div, px,
|
||||
uniform_list,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
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::input::{Input, InputState};
|
||||
use gpui_kit::component::notification::Notification;
|
||||
use gpui_kit::component::scroll::ScrollableElement as _;
|
||||
use gpui_kit::component::separator::Separator;
|
||||
use gpui_kit::component::{ActiveTheme, Icon, Sizable, WindowExt, h_flex, v_flex};
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<WalletPanel> {
|
||||
cx.new(|cx| WalletPanel::new(window, cx))
|
||||
}
|
||||
|
||||
type SubmitFn = Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool>;
|
||||
|
||||
pub struct WalletPanel {
|
||||
name: SharedString,
|
||||
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
|
||||
sent_token: Option<SharedString>,
|
||||
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
}
|
||||
|
||||
impl WalletPanel {
|
||||
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 mut subscriptions = SmallVec::new();
|
||||
subscriptions.push(
|
||||
@@ -68,83 +58,20 @@ impl WalletPanel {
|
||||
Self {
|
||||
name: "Wallet".into(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
mint_input,
|
||||
send_input,
|
||||
invoice_input,
|
||||
token_input,
|
||||
sent_token: None,
|
||||
_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 {
|
||||
let balance = WalletRegistry::global(cx).read(cx).balance();
|
||||
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.items_end()
|
||||
v_flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_2xl()
|
||||
.text_xl()
|
||||
.child(SharedString::from(balance.to_string())),
|
||||
)
|
||||
.child(
|
||||
@@ -155,41 +82,24 @@ impl WalletPanel {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_setup(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let has_wallet = WalletRegistry::global(cx).read(cx).has_wallet();
|
||||
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
fn render_actions(&self) -> impl IntoElement {
|
||||
h_flex()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child(SharedString::from(if has_wallet {
|
||||
"Add a mint:"
|
||||
} else {
|
||||
"Set up your wallet with a mint:"
|
||||
})),
|
||||
Button::new("send")
|
||||
.label("Send")
|
||||
.primary()
|
||||
.on_click(|_, window, cx| open_send_dialog(window, cx)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.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);
|
||||
})),
|
||||
),
|
||||
Button::new("receive")
|
||||
.label("Receive")
|
||||
.on_click(|_, window, cx| open_receive_dialog(window, cx)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_send(&self, cx: &Context<Self>) -> impl IntoElement {
|
||||
fn render_sent_token(&self, cx: &Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
@@ -198,21 +108,7 @@ impl WalletPanel {
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Send ecash:"),
|
||||
)
|
||||
.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);
|
||||
})),
|
||||
),
|
||||
.child("Sent token:"),
|
||||
)
|
||||
.when_some(self.sent_token.as_ref(), |this, token| {
|
||||
this.child(
|
||||
@@ -239,110 +135,43 @@ impl WalletPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_receive(&self, cx: &Context<Self>) -> impl IntoElement {
|
||||
let quotes = WalletRegistry::global(cx).read(cx).mint_quotes().to_vec();
|
||||
|
||||
let mut section =
|
||||
v_flex()
|
||||
.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("e, 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:"),
|
||||
);
|
||||
|
||||
fn render_history(
|
||||
&mut self,
|
||||
history: Vec<HistoryEntry>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
if history.is_empty() {
|
||||
section = section.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("No transactions yet."),
|
||||
);
|
||||
return div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("No transactions yet.")
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
for entry in history {
|
||||
section = section.child(self.render_history_entry(&entry, cx));
|
||||
}
|
||||
uniform_list(
|
||||
"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 {
|
||||
@@ -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 {
|
||||
fn panel_name(&self) -> &'static str {
|
||||
"Wallet"
|
||||
@@ -400,23 +425,26 @@ impl Focusable for WalletPanel {
|
||||
|
||||
impl Render for WalletPanel {
|
||||
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()
|
||||
.size_full()
|
||||
.p_3()
|
||||
.gap_3()
|
||||
.w_full()
|
||||
.overflow_y_scrollbar()
|
||||
.text_xs()
|
||||
.when(!has_wallet, |this| this.child(self.render_setup(cx)))
|
||||
.when(has_wallet, |this| {
|
||||
this.child(self.render_balance(cx))
|
||||
.child(Separator::horizontal())
|
||||
.child(self.render_send(cx))
|
||||
.child(self.render_receive(cx))
|
||||
.child(Separator::horizontal())
|
||||
.child(self.render_history(cx))
|
||||
.child(self.render_setup(cx))
|
||||
.child(self.render_balance(cx))
|
||||
.child(self.render_actions())
|
||||
.when_some(self.sent_token.as_ref(), |this, _token| {
|
||||
this.child(self.render_sent_token(cx))
|
||||
})
|
||||
.child(Separator::horizontal())
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("History:"),
|
||||
)
|
||||
.child(self.render_history(history, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ pub struct Preferences {
|
||||
radius_input: Entity<InputState>,
|
||||
radius_lg_input: Entity<InputState>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mint_input: Entity<InputState>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
_speech_poll: Task<()>,
|
||||
/// Keeps the control subscriptions alive while the dialog is open
|
||||
_subscriptions: Vec<Subscription>,
|
||||
@@ -90,6 +92,9 @@ impl Preferences {
|
||||
.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![
|
||||
Self::wire_number_input(&font_size_input, FONT_SIZE_RANGE, cx, |value, cx| {
|
||||
if let Some(font_size) = value {
|
||||
@@ -140,6 +145,8 @@ impl Preferences {
|
||||
radius_input,
|
||||
radius_lg_input,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mint_input,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
_speech_poll,
|
||||
_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)
|
||||
fn set_appearance_mode(mode: AppearanceMode, cx: &mut App) {
|
||||
AppSettings::update_appearance_mode(mode, cx);
|
||||
@@ -546,5 +603,7 @@ impl Render for Preferences {
|
||||
),
|
||||
cx,
|
||||
))
|
||||
.child(Separator::horizontal())
|
||||
.child(self.render_wallet(cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ enum Command {
|
||||
ShowContactList,
|
||||
ShowInbox,
|
||||
ShowBrowse,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ShowWallet,
|
||||
NewChat,
|
||||
NewGroup,
|
||||
@@ -333,10 +332,12 @@ impl Workspace {
|
||||
let panel = browse::init(self.dock.downgrade(), window, cx);
|
||||
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Command::ShowWallet => {
|
||||
let panel = nip60_ui::wallet::init(window, cx);
|
||||
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let panel = nip60_ui::wallet::init(window, cx);
|
||||
self.add_panel_to_dock(panel, DockPlacement::Center, window, cx);
|
||||
}
|
||||
}
|
||||
Command::NewChat => {
|
||||
new_chat::open(self.dock.downgrade(), window, cx);
|
||||
|
||||
@@ -39,23 +39,9 @@ impl Sidebar {
|
||||
let requests_open = self.requests_open;
|
||||
let contacts_open = self.contacts_open && !requests_open;
|
||||
let rows = Rc::new(self.rows_for(cx));
|
||||
|
||||
// Whether to show the create button for the contact picker
|
||||
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 {
|
||||
("sidebar-contacts", Some("Contacts"))
|
||||
} else if requests_open {
|
||||
@@ -73,12 +59,25 @@ impl Sidebar {
|
||||
.flex_1()
|
||||
.min_h_0()
|
||||
.relative()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().sidebar_border)
|
||||
.child(
|
||||
v_flex()
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.items_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(
|
||||
Nav::new("browse")
|
||||
.prefix(Icon::new(IconName::Compass).small())
|
||||
@@ -88,7 +87,6 @@ impl Sidebar {
|
||||
window.dispatch_action(Box::new(Command::ShowBrowse), cx)
|
||||
}),
|
||||
)
|
||||
.when_some(wallet_row, |this, row| this.child(row))
|
||||
.child(
|
||||
Nav::new("reqs")
|
||||
.prefix(Icon::new(IconName::Invite).small())
|
||||
|
||||
Reference in New Issue
Block a user