From bc195e771e488e26e1e6cc6e79e80a38b847c7ea Mon Sep 17 00:00:00 2001 From: HamroRamro <109491096+HamroRamro@users.noreply.github.com> Date: Tue, 27 Sep 2022 05:24:53 -0700 Subject: [PATCH] Update WalletTransactions pagination default settings (#4074) * Update WalletTransactions pagination default settings Remove the numeric page selection and add displaying data of last 30 days by default. * Update WalletTransactions to show txs based on Days * Update text formatting on WalletTransactions view Keeps the logic changes. Just undo the formatting of the file from previous commit * Update WalletTransactions to show all after second load Utilize Model.Days instead of new variables Moved javascript code to PageFootContent section * Update WalletTransactions to use ajax for infinite scroll * Cleanups * Apply skip and count only when not prefiltering * Infinite scroll mode * Improve datetime formatting and switching * Upgrade NBXplorer to include get_wallets_recent bugfix * Revert "Upgrade NBXplorer to include get_wallets_recent bugfix" This reverts commit b390d942d74d88bb1da3ab8e3407184a527175ef. * JS fixes * Upgrade ChromeDriver and BundleMinifier Co-authored-by: Dennis Reimann --- .../Extensions/ViewsRazor.cs | 33 +-- BTCPayServer.Tests/BTCPayServer.Tests.csproj | 2 +- BTCPayServer/BTCPayServer.csproj | 2 +- .../Controllers/UIWalletsController.cs | 8 +- .../Views/UIInvoice/ListInvoices.cshtml | 12 +- .../Views/UINotifications/Index.cshtml | 16 +- .../Views/UIWallets/WalletTransactions.cshtml | 258 ++++++++---------- .../UIWallets/_WalletTransactionsList.cshtml | 119 ++++++++ BTCPayServer/wwwroot/main/site.css | 8 + BTCPayServer/wwwroot/main/site.js | 47 ++-- 10 files changed, 291 insertions(+), 214 deletions(-) create mode 100644 BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml diff --git a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs index c498d522b..623c946d3 100644 --- a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs +++ b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs @@ -88,24 +88,23 @@ namespace BTCPayServer.Abstractions.Extensions public static HtmlString ToBrowserDate(this DateTimeOffset date) { - var displayDate = date.ToString("o", CultureInfo.InvariantCulture); - return new HtmlString($"{displayDate}"); + var displayDate = date.ToString("g", CultureInfo.InvariantCulture); + var dateTime = date.ToString("s", CultureInfo.InvariantCulture); + return new HtmlString($""); } public static HtmlString ToBrowserDate(this DateTime date) { - var displayDate = date.ToString("o", CultureInfo.InvariantCulture); - return new HtmlString($"{displayDate}"); + var displayDate = date.ToString("g", CultureInfo.InvariantCulture); + var dateTime = date.ToString("s", CultureInfo.InvariantCulture); + return new HtmlString($""); } - public static string ToTimeAgo(this DateTimeOffset date) - { - var diff = DateTimeOffset.UtcNow - date; - var formatted = diff.TotalSeconds > 0 - ? $"{diff.TimeString()} ago" - : $"in {diff.Negate().TimeString()}"; - return formatted; - } + public static string ToTimeAgo(this DateTimeOffset date) => (DateTimeOffset.UtcNow - date).ToTimeAgo(); + + public static string ToTimeAgo(this DateTime date) => (DateTimeOffset.UtcNow - date).ToTimeAgo(); + + public static string ToTimeAgo(this TimeSpan diff) => diff.TotalSeconds > 0 ? $"{diff.TimeString()} ago" : $"in {diff.Negate().TimeString()}"; public static string TimeString(this TimeSpan timeSpan) { @@ -117,16 +116,14 @@ namespace BTCPayServer.Abstractions.Extensions { return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}"; } - if (timeSpan.Days < 1) - { - return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}"; - } - return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}"; + return timeSpan.Days < 1 + ? $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}" + : $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}"; } private static string Plural(int value) { - return value > 1 ? "s" : string.Empty; + return value == 1 ? string.Empty : "s"; } } } diff --git a/BTCPayServer.Tests/BTCPayServer.Tests.csproj b/BTCPayServer.Tests/BTCPayServer.Tests.csproj index 6683253d7..6f88d4733 100644 --- a/BTCPayServer.Tests/BTCPayServer.Tests.csproj +++ b/BTCPayServer.Tests/BTCPayServer.Tests.csproj @@ -23,7 +23,7 @@ - + all diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 3bd4494a5..65105432b 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -50,7 +50,7 @@ - + diff --git a/BTCPayServer/Controllers/UIWalletsController.cs b/BTCPayServer/Controllers/UIWalletsController.cs index e4f64b780..cde13d3dd 100644 --- a/BTCPayServer/Controllers/UIWalletsController.cs +++ b/BTCPayServer/Controllers/UIWalletsController.cs @@ -320,14 +320,16 @@ namespace BTCPayServer.Controllers // if we couldn't filter at the db level, we need to apply skip and count if (!preFiltering) { - model.Transactions = model.Transactions.Skip(skip).Take(count) - .ToList(); + model.Transactions = model.Transactions.Skip(skip).Take(count).ToList(); } } model.CryptoCode = walletId.CryptoCode; - return View(model); + //If ajax call then load the partial view + return Request.Headers["X-Requested-With"] == "XMLHttpRequest" + ? PartialView("_WalletTransactionsList", model) + : View(model); } [HttpGet("{walletId}/histogram/{type}")] diff --git a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml index c8da2c4ec..05537fdc9 100644 --- a/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml +++ b/BTCPayServer/Views/UIInvoice/ListInvoices.cshtml @@ -29,8 +29,6 @@ }); }); - delegate('click', '#switchTimeFormat', switchTimeFormat) - delegate('click', '.changeInvoiceState', e => { const { invoiceId, newState } = e.target.dataset; const pavpill = $("#pavpill_" + invoiceId); @@ -286,9 +284,7 @@ Date - - - + Order Id Invoice Id @@ -304,11 +300,7 @@ - - - @invoice.Date.ToBrowserDate() - - + @invoice.Date.ToBrowserDate() @if (invoice.RedirectUrl != string.Empty) { diff --git a/BTCPayServer/Views/UINotifications/Index.cshtml b/BTCPayServer/Views/UINotifications/Index.cshtml index 246011fe3..6c10c30da 100644 --- a/BTCPayServer/Views/UINotifications/Index.cshtml +++ b/BTCPayServer/Views/UINotifications/Index.cshtml @@ -33,17 +33,15 @@ - - @@ -56,11 +54,7 @@ - + @@ -130,8 +124,6 @@ else updateSelectors(); }) - delegate('click', '#switchTimeFormat', switchTimeFormat) - delegate('click', '.btn-toggle-seen', e => { const row = $(e.target).parents(".notification-row").toggleClass("loading"); const guid = row.data("guid"); diff --git a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml index 07a890990..6b64d2d95 100644 --- a/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml +++ b/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml @@ -1,7 +1,9 @@ @model ListTransactionsViewModel + @{ var walletId = Context.GetRouteValue("walletId").ToString(); var labelFilter = Context.Request.Query["labelFilter"].ToString(); + Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", walletId); } @@ -17,29 +19,21 @@ max-width: 200px; } } - + /* pull actions area, so that it is besides the search form */ @@media (min-width: 1200px) { #Filter + #Dropdowns { margin-top: -4rem; float: right; } - + #Filter + #Dropdowns #Actions { order: 1; } - } - - .unconf > * { - opacity: 0.5; } - .switchTimeFormat { - display: block; - max-width: 125px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + .unconf > * { + opacity: 0.5; } .badge-container { @@ -60,16 +54,20 @@ background-color: transparent; border: 0; } - + .label-tooltip .tooltip-inner { max-width: 15rem; text-align: left; } - + .label-tooltip ul { margin: 0; padding-left: var(--btcpay-space-m); } + + #LoadingIndicator { + margin-bottom: 1.5rem; + } } @@ -79,12 +77,81 @@ @* Custom Range Modal *@ } @@ -115,8 +182,8 @@
- +
-
+
+ @if (Model.Items.Count > 0) { } + Date - - - + Message Actions - - @item.Created.ToBrowserDate() - - @item.Created.ToBrowserDate() @item.Body
@@ -151,136 +216,27 @@ - - @foreach (var transaction in Model.Transactions) - { - - - - - - @if (transaction.Positive) - { - - } - else - { - - } - - - } + +
- + Date - - - + Label Transaction Id
- - - - @transaction.Timestamp.ToBrowserDate() - - - @if (transaction.Labels.Any()) - { -
- @foreach (var label in transaction.Labels) - { -
-
- @label.Text - -
- - -
-
- @if (!string.IsNullOrEmpty(label.Link)) - { - - - Transaction details - - - } -
- } -
- } -
- - @transaction.Id - - @transaction.Balance@transaction.Balance -
- - -
-
- @if (string.IsNullOrEmpty(transaction.Comment)) - { - - } - else - { - - } - -
-
- + + +
+
+ Loading... +
+
+
+ + +
} else { @@ -291,6 +247,6 @@ else

If BTCPay Server shows you an invalid balance, rescan your wallet. -
+
If some transactions appear in BTCPay Server, but are missing in another wallet, follow these instructions.

diff --git a/BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml b/BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml new file mode 100644 index 000000000..10cc920c2 --- /dev/null +++ b/BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml @@ -0,0 +1,119 @@ +@model ListTransactionsViewModel + +@foreach (var transaction in Model.Transactions) +{ + + + + + + @transaction.Timestamp.ToBrowserDate() + + + @if (transaction.Labels.Any()) + { +
+ @foreach (var label in transaction.Labels) + { +
+
+ @label.Text + +
+ + +
+
+ @if (!string.IsNullOrEmpty(label.Link)) + { + + + Transaction details + + + } +
+ } +
+ } + + + + @transaction.Id + + + @if (transaction.Positive) + { + @transaction.Balance + } + else + { + @transaction.Balance + } + +
+ + +
+
+ @if (string.IsNullOrEmpty(transaction.Comment)) + { + + } + else + { + + } + +
+ + +} diff --git a/BTCPayServer/wwwroot/main/site.css b/BTCPayServer/wwwroot/main/site.css index ab8dd0cc2..bd5aa3b6b 100644 --- a/BTCPayServer/wwwroot/main/site.css +++ b/BTCPayServer/wwwroot/main/site.css @@ -96,6 +96,14 @@ a.unobtrusive-link { transform: rotate(-180deg); } +time[datetime] { + display: block; + max-width: 125px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + /* Badges */ .badge-new { background: #d4edda; diff --git a/BTCPayServer/wwwroot/main/site.js b/BTCPayServer/wwwroot/main/site.js index 88eb721bb..4776ea600 100644 --- a/BTCPayServer/wwwroot/main/site.js +++ b/BTCPayServer/wwwroot/main/site.js @@ -1,6 +1,30 @@ const flatpickrInstances = []; -document.addEventListener("DOMContentLoaded", function () { +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat +const dtFormatOpts = { dateStyle: 'short', timeStyle: 'short' }; + +const formatDateTimes = mode => { + if (!mode) mode = 'localized'; + // select only elements which haven't been initialized before, those without data-localized + document.querySelectorAll("time[datetime]:not([data-localized])").forEach($el => { + const date = new Date($el.getAttribute("datetime")); + // initialize and set localized attribute + $el.dataset.localized = new Intl.DateTimeFormat('default', dtFormatOpts).format(date); + // set text to chosen mode + if ($el.dataset[mode]) $el.innerText = $el.dataset[mode]; + }); +}; + +const switchTimeFormat = event => { + const curr = event.target.dataset.mode || 'localized'; + const mode = curr === 'relative' ? 'localized' : 'relative'; + document.querySelectorAll("time[datetime]").forEach($el => { + $el.innerText = $el.dataset[mode]; + }); + event.target.dataset.mode = mode; +}; + +document.addEventListener("DOMContentLoaded", () => { // sticky header const stickyHeader = document.querySelector('.sticky-header-setup + .sticky-header'); if (stickyHeader) { @@ -12,13 +36,7 @@ document.addEventListener("DOMContentLoaded", function () { $("#TimezoneOffset").val(timezoneOffset); // localize all elements that have localizeDate class - $(".localizeDate").each(function (index) { - var serverDate = $(this).text(); - var localDate = new Date(serverDate); - - var dateString = localDate.toLocaleDateString() + " " + localDate.toLocaleTimeString(); - $(this).text(dateString); - }); + formatDateTimes(); function updateTimeAgo(){ var timeagoElements = $("[data-timeago-unixms]"); @@ -119,6 +137,9 @@ document.addEventListener("DOMContentLoaded", function () { } }); }); + + // Time Format + delegate('click', '#switchTimeFormat', switchTimeFormat); // Theme Switch delegate('click', '.btcpay-theme-switch', e => { @@ -172,14 +193,4 @@ document.addEventListener("DOMContentLoaded", function () { } }); -function switchTimeFormat() { - $(".switchTimeFormat").each(function (index) { - var htmlVal = $(this).html(); - var switchVal = $(this).attr("data-switch"); - - $(this).html(switchVal); - $(this).attr("data-switch", htmlVal); - }); -} -