From b1a03372ce93803b2ee36efe289690d145ac665d Mon Sep 17 00:00:00 2001 From: Jonathan Hult Date: Sun, 13 Oct 2024 16:08:16 -0500 Subject: [PATCH] feat: format currency with commas and strip unnecessary decimal 0s --- src/account_activity/table_traits.rs | 6 +- src/blocks/components.rs | 28 +++++-- src/blocks/table_traits.rs | 10 +-- src/common/functions.rs | 112 ++++++++++++++++++++++++++ src/internal_commands/table_traits.rs | 2 +- src/user_commands/page.rs | 8 +- src/zk_apps/page.rs | 6 +- src/zk_apps/table_trait.rs | 4 +- 8 files changed, 155 insertions(+), 21 deletions(-) diff --git a/src/account_activity/table_traits.rs b/src/account_activity/table_traits.rs index 4d289c98e..0eb4c6a70 100644 --- a/src/account_activity/table_traits.rs +++ b/src/account_activity/table_traits.rs @@ -126,7 +126,7 @@ impl TableData for Vec> { snark.get_date_time(), ), convert_to_span(snark.get_prover()), - decorate_with_mina_tag(snark.get_fee()), + decorate_with_mina_tag(strip_decimal_if_zero(snark.get_fee())), ], None => vec![], }) @@ -214,7 +214,7 @@ impl TableData for Vec> { block.get_date_time(), ), convert_to_span(block.get_creator_account()), - decorate_with_mina_tag(block.get_coinbase()), + decorate_with_mina_tag(strip_decimal_if_zero(block.get_coinbase())), convert_to_pill(block.get_transaction_count(), ColorVariant::Blue), convert_to_pill(block.get_snark_job_count(), ColorVariant::Blue), convert_to_link( @@ -331,7 +331,7 @@ impl TableData for Vec> { internal_command.get_state_hash(), format!("/blocks/{}", internal_command.get_state_hash()), ), - decorate_with_mina_tag(internal_command.get_fee()), + decorate_with_mina_tag(strip_decimal_if_zero(internal_command.get_fee())), convert_to_pill(internal_command.get_type(), ColorVariant::Grey), convert_to_title( print_time_since(&internal_command.get_block_datetime()), diff --git a/src/blocks/components.rs b/src/blocks/components.rs index 676fa7cd4..25bfff782 100644 --- a/src/blocks/components.rs +++ b/src/blocks/components.rs @@ -222,21 +222,27 @@ pub fn BlockAnalytics(block: BlocksQueryBlocks) -> impl IntoView { @@ -429,7 +435,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView { }, SpotlightEntry { label: "Coinbase".to_string(), - any_el: Some(decorate_with_mina_tag(get_coinbase(&block))), + any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero(get_coinbase( + &block, + )))), ..Default::default() }, SpotlightEntry { @@ -445,7 +453,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView { }, SpotlightEntry { label: "SNARK Fees".to_string(), - any_el: Some(decorate_with_mina_tag(get_snark_fees(&block))), + any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero( + get_snark_fees(&block), + ))), ..Default::default() }, SpotlightEntry { @@ -465,7 +475,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView { }, SpotlightEntry { label: "Transaction Fees".to_string(), - any_el: Some(decorate_with_mina_tag(get_transaction_fees(&block))), + any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero( + get_transaction_fees(&block), + ))), ..Default::default() }, SpotlightEntry { @@ -478,7 +490,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView { }, SpotlightEntry { label: "Total MINA".to_string(), - any_el: Some(decorate_with_mina_tag(get_total_currency(&block))), + any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero( + get_total_currency(&block), + ))), ..Default::default() }, SpotlightEntry { diff --git a/src/blocks/table_traits.rs b/src/blocks/table_traits.rs index 415f67386..23b1a8a2c 100644 --- a/src/blocks/table_traits.rs +++ b/src/blocks/table_traits.rs @@ -63,7 +63,7 @@ impl TableData for Vec> { get_creator_account(block), format!("/addresses/accounts/{}", get_creator_account(block)), ), - decorate_with_mina_tag(get_coinbase(block)), + decorate_with_mina_tag(strip_decimal_if_zero(get_coinbase(block))), convert_to_pill( get_transaction_count(block).map_or_else(String::new, |o| o.to_string()), ColorVariant::Blue, @@ -134,9 +134,9 @@ impl TableData for Vec> { format!("/addresses/accounts/{}", user_command.get_to()), ), convert_to_pill(format_number(user_command.get_nonce()), ColorVariant::Grey), - decorate_with_mina_tag(nanomina_to_mina( + decorate_with_mina_tag(strip_decimal_if_zero(nanomina_to_mina( user_command.get_fee().parse::().ok().unwrap_or(0), - )), + ))), decorate_with_mina_tag(nanomina_to_mina( user_command.get_amount().parse::().ok().unwrap_or(0), )), @@ -230,7 +230,7 @@ impl TableData for Vec> { get_snark_prover(snark), format!("/addresses/accounts/{}", get_snark_prover(snark)), ), - decorate_with_mina_tag(get_snark_fee(snark)), + decorate_with_mina_tag(strip_decimal_if_zero(get_snark_fee(snark))), ], None => vec![], }) @@ -247,7 +247,7 @@ impl TableData for Vec> { fee_transfer.get_receipient(), format!("/addresses/accounts/{}", fee_transfer.get_receipient()), ), - decorate_with_mina_tag(fee_transfer.get_fee()), + decorate_with_mina_tag(strip_decimal_if_zero(fee_transfer.get_fee())), convert_to_pill(fee_transfer.get_type(), ColorVariant::Grey), ], None => vec![], diff --git a/src/common/functions.rs b/src/common/functions.rs index b7df33ad0..2854b9aaa 100644 --- a/src/common/functions.rs +++ b/src/common/functions.rs @@ -445,6 +445,64 @@ pub fn decorate_with_currency_tag( .into() } +/// Format currency by stripping out the decimal if it is 0 and calling +/// [format_with_commas]. +pub fn strip_decimal_if_zero(num_str: String) -> String { + // Check for special cases first + match num_str.to_lowercase().as_str() { + "inf" | "+inf" | "infinity" | "+infinity" => return "inf".to_string(), + "-inf" | "-infinity" => return "-inf".to_string(), + "nan" => return "NaN".to_string(), + _ => {} + } + + // Remove existing commas + let no_commas = num_str.replace(',', ""); + + // Try to parse as a number + if let Ok(num) = no_commas.parse::() { + if num == 0.0 { + return "0".to_string(); + } + + // Split the string into integer and fractional parts + let parts: Vec<&str> = no_commas.split('.').collect(); + + match parts.as_slice() { + [integer] => format_with_commas(integer.parse::().unwrap()), + [integer, fraction] => { + let int_with_commas = format_with_commas(integer.parse::().unwrap()); + let trimmed_fraction = fraction.trim_end_matches('0'); + if trimmed_fraction.is_empty() { + int_with_commas + } else { + format!("{}.{}", int_with_commas, trimmed_fraction) + } + } + _ => num_str, // Should never happen for valid numbers + } + } else { + // If parsing fails, return the original string + num_str + } +} + +/// Format [num][i64] with commas (every 3 digits). +fn format_with_commas(num: i64) -> String { + use std::fmt::Write; + + let mut result = String::new(); + write!(&mut result, "{}", num).unwrap(); + let mut formatted = String::new(); + for (count, c) in result.chars().rev().enumerate() { + if count != 0 && count % 3 == 0 { + formatted.push(','); + } + formatted.push(c); + } + formatted.chars().rev().collect() +} + pub fn convert_to_tooltip(tooltip: String) -> HtmlElement { view! { HtmlElement String { + super::strip_decimal_if_zero(num_str.to_string()) + } + + #[test] + fn test_whole_numbers() { + assert_eq!(strip_decimal_if_zero("1440.0"), "1,440"); + assert_eq!(strip_decimal_if_zero("100.0"), "100"); + assert_eq!(strip_decimal_if_zero("1000"), "1,000"); + assert_eq!(strip_decimal_if_zero("1000000"), "1,000,000"); + } + + #[test] + fn test_decimal_numbers() { + assert_eq!(strip_decimal_if_zero("0.54335"), "0.54335"); + assert_eq!(strip_decimal_if_zero("3.14"), "3.14"); + assert_eq!(strip_decimal_if_zero("0.1000000000000000"), "0.1"); + assert_eq!(strip_decimal_if_zero("1234567.89"), "1,234,567.89"); + } + + #[test] + fn test_high_precision_numbers() { + assert_eq!(strip_decimal_if_zero("0.100000000000000000"), "0.1"); + assert_eq!( + strip_decimal_if_zero("1.234567890123456789"), + "1.234567890123456789" + ); + } + + #[test] + fn test_numbers_with_commas() { + assert_eq!(strip_decimal_if_zero("1,440.0"), "1,440"); + assert_eq!(strip_decimal_if_zero("1,000,000.0"), "1,000,000"); + assert_eq!(strip_decimal_if_zero("1,234,567.890"), "1,234,567.89"); + } + + #[test] + fn test_non_numeric_strings() { + assert_eq!(strip_decimal_if_zero("not a number"), "not a number"); + assert_eq!(strip_decimal_if_zero("123abc"), "123abc"); + } + + #[test] + fn test_edge_cases() { + assert_eq!(strip_decimal_if_zero("0.0"), "0"); + assert_eq!(strip_decimal_if_zero("-0.0"), "0"); + assert_eq!(strip_decimal_if_zero("inf"), "inf"); + assert_eq!(strip_decimal_if_zero("-inf"), "-inf"); + assert_eq!(strip_decimal_if_zero("NaN"), "NaN"); + } +} + pub fn generate_random_string(len: usize) -> String { iter::repeat(()) .map(|()| rand::thread_rng().sample(Alphanumeric)) diff --git a/src/internal_commands/table_traits.rs b/src/internal_commands/table_traits.rs index 239c5ffa7..f9ac66092 100644 --- a/src/internal_commands/table_traits.rs +++ b/src/internal_commands/table_traits.rs @@ -30,7 +30,7 @@ impl TableData for Vec> { internal_command.get_receipient(), format!("/addresses/accounts/{}", internal_command.get_receipient()), ), - decorate_with_mina_tag(internal_command.get_fee()), + decorate_with_mina_tag(strip_decimal_if_zero(internal_command.get_fee())), convert_to_pill(internal_command.get_type(), ColorVariant::Grey), convert_to_title( print_time_since(&internal_command.get_block_datetime()), diff --git a/src/user_commands/page.rs b/src/user_commands/page.rs index 9642dc356..b34f617e2 100644 --- a/src/user_commands/page.rs +++ b/src/user_commands/page.rs @@ -160,7 +160,7 @@ pub fn CommandSpotlightPage() -> impl IntoView { label: "Amount".to_string(), any_el: { let amount_el = decorate_with_mina_tag( - transaction.get_amount(), + strip_decimal_if_zero(transaction.get_amount()), ); Some( if transaction.get_kind() == STAKE_DELEGATION_TYPE { @@ -181,7 +181,11 @@ pub fn CommandSpotlightPage() -> impl IntoView { }, SpotlightEntry { label: "Fee".to_string(), - any_el: Some(decorate_with_mina_tag(transaction.get_fee())), + any_el: Some( + decorate_with_mina_tag( + strip_decimal_if_zero(transaction.get_fee()), + ), + ), ..Default::default() }, SpotlightEntry { diff --git a/src/zk_apps/page.rs b/src/zk_apps/page.rs index cec3a3af3..2de5c55b5 100644 --- a/src/zk_apps/page.rs +++ b/src/zk_apps/page.rs @@ -46,7 +46,11 @@ pub fn ZkAppSpotlight() -> impl IntoView { spotlight_items=vec![ SpotlightEntry { label: String::from("Balance"), - any_el: Some(decorate_with_mina_tag("1324.593847562".to_string())), + any_el: Some( + decorate_with_mina_tag( + strip_decimal_if_zero("1324.593847562".to_string()), + ), + ), ..Default::default() }, SpotlightEntry { diff --git a/src/zk_apps/table_trait.rs b/src/zk_apps/table_trait.rs index a7d7f0b8b..5507378dd 100644 --- a/src/zk_apps/table_trait.rs +++ b/src/zk_apps/table_trait.rs @@ -8,7 +8,7 @@ impl TableData for Vec> { .map(|opt_app| match opt_app { Some(zk_app) => vec![ convert_to_link(zk_app.validator_pk.to_string(), "#".to_string()), - decorate_with_mina_tag(zk_app.balance.to_string()), + decorate_with_mina_tag(strip_decimal_if_zero(zk_app.balance.to_string())), convert_to_pill(format_number(zk_app.nonce.to_string()), ColorVariant::Blue), convert_to_link(zk_app.delegate.to_string(), "#".to_string()), ], @@ -37,7 +37,7 @@ impl TableData for Vec> { .collect::>(), ) .attr("class", "block"), - decorate_with_mina_tag(txn.fee.to_string()), + decorate_with_mina_tag(strip_decimal_if_zero(txn.fee.to_string())), ], None => vec![], })